diff --git a/src/app/features/sidebar/nimbus-sidebar.component.ts b/src/app/features/sidebar/nimbus-sidebar.component.ts
index b43ff8c..f4c6337 100644
--- a/src/app/features/sidebar/nimbus-sidebar.component.ts
+++ b/src/app/features/sidebar/nimbus-sidebar.component.ts
@@ -62,7 +62,7 @@ import { FilterService } from '../../services/filter.service';
{{ open.quick ? '▾' : '▸' }}
⚡
- Quick Links
+ Quick Links
@@ -241,11 +241,16 @@ export class NimbusSidebarComponent implements OnChanges {
onHomeClick(event: MouseEvent): void {
event.preventDefault();
- this.toggleSection('quick');
- this.quickLinkSelected.emit('all');
- queueMicrotask(async () => {
- await this.urlState.filterByKind('markdown');
- });
+ this.open = { quick: true, folders: false, tags: false, trash: false, tests: false };
+ this.sidebar.open('quick');
+ void this.urlState.setQuickWithMarkdown('all');
+ }
+
+ onQuickLinksHeaderClick(event: MouseEvent): void {
+ event.preventDefault();
+ this.open = { quick: true, folders: false, tags: false, trash: false, tests: false };
+ this.sidebar.open('quick');
+ void this.urlState.setQuickWithMarkdown('all');
}
onMarkdownPlaygroundClick(): void {
diff --git a/src/app/layout/app-shell-nimbus/app-shell-nimbus.component.ts b/src/app/layout/app-shell-nimbus/app-shell-nimbus.component.ts
index 8350884..646b088 100644
--- a/src/app/layout/app-shell-nimbus/app-shell-nimbus.component.ts
+++ b/src/app/layout/app-shell-nimbus/app-shell-nimbus.component.ts
@@ -395,6 +395,7 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
private flyoutCloseTimer: any = null;
tagFilter: string | null = null;
quickLinkFilter: 'favoris' | 'publish' | 'draft' | 'template' | 'task' | 'private' | 'archive' | null = null;
+ private suppressNextNoteSelection = false;
// --- URL State <-> Layout sync ---
private mapUrlQuickToInternal(q: string | null): AppShellNimbusLayoutComponent['quickLinkFilter'] {
@@ -511,14 +512,24 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
this.quickLinkFilter = internal;
this.folderFilter = null;
this.tagFilter = null;
- if (!hasNote) this.autoSelectFirstNote();
- if (!this.responsive.isDesktop()) this.mobileNav.setActiveTab('list');
+ if (internal === 'favoris') {
+ this.suppressNextNoteSelection = true;
+ }
+ if (!hasNote && !this.suppressNextNoteSelection) {
+ this.autoSelectFirstNote();
+ }
+ if (!this.responsive.isDesktop()) {
+ this.mobileNav.setActiveTab('list');
+ }
+ } else if (!hasNote && !this.suppressNextNoteSelection) {
+ this.autoSelectFirstNote();
}
// Auto-open quick flyout when quick filter is active
if (this.hoveredFlyout !== 'quick') {
console.log('🎨 Layout - opening quick flyout for quick filter');
this.openFlyout('quick');
}
+ this.suppressNextNoteSelection = false;
} else {
// No filters -> show all
if (this.folderFilter || this.tagFilter || this.quickLinkFilter) {
@@ -527,6 +538,7 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
this.quickLinkFilter = null;
if (!hasNote) this.autoSelectFirstNote();
}
+ this.suppressNextNoteSelection = false;
// Close any open flyout when no filters
if (this.hoveredFlyout) {
console.log('🎨 Layout - closing flyout (no active filters)');
@@ -722,6 +734,9 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
}
onQuickLink(_id: string) {
+ const suppressAutoSelect = _id === 'all' || _id === 'favorites';
+ this.suppressNextNoteSelection = suppressAutoSelect;
+
if (_id === 'all') {
// Show all pages: clear filters and focus list
this.folderFilter = null;
@@ -735,7 +750,7 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
this.mobileNav.setActiveTab('list');
}
this.scheduleCloseFlyout(150);
- this.urlState.showAllAndReset();
+ this.urlState.setQuickWithMarkdown('all');
} else if (_id === 'publish') {
// Filter by publish: true
this.folderFilter = null;
@@ -749,7 +764,9 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
}
this.scheduleCloseFlyout(150);
const label = this.mapInternalQuickToUrl('publish');
- if (label) this.urlState.filterByQuickLink(label);
+ if (label) {
+ this.urlState.setQuickWithMarkdown(label);
+ }
} else if (_id === 'favorites') {
// Filter by favoris: true
this.folderFilter = null;
@@ -763,7 +780,9 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
}
this.scheduleCloseFlyout(150);
const label = this.mapInternalQuickToUrl('favoris');
- if (label) this.urlState.filterByQuickLink(label);
+ if (label) {
+ this.urlState.setQuickWithMarkdown(label);
+ }
} else if (_id === 'templates') {
// Filter by template: true
this.folderFilter = null;
@@ -777,7 +796,9 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
}
this.scheduleCloseFlyout(150);
const label = this.mapInternalQuickToUrl('template');
- if (label) this.urlState.filterByQuickLink(label);
+ if (label) {
+ this.urlState.setQuickWithMarkdown(label);
+ }
} else if (_id === 'tasks') {
// Filter by task: true
this.folderFilter = null;
@@ -791,7 +812,9 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
}
this.scheduleCloseFlyout(150);
const label = this.mapInternalQuickToUrl('task');
- if (label) this.urlState.filterByQuickLink(label);
+ if (label) {
+ this.urlState.setQuickWithMarkdown(label);
+ }
} else if (_id === 'drafts') {
// Filter by draft: true
this.folderFilter = null;
@@ -805,7 +828,9 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
}
this.scheduleCloseFlyout(150);
const label = this.mapInternalQuickToUrl('draft');
- if (label) this.urlState.filterByQuickLink(label);
+ if (label) {
+ this.urlState.setQuickWithMarkdown(label);
+ }
} else if (_id === 'private') {
// Filter by private: true
this.folderFilter = null;
@@ -819,7 +844,9 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
}
this.scheduleCloseFlyout(150);
const label = this.mapInternalQuickToUrl('private');
- if (label) this.urlState.filterByQuickLink(label);
+ if (label) {
+ this.urlState.setQuickWithMarkdown(label);
+ }
} else if (_id === 'archive') {
// Filter by archive: true
this.folderFilter = null;
@@ -833,10 +860,15 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
}
this.scheduleCloseFlyout(150);
const label = this.mapInternalQuickToUrl('archive');
- if (label) this.urlState.filterByQuickLink(label);
+ if (label) {
+ this.urlState.setQuickWithMarkdown(label);
+ }
}
// Auto-select first note after filter changes
- this.autoSelectFirstNote();
+ if (!this.suppressNextNoteSelection) {
+ this.autoSelectFirstNote();
+ }
+ this.suppressNextNoteSelection = false;
}
onTagSelected(tagName: string) {
diff --git a/src/app/services/url-state.service.ts b/src/app/services/url-state.service.ts
index ce33ab9..5c97556 100644
--- a/src/app/services/url-state.service.ts
+++ b/src/app/services/url-state.service.ts
@@ -1,7 +1,7 @@
import { Injectable, inject, signal, effect, computed, OnDestroy } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { VaultService } from '../../services/vault.service';
-import { filter, takeUntil, map } from 'rxjs/operators';
+import { filter, takeUntil, map, distinctUntilChanged } from 'rxjs/operators';
import { startWith } from 'rxjs';
import { Subject } from 'rxjs';
import type { Note } from '../../types';
@@ -168,6 +168,22 @@ export class UrlStateService implements OnDestroy {
console.log('🌐 UrlStateService - new state:', newState);
const previousState = this.currentStateSignal();
+ // Normalize when multiple section params (tag/folder/quick) coexist in the raw URL
+ // Keep only the active one determined by priority, preserve note/search/kind
+ const rawHasMultiple = (Number(!!params['tag']) + Number(!!params['folder']) + Number(!!params['quick'])) > 1;
+ if (rawHasMultiple) {
+ setTimeout(() => {
+ const active = this.getActiveSection(newState);
+ if (active) {
+ this.updateUrl({
+ tag: active === 'tag' ? newState.tag! : null,
+ folder: active === 'folder' ? newState.folder! : null,
+ quick: active === 'quick' ? newState.quick! : null,
+ });
+ }
+ }, 0);
+ }
+
const changed = this.detectChanges(previousState, newState);
console.log('🌐 UrlStateService - changed keys:', changed);
if (changed.length > 0) {
@@ -208,6 +224,7 @@ export class UrlStateService implements OnDestroy {
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
+ this.stateChangeSubject.complete();
}
// ========================================
@@ -320,15 +337,16 @@ export class UrlStateService implements OnDestroy {
return;
}
- const note = this.vaultService.allNotes().find(n => n.filePath === trimmed);
+ const normalized = trimmed.replace(/\\/g, '/').replace(/^\/+/, '');
+ const note = this.vaultService.allNotes().find(n => n.filePath === normalized);
if (!note && !options?.force) {
- console.warn(`Note not found: ${trimmed}`);
+ console.warn(`Note not found: ${normalized}`);
return;
}
// Mettre à jour l'URL (même si la note n'est pas encore connue localement lorsqu'on force)
- await this.updateUrl({ note: trimmed });
+ await this.updateUrl({ note: normalized });
}
/**
@@ -351,7 +369,7 @@ export class UrlStateService implements OnDestroy {
*/
async filterByFolder(folder: string): Promise {
// Vérifier que le dossier existe
- const fileTree = this.vaultService.fileTree();
+ const fileTree = this.vaultService.fileTree() ?? [];
const folderExists = this.folderExistsInTree(fileTree, folder);
if (!folderExists) {
@@ -382,17 +400,25 @@ export class UrlStateService implements OnDestroy {
* Filtrer par quick link
*/
async filterByQuickLink(quickLink: string): Promise {
- const validQuickLinks = ['all', 'All Pages', 'Favoris', 'Publié', 'Modèles', 'Tâches', 'Brouillons', 'Privé', 'Archive', 'Corbeille', 'favorites', 'publish', 'drafts', 'templates', 'tasks', 'private', 'archive'];
+ const raw = (quickLink ?? '').trim().toLowerCase();
+ const mapQuick = new Map([
+ ['all', 'all'], ['all pages', 'all'],
+ ['favoris', 'Favoris'], ['favorites', 'favorites'],
+ ['publié', 'Publié'], ['publie', 'Publié'], ['publish', 'publish'],
+ ['modèles', 'Modèles'], ['modeles', 'Modèles'], ['templates', 'templates'],
+ ['tâches', 'Tâches'], ['taches', 'Tâches'], ['tasks', 'tasks'],
+ ['brouillons', 'Brouillons'], ['drafts', 'drafts'],
+ ['privé', 'Privé'], ['prive', 'Privé'], ['private', 'private'],
+ ['archive', 'Archive'], ['corbeille', 'Corbeille']
+ ]);
- if (!validQuickLinks.includes(quickLink)) {
+ const canonical = mapQuick.get(raw);
+ if (!canonical) {
console.warn(`Invalid quick link: ${quickLink}`);
return;
}
- // Si 'All Pages' est sélectionné, on veut supprimer le filtre 'quick'.
- const newQuickValue = (quickLink === 'all' || quickLink === 'All Pages') ? null : quickLink;
-
- // Mettre à jour l'URL
+ const newQuickValue = canonical === 'all' ? null : canonical;
await this.updateUrl({ quick: newQuickValue, note: null, tag: null, folder: null, search: null });
}
@@ -439,22 +465,20 @@ export class UrlStateService implements OnDestroy {
const normalized = (notePath ?? '').trim()
.replace(/\\/g, '/')
.replace(/^\/+/, '');
-
+
if (!normalized) {
console.warn('setNote() called with empty path');
return;
}
- const queryParams: any = { note: normalized };
+ const partial: Partial = { note: normalized, tag: null, quick: null };
if (folderPath) {
- queryParams.folder = folderPath.replace(/\\/g, '/').replace(/^\/+/, '');
+ partial.folder = folderPath.replace(/\\/g, '/').replace(/^\/+/, '');
+ } else {
+ partial.folder = null;
}
- await this.router.navigate([], {
- queryParams,
- queryParamsHandling: 'merge',
- preserveFragment: true
- });
+ await this.updateUrl(partial);
}
/**
@@ -483,11 +507,27 @@ export class UrlStateService implements OnDestroy {
async resetState(): Promise {
await this.router.navigate([], {
queryParams: {},
- queryParamsHandling: 'merge',
+ queryParamsHandling: '',
preserveFragment: true
});
}
+ /**
+ * Appliquer en une seule navigation: quick (ou aucun) + kind=markdown,
+ * en réinitialisant les autres filtres (note, tag, folder, search).
+ */
+ async setQuickWithMarkdown(quickLabel: string | null): Promise {
+ const partial: Partial = {
+ note: null,
+ tag: null,
+ folder: null,
+ search: null,
+ quick: quickLabel ?? null,
+ kind: 'markdown',
+ };
+ await this.updateUrl(partial);
+ }
+
/**
* Générer une URL partageble
*/
@@ -503,7 +543,9 @@ export class UrlStateService implements OnDestroy {
if (stateToShare.search) params.set('search', stateToShare.search);
if (stateToShare.kind) params.set('kind', stateToShare.kind);
- const baseUrl = window.location.origin + window.location.pathname;
+ const baseUrl = (typeof window !== 'undefined')
+ ? window.location.origin + window.location.pathname
+ : this.router.serializeUrl(this.router.createUrlTree([]));
return `${baseUrl}?${params.toString()}`;
}
@@ -543,7 +585,8 @@ export class UrlStateService implements OnDestroy {
*/
onStatePropertyChange(property: keyof UrlState) {
return this.stateChangeSubject.asObservable().pipe(
- filter(event => event.changed.includes(property))
+ filter(event => event.changed.includes(property)),
+ distinctUntilChanged((a, b) => a.current[property] === b.current[property])
);
}
diff --git a/vault/titi/Nouveau-markdown.md b/vault/titi/Nouveau-markdown.md
new file mode 100644
index 0000000..6c11cee
--- /dev/null
+++ b/vault/titi/Nouveau-markdown.md
@@ -0,0 +1,58 @@
+---
+titre: Nouveau-markdown
+auteur: Bruno Charest
+creation_date: 2025-10-19T21:42:53-04:00
+modification_date: 2025-10-30T21:24:35-04:00
+catégorie: ""
+tags: []
+aliases: []
+status: en-cours
+publish: true
+favoris: false
+template: true
+task: true
+archive: true
+draft: true
+private: true
+toto: tata
+color: "#EF4444"
+---
+Allo ceci est un tests
+toto
+
+# Test 1 Markdown
+
+## Titres
+
+# Niveau 1
+#tag1 #tag2 #test #test2
+
+
+# Nouveau-markdown
+
+## sous-titre
+- [ ] allo
+- [ ] toto
+- [ ] tata
+
+## sous-titre 2
+
+#tag1 #tag2 #tag3 #tag4
+
+## sous-titre 3
+
+## sous-titre 4
+
+## sous-titre 5
+test
+
+## sous-titre 6
+test
+
+## sous-titre 7
+test
+
+## sous-titre 8
+
+
+
diff --git a/vault/Nouveau-markdown.md b/vault/titi/Nouveau-markdown.md.bak
similarity index 100%
rename from vault/Nouveau-markdown.md
rename to vault/titi/Nouveau-markdown.md.bak