perf: optimize Docker build with layer caching and BuildKit support
This commit is contained in:
parent
58bd57543b
commit
c030f91ebe
14
.dockerignore
Normal file
14
.dockerignore
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
.git
|
||||||
|
.angular
|
||||||
|
.vscode
|
||||||
|
*.log
|
||||||
|
tmp
|
||||||
|
db/*.db
|
||||||
|
.env
|
||||||
|
.DS_Store
|
||||||
|
coverage
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
@ -4,17 +4,21 @@
|
|||||||
FROM node:20-bullseye AS builder
|
FROM node:20-bullseye AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install dependencies
|
# Copy ONLY package files first (creates cacheable layer)
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm ci
|
|
||||||
|
|
||||||
# Copy sources
|
# Install dependencies with cache mount for faster rebuilds
|
||||||
|
RUN --mount=type=cache,target=/root/.npm \
|
||||||
|
npm ci
|
||||||
|
|
||||||
|
# Copy source files AFTER npm install (only invalidates cache if code changes)
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Build Angular app (outputs to ./dist)
|
# Build Angular app with cache mount for Angular's build cache
|
||||||
RUN npx ng build --configuration=production
|
RUN --mount=type=cache,target=/app/.angular/cache \
|
||||||
|
npx ng build --configuration=production
|
||||||
|
|
||||||
# Prune dev dependencies to keep only production deps for runtime copy
|
# Prune dev dependencies to keep only production deps
|
||||||
RUN npm prune --omit=dev
|
RUN npm prune --omit=dev
|
||||||
|
|
||||||
# -------------------- Runtime stage --------------------
|
# -------------------- Runtime stage --------------------
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Construit l'image Docker avec toutes les dépendances requises via PowerShell et WSL (Debian).
|
Construit l'image Docker avec toutes les dépendances requises via PowerShell et WSL (Debian).
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Ce script prépare les dépendances et construit l'image Docker sous WSL (Debian).
|
Ce script prépare les dépendances et construit l'image Docker sous WSL (Debian) avec BuildKit activé pour des builds plus rapides.
|
||||||
.PARAMETRES
|
.PARAMETRES
|
||||||
-full : Si présent, effectue une construction complète de l'image Docker (équivalent à --no-cache).
|
-full : Si présent, effectue une construction complète de l'image Docker (équivalent à --no-cache).
|
||||||
.EXEMPLE
|
.EXEMPLE
|
||||||
@ -43,28 +43,77 @@ try {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Compose build command
|
# Check if .dockerignore exists, warn if not
|
||||||
|
$dockerignorePath = Join-Path $projectRoot ".dockerignore"
|
||||||
|
if (-not (Test-Path $dockerignorePath)) {
|
||||||
|
Write-Warning "Aucun fichier .dockerignore trouvé. Créez-en un pour optimiser le build."
|
||||||
|
Write-Host "Exemple de contenu :" -ForegroundColor Yellow
|
||||||
|
Write-Host @"
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
.git
|
||||||
|
.angular
|
||||||
|
.vscode
|
||||||
|
*.log
|
||||||
|
tmp
|
||||||
|
db/*.db
|
||||||
|
.env
|
||||||
|
"@ -ForegroundColor DarkGray
|
||||||
|
}
|
||||||
|
|
||||||
|
# Compose build command with BuildKit enabled
|
||||||
$noCache = if ($full) { '--no-cache' } else { '' }
|
$noCache = if ($full) { '--no-cache' } else { '' }
|
||||||
$innerCmd = "cd '$wslProjectRoot' && docker build $noCache -t obsiviewer-angular:latest -f docker/Dockerfile ."
|
|
||||||
|
# Escape spaces in path for bash
|
||||||
|
$escapedPath = $wslProjectRoot -replace ' ','\\ '
|
||||||
|
|
||||||
|
# Build command parts
|
||||||
|
$cdCmd = "cd `"$wslProjectRoot`""
|
||||||
|
$dockerCmd = "docker build $noCache --progress=plain -t obsiviewer-angular:latest -f docker/Dockerfile ."
|
||||||
|
|
||||||
|
# Complete command with BuildKit
|
||||||
|
$innerCmd = "export DOCKER_BUILDKIT=1 && $cdCmd && $dockerCmd"
|
||||||
|
|
||||||
# Run build inside WSL Debian to use Linux Docker daemon
|
# Run build inside WSL Debian to use Linux Docker daemon
|
||||||
Write-Host "Construction de l'image Docker obsiviewer-angular:latest..." -ForegroundColor Cyan
|
Write-Host "Construction de l'image Docker obsiviewer-angular:latest..." -ForegroundColor Cyan
|
||||||
|
Write-Host "BuildKit activé pour un build optimisé avec cache" -ForegroundColor Cyan
|
||||||
|
|
||||||
$buildResult = wsl -d Debian bash -lc $innerCmd 2>&1
|
$buildResult = wsl -d Debian bash -lc $innerCmd 2>&1
|
||||||
|
|
||||||
|
# Display build output
|
||||||
|
$buildResult | ForEach-Object { Write-Host $_ }
|
||||||
|
|
||||||
if ($LASTEXITCODE -ne 0) {
|
if ($LASTEXITCODE -ne 0) {
|
||||||
Write-Error "Erreur lors de la construction de l'image Docker : $buildResult"
|
Write-Error "Erreur lors de la construction de l'image Docker (code: $LASTEXITCODE)"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Verify the image was built successfully
|
# Verify the image was built successfully
|
||||||
$verifyCmd = "docker image inspect obsiviewer-angular:latest"
|
$verifyCmd = "docker image inspect obsiviewer-angular:latest --format='{{.Size}}'"
|
||||||
Write-Host "Vérification de l'image construite..." -ForegroundColor Cyan
|
Write-Host "`nVérification de l'image construite..." -ForegroundColor Cyan
|
||||||
$verifyResult = wsl -d Debian bash -lc $verifyCmd 2>&1
|
$imageSize = wsl -d Debian bash -lc $verifyCmd 2>&1
|
||||||
|
|
||||||
if ($LASTEXITCODE -ne 0) {
|
if ($LASTEXITCODE -ne 0) {
|
||||||
Write-Error "L'image Docker n'a pas été construite correctement : $verifyResult"
|
Write-Error "L'image Docker n'a pas été construite correctement : $imageSize"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "Image Docker obsiviewer-angular:latest construite avec succès via WSL Debian." -ForegroundColor Green
|
# Convert size to MB
|
||||||
|
$sizeMB = [math]::Round([long]$imageSize / 1MB, 2)
|
||||||
|
|
||||||
|
Write-Host "`n================================================" -ForegroundColor Green
|
||||||
|
Write-Host "✓ Image Docker construite avec succès" -ForegroundColor Green
|
||||||
|
Write-Host " Nom: obsiviewer-angular:latest" -ForegroundColor Green
|
||||||
|
Write-Host " Taille: $sizeMB MB" -ForegroundColor Green
|
||||||
|
Write-Host "================================================" -ForegroundColor Green
|
||||||
|
|
||||||
|
# Display tips for faster rebuilds
|
||||||
|
if (-not $full) {
|
||||||
|
Write-Host "`nConseils pour les prochains builds :" -ForegroundColor Cyan
|
||||||
|
Write-Host " • Les dépendances npm sont mises en cache" -ForegroundColor Gray
|
||||||
|
Write-Host " • Le cache Angular est préservé entre les builds" -ForegroundColor Gray
|
||||||
|
Write-Host " • Seuls les fichiers modifiés déclencheront un rebuild" -ForegroundColor Gray
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
Write-Error "Erreur lors de la construction de l'image Docker : $_"
|
Write-Error "Erreur lors de la construction de l'image Docker : $_"
|
||||||
|
|||||||
@ -15,8 +15,8 @@ export interface ThemePrefs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_PREFS: ThemePrefs = {
|
const DEFAULT_PREFS: ThemePrefs = {
|
||||||
mode: 'system',
|
mode: 'dark',
|
||||||
theme: 'light',
|
theme: 'nord',
|
||||||
language: 'fr'
|
language: 'fr'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,14 @@ import { TagFilterStore } from '../../core/stores/tag-filter.store';
|
|||||||
<svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
<svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div *ngIf="quickLinkFilter() && getQuickLinkDisplay(quickLinkFilter()) as ql" class="flex items-center gap-2 text-xs">
|
||||||
|
<span class="inline-flex items-center gap-1 rounded-full bg-surface1 dark:bg-card text-main dark:text-main px-2 py-1">
|
||||||
|
{{ ql.icon }} {{ ql.name }}
|
||||||
|
</span>
|
||||||
|
<button type="button" (click)="clearQuickLinkFilter.emit()" class="rounded-full hover:bg-slate-500/10 dark:hover:bg-surface2/10 w-6 h-6 inline-flex items-center justify-center" title="Effacer le filtre">
|
||||||
|
<svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
[value]="query()"
|
[value]="query()"
|
||||||
(input)="onQuery($any($event.target).value)"
|
(input)="onQuery($any($event.target).value)"
|
||||||
@ -64,6 +72,7 @@ export class NotesListComponent {
|
|||||||
|
|
||||||
@Output() openNote = new EventEmitter<string>();
|
@Output() openNote = new EventEmitter<string>();
|
||||||
@Output() queryChange = new EventEmitter<string>();
|
@Output() queryChange = new EventEmitter<string>();
|
||||||
|
@Output() clearQuickLinkFilter = new EventEmitter<void>();
|
||||||
|
|
||||||
private store = inject(TagFilterStore);
|
private store = inject(TagFilterStore);
|
||||||
private q = signal('');
|
private q = signal('');
|
||||||
@ -88,6 +97,14 @@ export class NotesListComponent {
|
|||||||
const quickLink = this.quickLinkFilter();
|
const quickLink = this.quickLinkFilter();
|
||||||
let list = this.notes();
|
let list = this.notes();
|
||||||
|
|
||||||
|
// Exclude trash notes by default unless specifically viewing trash
|
||||||
|
if (folder !== '.trash') {
|
||||||
|
list = list.filter(n => {
|
||||||
|
const filePath = (n.filePath || n.originalPath || '').toLowerCase().replace(/\\/g, '/');
|
||||||
|
return !filePath.startsWith('.trash/') && !filePath.includes('/.trash/');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (folder) {
|
if (folder) {
|
||||||
if (folder === '.trash') {
|
if (folder === '.trash') {
|
||||||
// All files anywhere under .trash (including subfolders)
|
// All files anywhere under .trash (including subfolders)
|
||||||
@ -130,6 +147,19 @@ export class NotesListComponent {
|
|||||||
return [...list].sort((a, b) => (score(b) - score(a)));
|
return [...list].sort((a, b) => (score(b) - score(a)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
getQuickLinkDisplay(quickLink: string): { icon: string; name: string } | null {
|
||||||
|
const displays: Record<string, { icon: string; name: string }> = {
|
||||||
|
'favoris': { icon: '❤️', name: 'Favoris' },
|
||||||
|
'publish': { icon: '🌐', name: 'Publish' },
|
||||||
|
'draft': { icon: '📝', name: 'Draft' },
|
||||||
|
'template': { icon: '📑', name: 'Template' },
|
||||||
|
'task': { icon: '🗒️', name: 'Task' },
|
||||||
|
'private': { icon: '🔒', name: 'Private' },
|
||||||
|
'archive': { icon: '🗃️', name: 'Archive' }
|
||||||
|
};
|
||||||
|
return displays[quickLink] || null;
|
||||||
|
}
|
||||||
|
|
||||||
onQuery(v: string) {
|
onQuery(v: string) {
|
||||||
this.q.set(v);
|
this.q.set(v);
|
||||||
this.queryChange.emit(v);
|
this.queryChange.emit(v);
|
||||||
|
|||||||
@ -68,7 +68,7 @@ import { TrashExplorerComponent } from '../../layout/sidebar/trash/trash-explore
|
|||||||
<span class="text-xs text-muted transition-transform duration-200" [class.rotate-90]="!open.folders">{{ open.folders ? '▾' : '▸' }}</span>
|
<span class="text-xs text-muted transition-transform duration-200" [class.rotate-90]="!open.folders">{{ open.folders ? '▾' : '▸' }}</span>
|
||||||
</button>
|
</button>
|
||||||
<div *ngIf="open.folders" class="px-1 py-1">
|
<div *ngIf="open.folders" class="px-1 py-1">
|
||||||
<app-file-explorer [nodes]="nodes" [selectedNoteId]="selectedNoteId" [foldersOnly]="true" (folderSelected)="onFolder($event)" (fileSelected)="onSelect($event)"></app-file-explorer>
|
<app-file-explorer [nodes]="nodes" [selectedNoteId]="selectedNoteId" [foldersOnly]="true" [quickLinkFilter]="quickLinkFilter" (folderSelected)="onFolder($event)" (fileSelected)="onSelect($event)"></app-file-explorer>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -152,6 +152,7 @@ export class AppSidebarDrawerComponent {
|
|||||||
@Input() selectedNoteId: string | null = null;
|
@Input() selectedNoteId: string | null = null;
|
||||||
@Input() vaultName = '';
|
@Input() vaultName = '';
|
||||||
@Input() tags: TagInfo[] = [];
|
@Input() tags: TagInfo[] = [];
|
||||||
|
@Input() quickLinkFilter: 'favoris' | 'publish' | 'draft' | 'template' | 'task' | 'private' | 'archive' | null = null;
|
||||||
@Output() noteSelected = new EventEmitter<string>();
|
@Output() noteSelected = new EventEmitter<string>();
|
||||||
@Output() folderSelected = new EventEmitter<string>();
|
@Output() folderSelected = new EventEmitter<string>();
|
||||||
@Output() tagSelected = new EventEmitter<string>();
|
@Output() tagSelected = new EventEmitter<string>();
|
||||||
|
|||||||
@ -66,6 +66,7 @@ import { VaultService } from '../../../services/vault.service';
|
|||||||
[nodes]="effectiveFileTree"
|
[nodes]="effectiveFileTree"
|
||||||
[selectedNoteId]="selectedNoteId"
|
[selectedNoteId]="selectedNoteId"
|
||||||
[foldersOnly]="true"
|
[foldersOnly]="true"
|
||||||
|
[quickLinkFilter]="quickLinkFilter"
|
||||||
(folderSelected)="folderSelected.emit($event)"
|
(folderSelected)="folderSelected.emit($event)"
|
||||||
(fileSelected)="fileSelected.emit($event)">
|
(fileSelected)="fileSelected.emit($event)">
|
||||||
</app-file-explorer>
|
</app-file-explorer>
|
||||||
@ -145,6 +146,7 @@ export class NimbusSidebarComponent {
|
|||||||
@Input() effectiveFileTree: VaultNode[] = [];
|
@Input() effectiveFileTree: VaultNode[] = [];
|
||||||
@Input() selectedNoteId: string | null = null;
|
@Input() selectedNoteId: string | null = null;
|
||||||
@Input() tags: TagInfo[] = [];
|
@Input() tags: TagInfo[] = [];
|
||||||
|
@Input() quickLinkFilter: 'favoris' | 'publish' | 'draft' | 'template' | 'task' | 'private' | 'archive' | null = null;
|
||||||
|
|
||||||
@Output() toggleSidebarRequest = new EventEmitter<void>();
|
@Output() toggleSidebarRequest = new EventEmitter<void>();
|
||||||
@Output() folderSelected = new EventEmitter<string>();
|
@Output() folderSelected = new EventEmitter<string>();
|
||||||
@ -156,7 +158,7 @@ export class NimbusSidebarComponent {
|
|||||||
@Output() aboutSelected = new EventEmitter<void>();
|
@Output() aboutSelected = new EventEmitter<void>();
|
||||||
|
|
||||||
env = environment;
|
env = environment;
|
||||||
open = { quick: true, folders: true, tags: false, trash: false, tests: true };
|
open = { quick: true, folders: false, tags: false, trash: false, tests: false };
|
||||||
private vault = inject(VaultService);
|
private vault = inject(VaultService);
|
||||||
|
|
||||||
onQuickLink(id: string) { this.quickLinkSelected.emit(id); }
|
onQuickLink(id: string) { this.quickLinkSelected.emit(id); }
|
||||||
|
|||||||
@ -57,6 +57,7 @@ import { AboutPanelComponent } from '../../features/about/about-panel.component'
|
|||||||
[effectiveFileTree]="effectiveFileTree"
|
[effectiveFileTree]="effectiveFileTree"
|
||||||
[selectedNoteId]="selectedNoteId"
|
[selectedNoteId]="selectedNoteId"
|
||||||
[tags]="tags"
|
[tags]="tags"
|
||||||
|
[quickLinkFilter]="quickLinkFilter"
|
||||||
(toggleSidebarRequest)="toggleSidebarRequest.emit()"
|
(toggleSidebarRequest)="toggleSidebarRequest.emit()"
|
||||||
(folderSelected)="onFolderSelected($event)"
|
(folderSelected)="onFolderSelected($event)"
|
||||||
(fileSelected)="noteSelected.emit($event)"
|
(fileSelected)="noteSelected.emit($event)"
|
||||||
@ -100,7 +101,7 @@ import { AboutPanelComponent } from '../../features/about/about-panel.component'
|
|||||||
<ng-container [ngSwitch]="f">
|
<ng-container [ngSwitch]="f">
|
||||||
<app-quick-links *ngSwitchCase="'quick'" (quickLinkSelected)="onQuickLink($event)"></app-quick-links>
|
<app-quick-links *ngSwitchCase="'quick'" (quickLinkSelected)="onQuickLink($event)"></app-quick-links>
|
||||||
<div *ngSwitchCase="'folders'" class="p-2">
|
<div *ngSwitchCase="'folders'" class="p-2">
|
||||||
<app-file-explorer [nodes]="effectiveFileTree" [selectedNoteId]="selectedNoteId" [foldersOnly]="true" (folderSelected)="onFolderSelected($event)" (fileSelected)="noteSelected.emit($event)"></app-file-explorer>
|
<app-file-explorer [nodes]="effectiveFileTree" [selectedNoteId]="selectedNoteId" [foldersOnly]="true" [quickLinkFilter]="quickLinkFilter" (folderSelected)="onFolderSelected($event)" (fileSelected)="noteSelected.emit($event)"></app-file-explorer>
|
||||||
</div>
|
</div>
|
||||||
<div *ngSwitchCase="'tags'" class="p-2">
|
<div *ngSwitchCase="'tags'" class="p-2">
|
||||||
<ul class="space-y-0.5 text-sm">
|
<ul class="space-y-0.5 text-sm">
|
||||||
@ -153,6 +154,7 @@ import { AboutPanelComponent } from '../../features/about/about-panel.component'
|
|||||||
[query]="listQuery"
|
[query]="listQuery"
|
||||||
(openNote)="onOpenNote($event)"
|
(openNote)="onOpenNote($event)"
|
||||||
(queryChange)="onQueryChange($event)"
|
(queryChange)="onQueryChange($event)"
|
||||||
|
(clearQuickLinkFilter)="onClearQuickLinkFilter()"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -224,10 +226,10 @@ import { AboutPanelComponent } from '../../features/about/about-panel.component'
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex-1 overflow-hidden">
|
<div class="flex-1 overflow-hidden">
|
||||||
<div [hidden]="mobileNav.activeTab() !== 'sidebar'" class="h-full overflow-y-auto p-2" appScrollableOverlay>
|
<div [hidden]="mobileNav.activeTab() !== 'sidebar'" class="h-full overflow-y-auto p-2" appScrollableOverlay>
|
||||||
<app-file-explorer [nodes]="effectiveFileTree" [selectedNoteId]="selectedNoteId" [foldersOnly]="true" (folderSelected)="onFolderSelected($event)" (fileSelected)="onOpenNote($event)"></app-file-explorer>
|
<app-file-explorer [nodes]="effectiveFileTree" [selectedNoteId]="selectedNoteId" [foldersOnly]="true" [quickLinkFilter]="quickLinkFilter" (folderSelected)="onFolderSelected($event)" (fileSelected)="onOpenNote($event)"></app-file-explorer>
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="mobileNav.activeTab() !== 'list'" class="h-full overflow-y-auto" appScrollableOverlay>
|
<div [hidden]="mobileNav.activeTab() !== 'list'" class="h-full overflow-y-auto" appScrollableOverlay>
|
||||||
<app-notes-list [notes]="vault.allNotes()" [folderFilter]="folderFilter" [tagFilter]="tagFilter" [quickLinkFilter]="quickLinkFilter" [query]="listQuery" (queryChange)="onQueryChange($event)" (openNote)="onOpenNote($event)"></app-notes-list>
|
<app-notes-list [notes]="vault.allNotes()" [folderFilter]="folderFilter" [tagFilter]="tagFilter" [quickLinkFilter]="quickLinkFilter" [query]="listQuery" (queryChange)="onQueryChange($event)" (openNote)="onOpenNote($event)" (clearQuickLinkFilter)="onClearQuickLinkFilter()"></app-notes-list>
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="mobileNav.activeTab() !== 'page'" class="note-content-area h-full overflow-y-auto px-3 py-4" appScrollableOverlay>
|
<div [hidden]="mobileNav.activeTab() !== 'page'" class="note-content-area h-full overflow-y-auto px-3 py-4" appScrollableOverlay>
|
||||||
<app-markdown-playground *ngIf="activeView === 'markdown-playground'"></app-markdown-playground>
|
<app-markdown-playground *ngIf="activeView === 'markdown-playground'"></app-markdown-playground>
|
||||||
@ -244,6 +246,7 @@ import { AboutPanelComponent } from '../../features/about/about-panel.component'
|
|||||||
[selectedNoteId]="selectedNoteId"
|
[selectedNoteId]="selectedNoteId"
|
||||||
[vaultName]="vaultName"
|
[vaultName]="vaultName"
|
||||||
[tags]="tags"
|
[tags]="tags"
|
||||||
|
[quickLinkFilter]="quickLinkFilter"
|
||||||
(noteSelected)="onNoteSelectedMobile($event)"
|
(noteSelected)="onNoteSelectedMobile($event)"
|
||||||
(folderSelected)="onFolderSelectedFromDrawer($event)"
|
(folderSelected)="onFolderSelectedFromDrawer($event)"
|
||||||
(tagSelected)="onTagSelected($event)"
|
(tagSelected)="onTagSelected($event)"
|
||||||
@ -255,7 +258,7 @@ import { AboutPanelComponent } from '../../features/about/about-panel.component'
|
|||||||
|
|
||||||
@if (mobileNav.activeTab() === 'list') {
|
@if (mobileNav.activeTab() === 'list') {
|
||||||
<div class="h-full flex flex-col overflow-hidden animate-fadeIn">
|
<div class="h-full flex flex-col overflow-hidden animate-fadeIn">
|
||||||
<app-notes-list class="flex-1" [notes]="vault.allNotes()" [folderFilter]="folderFilter" [tagFilter]="tagFilter" [quickLinkFilter]="quickLinkFilter" [query]="listQuery" (queryChange)="onQueryChange($event)" (openNote)="onNoteSelectedMobile($event)"></app-notes-list>
|
<app-notes-list class="flex-1" [notes]="vault.allNotes()" [folderFilter]="folderFilter" [tagFilter]="tagFilter" [quickLinkFilter]="quickLinkFilter" [query]="listQuery" (queryChange)="onQueryChange($event)" (openNote)="onNoteSelectedMobile($event)" (clearQuickLinkFilter)="onClearQuickLinkFilter()"></app-notes-list>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,6 +356,14 @@ export class AppShellNimbusLayoutComponent {
|
|||||||
const quickLink = this.quickLinkFilter;
|
const quickLink = this.quickLinkFilter;
|
||||||
let list = this.vault.allNotes();
|
let list = this.vault.allNotes();
|
||||||
|
|
||||||
|
// Exclude trash notes by default unless specifically viewing trash
|
||||||
|
if (folder !== '.trash') {
|
||||||
|
list = list.filter(n => {
|
||||||
|
const filePath = (n.filePath || n.originalPath || '').toLowerCase().replace(/\\/g, '/');
|
||||||
|
return !filePath.startsWith('.trash/') && !filePath.includes('/.trash/');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (folder) {
|
if (folder) {
|
||||||
if (folder === '.trash') {
|
if (folder === '.trash') {
|
||||||
// All files anywhere under .trash (including subfolders)
|
// All files anywhere under .trash (including subfolders)
|
||||||
@ -603,7 +614,11 @@ export class AppShellNimbusLayoutComponent {
|
|||||||
this.helpPageRequested.emit();
|
this.helpPageRequested.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
onAboutSelected(): void {
|
onClearQuickLinkFilter(): void {
|
||||||
this.showAboutPanel = true;
|
this.folderFilter = null;
|
||||||
|
this.tagFilter = null;
|
||||||
|
this.quickLinkFilter = null;
|
||||||
|
this.listQuery = '';
|
||||||
|
this.autoSelectFirstNote();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,12 +66,33 @@ export class FileExplorerComponent {
|
|||||||
selectedNoteId = input<string | null>(null);
|
selectedNoteId = input<string | null>(null);
|
||||||
foldersOnly = input<boolean>(false);
|
foldersOnly = input<boolean>(false);
|
||||||
useTrashCounts = input<boolean>(false);
|
useTrashCounts = input<boolean>(false);
|
||||||
|
quickLinkFilter = input<'favoris' | 'publish' | 'draft' | 'template' | 'task' | 'private' | 'archive' | null>(null);
|
||||||
fileSelected = output<string>();
|
fileSelected = output<string>();
|
||||||
folderSelected = output<string>();
|
folderSelected = output<string>();
|
||||||
|
|
||||||
private vaultService = inject(VaultService);
|
private vaultService = inject(VaultService);
|
||||||
|
|
||||||
folderCount(path: string): number {
|
folderCount(path: string): number {
|
||||||
|
const quickLink = this.quickLinkFilter();
|
||||||
|
if (quickLink) {
|
||||||
|
// Compute filtered count
|
||||||
|
let filteredNotes = this.vaultService.allNotes().filter(note => {
|
||||||
|
const frontmatter = note.frontmatter || {};
|
||||||
|
return frontmatter[quickLink] === true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Apply folder filter
|
||||||
|
if (path) {
|
||||||
|
const folder = path.toLowerCase().replace(/^\/+|\/+$/g, '');
|
||||||
|
filteredNotes = filteredNotes.filter(note => {
|
||||||
|
const originalPath = (note.originalPath || '').toLowerCase().replace(/^\/+|\/+$/g, '');
|
||||||
|
return originalPath === folder || originalPath.startsWith(folder + '/');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredNotes.length;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.useTrashCounts()) {
|
if (this.useTrashCounts()) {
|
||||||
const counts = this.vaultService.trashFolderCounts();
|
const counts = this.vaultService.trashFolderCounts();
|
||||||
const raw = (path || '').replace(/\\/g, '/');
|
const raw = (path || '').replace(/\\/g, '/');
|
||||||
|
|||||||
@ -11,11 +11,11 @@ aliases: []
|
|||||||
status: en-cours
|
status: en-cours
|
||||||
publish: true
|
publish: true
|
||||||
favoris: true
|
favoris: true
|
||||||
template: false
|
template: true
|
||||||
task: true
|
task: true
|
||||||
archive: false
|
archive: true
|
||||||
draft: false
|
draft: true
|
||||||
private: false
|
private: true
|
||||||
---
|
---
|
||||||
# Archived Note
|
# Archived Note
|
||||||
|
|
||||||
|
|||||||
@ -4,18 +4,18 @@ auteur: Bruno Charest
|
|||||||
creation_date: 2025-10-19T11:13:12-04:00
|
creation_date: 2025-10-19T11:13:12-04:00
|
||||||
modification_date: 2025-10-19T12:09:46-04:00
|
modification_date: 2025-10-19T12:09:46-04:00
|
||||||
catégorie: ""
|
catégorie: ""
|
||||||
|
tags:
|
||||||
|
- bruno
|
||||||
|
- configuration
|
||||||
aliases: []
|
aliases: []
|
||||||
status: en-cours
|
status: en-cours
|
||||||
publish: true
|
publish: true
|
||||||
favoris: true
|
favoris: true
|
||||||
template: false
|
template: true
|
||||||
task: true
|
task: true
|
||||||
archive: false
|
archive: false
|
||||||
draft: false
|
draft: true
|
||||||
private: false
|
private: true
|
||||||
tags:
|
|
||||||
- bruno
|
|
||||||
- configuration
|
|
||||||
---
|
---
|
||||||
# Archived Note
|
# Archived Note
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user