perf: optimize Docker build with layer caching and BuildKit support
This commit is contained in:
@@ -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, '/');
|
||||
|
||||
Reference in New Issue
Block a user