perf: optimize Docker build with layer caching and BuildKit support

This commit is contained in:
2025-10-22 23:13:09 -04:00
parent 58bd57543b
commit c030f91ebe
11 changed files with 172 additions and 36 deletions
+2 -2
View File
@@ -15,8 +15,8 @@ export interface ThemePrefs {
}
const DEFAULT_PREFS: ThemePrefs = {
mode: 'system',
theme: 'light',
mode: 'dark',
theme: 'nord',
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>
</button>
</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"
[value]="query()"
(input)="onQuery($any($event.target).value)"
@@ -64,6 +72,7 @@ export class NotesListComponent {
@Output() openNote = new EventEmitter<string>();
@Output() queryChange = new EventEmitter<string>();
@Output() clearQuickLinkFilter = new EventEmitter<void>();
private store = inject(TagFilterStore);
private q = signal('');
@@ -88,6 +97,14 @@ export class NotesListComponent {
const quickLink = this.quickLinkFilter();
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 === '.trash') {
// All files anywhere under .trash (including subfolders)
@@ -130,6 +147,19 @@ export class NotesListComponent {
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) {
this.q.set(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>
</button>
<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>
</section>
@@ -152,6 +152,7 @@ export class AppSidebarDrawerComponent {
@Input() selectedNoteId: string | null = null;
@Input() vaultName = '';
@Input() tags: TagInfo[] = [];
@Input() quickLinkFilter: 'favoris' | 'publish' | 'draft' | 'template' | 'task' | 'private' | 'archive' | null = null;
@Output() noteSelected = new EventEmitter<string>();
@Output() folderSelected = new EventEmitter<string>();
@Output() tagSelected = new EventEmitter<string>();
@@ -66,6 +66,7 @@ import { VaultService } from '../../../services/vault.service';
[nodes]="effectiveFileTree"
[selectedNoteId]="selectedNoteId"
[foldersOnly]="true"
[quickLinkFilter]="quickLinkFilter"
(folderSelected)="folderSelected.emit($event)"
(fileSelected)="fileSelected.emit($event)">
</app-file-explorer>
@@ -145,6 +146,7 @@ export class NimbusSidebarComponent {
@Input() effectiveFileTree: VaultNode[] = [];
@Input() selectedNoteId: string | null = null;
@Input() tags: TagInfo[] = [];
@Input() quickLinkFilter: 'favoris' | 'publish' | 'draft' | 'template' | 'task' | 'private' | 'archive' | null = null;
@Output() toggleSidebarRequest = new EventEmitter<void>();
@Output() folderSelected = new EventEmitter<string>();
@@ -156,7 +158,7 @@ export class NimbusSidebarComponent {
@Output() aboutSelected = new EventEmitter<void>();
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);
onQuickLink(id: string) { this.quickLinkSelected.emit(id); }
@@ -57,6 +57,7 @@ import { AboutPanelComponent } from '../../features/about/about-panel.component'
[effectiveFileTree]="effectiveFileTree"
[selectedNoteId]="selectedNoteId"
[tags]="tags"
[quickLinkFilter]="quickLinkFilter"
(toggleSidebarRequest)="toggleSidebarRequest.emit()"
(folderSelected)="onFolderSelected($event)"
(fileSelected)="noteSelected.emit($event)"
@@ -100,7 +101,7 @@ import { AboutPanelComponent } from '../../features/about/about-panel.component'
<ng-container [ngSwitch]="f">
<app-quick-links *ngSwitchCase="'quick'" (quickLinkSelected)="onQuickLink($event)"></app-quick-links>
<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 *ngSwitchCase="'tags'" class="p-2">
<ul class="space-y-0.5 text-sm">
@@ -153,6 +154,7 @@ import { AboutPanelComponent } from '../../features/about/about-panel.component'
[query]="listQuery"
(openNote)="onOpenNote($event)"
(queryChange)="onQueryChange($event)"
(clearQuickLinkFilter)="onClearQuickLinkFilter()"
/>
</div>
</section>
@@ -224,10 +226,10 @@ import { AboutPanelComponent } from '../../features/about/about-panel.component'
</div>
<div class="flex-1 overflow-hidden">
<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 [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 [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>
@@ -244,6 +246,7 @@ import { AboutPanelComponent } from '../../features/about/about-panel.component'
[selectedNoteId]="selectedNoteId"
[vaultName]="vaultName"
[tags]="tags"
[quickLinkFilter]="quickLinkFilter"
(noteSelected)="onNoteSelectedMobile($event)"
(folderSelected)="onFolderSelectedFromDrawer($event)"
(tagSelected)="onTagSelected($event)"
@@ -255,7 +258,7 @@ import { AboutPanelComponent } from '../../features/about/about-panel.component'
@if (mobileNav.activeTab() === 'list') {
<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>
}
@@ -353,6 +356,14 @@ export class AppShellNimbusLayoutComponent {
const quickLink = this.quickLinkFilter;
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 === '.trash') {
// All files anywhere under .trash (including subfolders)
@@ -603,7 +614,11 @@ export class AppShellNimbusLayoutComponent {
this.helpPageRequested.emit();
}
onAboutSelected(): void {
this.showAboutPanel = true;
onClearQuickLinkFilter(): void {
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);
foldersOnly = input<boolean>(false);
useTrashCounts = input<boolean>(false);
quickLinkFilter = input<'favoris' | 'publish' | 'draft' | 'template' | 'task' | 'private' | 'archive' | null>(null);
fileSelected = output<string>();
folderSelected = output<string>();
private vaultService = inject(VaultService);
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()) {
const counts = this.vaultService.trashFolderCounts();
const raw = (path || '').replace(/\\/g, '/');