feat: reorder frontmatter keys and add publish/private quick links

This commit is contained in:
Bruno Charest 2025-10-19 22:19:35 -04:00
parent 0f7610bed1
commit 168fcaf049
9 changed files with 144 additions and 42 deletions

View File

@ -162,13 +162,13 @@ test('Should maintain correct key order', async () => {
'tags:',
'aliases:',
'status:',
'publish:',
'favoris:',
'template:',
'task:',
'archive:',
'publish:',
'draft:',
'private:'
'template:',
'task:',
'private:',
'archive:'
];
let lastIndex = -1;

View File

@ -50,7 +50,7 @@ export class NotesListComponent {
folderFilter = input<string | null>(null); // like "folder/subfolder"
query = input<string>('');
tagFilter = input<string | null>(null);
quickLinkFilter = input<'favoris' | 'template' | 'task' | 'draft' | 'archive' | null>(null);
quickLinkFilter = input<'favoris' | 'publish' | 'draft' | 'template' | 'task' | 'private' | 'archive' | null>(null);
@Output() openNote = new EventEmitter<string>();
@Output() queryChange = new EventEmitter<string>();
@ -72,7 +72,7 @@ export class NotesListComponent {
// All files anywhere under .trash (including subfolders)
list = list.filter(n => {
const filePath = (n.filePath || n.originalPath || '').toLowerCase().replace(/\\/g, '/');
return filePath.includes('/.trash/');
return filePath.startsWith('.trash/') || filePath.includes('/.trash/');
});
} else {
list = list.filter(n => {

View File

@ -6,9 +6,11 @@ import { BadgeCountComponent } from '../../shared/ui/badge-count.component';
interface QuickLinkCountsUi {
all: number;
favorites: number;
publish: number;
templates: number;
tasks: number;
drafts: number;
private: number;
archive: number;
}
@ -21,32 +23,44 @@ interface QuickLinkCountsUi {
<ul class="text-sm space-y-1">
<li>
<button (click)="select('all')" class="flex items-center gap-2 px-3 py-2 rounded-lg cursor-pointer hover:bg-slate-500/10 dark:hover:bg-slate-200/10 transition w-full text-left">
<span class="flex items-center gap-2"><span>🗂</span> <span>All pages</span></span>
<span class="flex items-center gap-2"><span>🗂</span> <span>All Pages</span></span>
<app-badge-count class="ml-auto" [count]="counts().all" color="slate"></app-badge-count>
</button>
</li>
<li>
<button (click)="select('favorites')" class="flex items-center gap-2 px-3 py-2 rounded-lg cursor-pointer hover:bg-slate-500/10 dark:hover:bg-slate-200/10 transition w-full text-left">
<span class="flex items-center gap-2"><span></span> <span>Favorites</span></span>
<span class="flex items-center gap-2"><span></span> <span>Favoris</span></span>
<app-badge-count class="ml-auto" [count]="counts().favorites" color="rose"></app-badge-count>
</button>
</li>
<li>
<button (click)="select('templates')" class="flex items-center gap-2 px-3 py-2 rounded-lg cursor-pointer hover:bg-slate-500/10 dark:hover:bg-slate-200/10 transition w-full text-left">
<span class="flex items-center gap-2"><span>📑</span> <span>Templates</span></span>
<button (click)="select('publish')" class="flex items-center gap-2 px-3 py-2 rounded-lg cursor-pointer hover:bg-slate-500/10 dark:hover:bg-slate-200/10 transition w-full text-left">
<span class="flex items-center gap-2"><span>🌐</span> <span>Publish</span></span>
<app-badge-count class="ml-auto" [count]="counts().publish" color="green"></app-badge-count>
</button>
</li>
<li>
<button (click)="select('drafts')" class="flex items-center gap-2 px-3 py-2 rounded-lg cursor-pointer hover:bg-slate-500/10 dark:hover:bg-slate-200/10 transition w-full text-left">
<span class="flex items-center gap-2"><span>📝</span> <span>Draft</span></span>
<app-badge-count class="ml-auto" [count]="counts().drafts" color="emerald"></app-badge-count>
</button>
</li>
<li>
<button (click)="select('templates')" class="flex items-center gap-2 px-3 py-2 rounded-lg cursor-pointer hover:bg-slate-500/10 dark:hover:bg-slate-200/10 transition w-full text left">
<span class="flex items-center gap-2"><span>📑</span> <span>Template</span></span>
<app-badge-count class="ml-auto" [count]="counts().templates" color="amber"></app-badge-count>
</button>
</li>
<li>
<button (click)="select('tasks')" class="flex items-center gap-2 px-3 py-2 rounded-lg cursor-pointer hover:bg-slate-500/10 dark:hover:bg-slate-200/10 transition w-full text-left">
<span class="flex items-center gap-2"><span>🗒</span> <span>Tasks</span></span>
<span class="flex items-center gap-2"><span>🗒</span> <span>Task</span></span>
<app-badge-count class="ml-auto" [count]="counts().tasks" color="indigo"></app-badge-count>
</button>
</li>
<li>
<button (click)="select('drafts')" class="flex items-center gap-2 px-3 py-2 rounded-lg cursor-pointer hover:bg-slate-500/10 dark:hover:bg-slate-200/10 transition w-full text-left">
<span class="flex items-center gap-2"><span>📝</span> <span>Drafts</span></span>
<app-badge-count class="ml-auto" [count]="counts().drafts" color="emerald"></app-badge-count>
<button (click)="select('private')" class="flex items-center gap-2 px-3 py-2 rounded-lg cursor-pointer hover:bg-slate-500/10 dark:hover:bg-slate-200/10 transition w-full text-left">
<span class="flex items-center gap-2"><span>🔒</span> <span>Private</span></span>
<app-badge-count class="ml-auto" [count]="counts().private" color="purple"></app-badge-count>
</button>
</li>
<li>

View File

@ -54,7 +54,7 @@ import { VaultService } from '../../../services/vault.service';
<!-- Folders accordion -->
<section class="border-b border-gray-200 dark:border-gray-800">
<button class="w-full flex items-center justify-between px-3 py-2 text-sm font-semibold hover:bg-gray-100 dark:hover:bg-gray-800"
(click)="open.folders = !open.folders">
(click)="toggleFoldersSection()">
<span>Folders</span>
<span class="text-xs text-gray-500">{{ open.folders ? '▾' : '▸' }}</span>
</button>
@ -92,7 +92,7 @@ import { VaultService } from '../../../services/vault.service';
<!-- Trash accordion -->
<section class="border-b border-gray-200 dark:border-gray-800">
<button class="w-full flex items-center justify-between px-3 py-2 text-sm font-semibold hover:bg-gray-100 dark:hover:bg-gray-800"
(click)="open.trash = !open.trash; folderSelected.emit('.trash')">
(click)="toggleTrashSection()">
<span class="flex items-center gap-2">Trash</span>
<span class="text-xs text-gray-500">{{ open.trash ? '▾' : '▸' }}</span>
</button>
@ -146,4 +146,20 @@ export class NimbusSidebarComponent {
trashCount = () => this.vault.counts().trash;
trashHasContent = () => (this.vault.trashTree() || []).length > 0;
trackNoteId = (_: number, n: { id: string }) => n.id;
toggleFoldersSection(): void {
const next = !this.open.folders;
this.open.folders = next;
if (next) {
this.quickLinkSelected.emit('all');
}
}
toggleTrashSection(): void {
const next = !this.open.trash;
this.open.trash = next;
if (next) {
this.folderSelected.emit('.trash');
}
}
}

View File

@ -261,7 +261,7 @@ export class AppShellNimbusLayoutComponent {
hoveredFlyout: 'quick' | 'folders' | 'tags' | 'trash' | null = null;
private flyoutCloseTimer: any = null;
tagFilter: string | null = null;
quickLinkFilter: 'favoris' | 'template' | 'task' | 'draft' | 'archive' | null = null;
quickLinkFilter: 'favoris' | 'publish' | 'draft' | 'template' | 'task' | 'private' | 'archive' | null = null;
toggleNoteFullScreen(): void {
this.noteFullScreen = !this.noteFullScreen;
@ -324,6 +324,16 @@ export class AppShellNimbusLayoutComponent {
this.mobileNav.setActiveTab('list');
}
this.scheduleCloseFlyout(150);
} else if (_id === 'publish') {
// Filter by publish: true
this.folderFilter = null;
this.tagFilter = null;
this.quickLinkFilter = 'publish';
this.listQuery = '';
if (!this.responsive.isDesktop()) {
this.mobileNav.setActiveTab('list');
}
this.scheduleCloseFlyout(150);
} else if (_id === 'favorites') {
// Filter by favoris: true
this.folderFilter = null;
@ -364,6 +374,16 @@ export class AppShellNimbusLayoutComponent {
this.mobileNav.setActiveTab('list');
}
this.scheduleCloseFlyout(150);
} else if (_id === 'private') {
// Filter by private: true
this.folderFilter = null;
this.tagFilter = null;
this.quickLinkFilter = 'private';
this.listQuery = '';
if (!this.responsive.isDesktop()) {
this.mobileNav.setActiveTab('list');
}
this.scheduleCloseFlyout(150);
} else if (_id === 'archive') {
// Filter by archive: true
this.folderFilter = null;

View File

@ -202,15 +202,6 @@ interface MetadataEntry {
</svg>
{{ getAuthorFromFrontmatter() ?? note().author ?? 'Auteur inconnu' }}
</span>
@if (hasState('publish')) {
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('publish') ? 'text-green-500' : 'text-gray-400'" title="{{ state('publish') ? 'Publié sur le web' : 'Non publié' }}" role="img" aria-label="{{ state('publish') ? 'Publié sur le web' : 'Non publié' }}">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10" />
<path d="M2 12h20" />
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
</svg>
</span>
}
@if (hasState('favoris')) {
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('favoris') ? 'text-rose-500' : 'text-gray-400'" title="{{ state('favoris') ? 'Ajouté aux favoris' : 'Non favori' }}" role="img" aria-label="{{ state('favoris') ? 'Ajouté aux favoris' : 'Non favori' }}">
@if (state('favoris')) {
@ -224,21 +215,13 @@ interface MetadataEntry {
}
</span>
}
@if (hasState('archive')) {
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('archive') ? 'text-amber-600' : 'text-gray-400'" title="{{ state('archive') ? 'Document archivé' : 'Document non archivé' }}" role="img" aria-label="{{ state('archive') ? 'Document archivé' : 'Document non archivé' }}">
@if (state('archive')) {
@if (hasState('publish')) {
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('publish') ? 'text-green-500' : 'text-gray-400'" title="{{ state('publish') ? 'Publié sur le web' : 'Non publié' }}" role="img" aria-label="{{ state('publish') ? 'Publié sur le web' : 'Non publié' }}">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="22,12 18,12 18,8"/>
<path d="M18,2H6a2,2,0,0,0-2,2V22a2,2,0,0,0,2,2H18a2,2,0,0,0,2-2V8Z"/>
<line x1="10" y1="12" x2="14" y2="12"/>
<circle cx="12" cy="12" r="10" />
<path d="M2 12h20" />
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
</svg>
} @else {
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 8v13a2 2 0 01-2 2H5a2 2 0 01-2-2V8"/>
<path d="M3 4h18l-2-2H5l-2 2z"/>
<line x1="10" y1="12" x2="14" y2="12"/>
</svg>
}
</span>
}
@if (hasState('draft')) {
@ -256,6 +239,35 @@ interface MetadataEntry {
}
</span>
}
@if (hasState('template')) {
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('template') ? 'text-amber-500' : 'text-gray-400'" title="{{ state('template') ? 'Modèle' : 'Non modèle' }}" role="img" aria-label="{{ state('template') ? 'Modèle' : 'Non modèle' }}">
@if (state('template')) {
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="4" width="18" height="14" rx="2" />
<path d="M7 8h10" />
<path d="M7 12h6" />
</svg>
} @else {
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="5" width="18" height="12" rx="2" />
</svg>
}
</span>
}
@if (hasState('task')) {
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('task') ? 'text-indigo-500' : 'text-gray-400'" title="{{ state('task') ? 'Tâche' : 'Pas une tâche' }}" role="img" aria-label="{{ state('task') ? 'Tâche' : 'Pas une tâche' }}">
@if (state('task')) {
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="4" width="18" height="16" rx="2" />
<path d="M8 12l2 2 4-4" />
</svg>
} @else {
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="4" width="18" height="16" rx="2" />
</svg>
}
</span>
}
@if (hasState('private')) {
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('private') ? 'text-purple-500' : 'text-gray-400'" title="{{ state('private') ? 'Privé' : 'Public' }}" role="img" aria-label="{{ state('private') ? 'Privé' : 'Public' }}">
@if (state('private')) {
@ -271,6 +283,23 @@ interface MetadataEntry {
}
</span>
}
@if (hasState('archive')) {
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('archive') ? 'text-amber-600' : 'text-gray-400'" title="{{ state('archive') ? 'Document archivé' : 'Document non archivé' }}" role="img" aria-label="{{ state('archive') ? 'Document archivé' : 'Document non archivé' }}">
@if (state('archive')) {
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="22,12 18,12 18,8"/>
<path d="M18,2H6a2,2,0,0,0-2,2V22a2,2,0,0,0,2,2H18a2,2,0,0,0,2-2V8Z"/>
<line x1="10" y1="12" x2="14" y2="12"/>
</svg>
} @else {
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 8v13a2 2 0 01-2 2H5a2 2 0 01-2-2V8"/>
<path d="M3 4h18l-2-2H5l-2 2z"/>
<line x1="10" y1="12" x2="14" y2="12"/>
</svg>
}
</span>
}
</div>
<div [innerHTML]="sanitizedHtmlContent()"></div>
@ -1148,7 +1177,7 @@ export class NoteViewerComponent implements OnDestroy {
this.copyFeedbackTimers.set(block, timeout);
}
hasState(key: 'publish' | 'favoris' | 'archive' | 'draft' | 'private'): boolean {
hasState(key: 'publish' | 'favoris' | 'archive' | 'draft' | 'private' | 'template' | 'task'): boolean {
try {
const fm = (this.note()?.frontmatter ?? {}) as any;
const raw = (fm as any)[key];
@ -1158,7 +1187,7 @@ export class NoteViewerComponent implements OnDestroy {
}
}
state(key: 'publish' | 'favoris' | 'archive' | 'draft' | 'private'): boolean {
state(key: 'publish' | 'favoris' | 'archive' | 'draft' | 'private' | 'template' | 'task'): boolean {
try {
const fm = (this.note()?.frontmatter ?? {}) as any;
const raw = (fm as any)[key];

View File

@ -29,9 +29,11 @@ interface VaultApiResponse {
interface QuickLinkCounts {
all: number;
favorites: number;
publish: number;
templates: number;
tasks: number;
drafts: number;
private: number;
archive: number;
trash: number;
}
@ -449,9 +451,11 @@ export class VaultService implements OnDestroy {
const counts: QuickLinkCounts = {
all: 0,
favorites: 0,
publish: 0,
templates: 0,
tasks: 0,
drafts: 0,
private: 0,
archive: 0,
trash: 0
};
@ -468,9 +472,11 @@ export class VaultService implements OnDestroy {
const fm = note.frontmatter || {};
if (fm.favoris === true) counts.favorites++;
if (fm.publish === true) counts.publish++;
if (fm.template === true) counts.templates++;
if (fm.task === true) counts.tasks++;
if (fm.draft === true) counts.drafts++;
if (fm.private === true) counts.private++;
if (fm.archive === true) counts.archive++;
}

17
vault/Nouveau-markdown.md Normal file
View File

@ -0,0 +1,17 @@
---
titre: Nouveau-markdown
auteur: Bruno Charest
creation_date: 2025-10-19T21:42:53-04:00
modification_date: 2025-10-19T21:43:06-04:00
catégorie: ""
tags: []
aliases: []
status: en-cours
publish: true
favoris: true
template: true
task: true
archive: true
draft: true
private: true
---

View File