refactor: update tag-related styles and UI components with new color system

This commit is contained in:
Bruno Charest 2025-10-21 14:36:26 -04:00
parent 6e50acbd22
commit 0d0607577d
15 changed files with 340 additions and 92 deletions

View File

@ -83,31 +83,31 @@
display: inline; display: inline;
} }
:host ::ng-deep .md-tag-palette-0 { background: rgba(190, 242, 100, 0.35); color: #3f6212; } :host ::ng-deep .md-tag-color-0 { background: rgba(190, 242, 100, 0.35); color: #3f6212; }
:host ::ng-deep .md-tag-palette-1 { background: rgba(129, 230, 217, 0.35); color: #0f766e; } :host ::ng-deep .md-tag-color-1 { background: rgba(129, 230, 217, 0.35); color: #0f766e; }
:host ::ng-deep .md-tag-palette-2 { background: rgba(196, 181, 253, 0.35); color: #6b21a8; } :host ::ng-deep .md-tag-color-2 { background: rgba(196, 181, 253, 0.35); color: #6b21a8; }
:host ::ng-deep .md-tag-palette-3 { background: rgba(248, 196, 113, 0.35); color: #b45309; } :host ::ng-deep .md-tag-color-3 { background: rgba(248, 196, 113, 0.35); color: #b45309; }
:host ::ng-deep .md-tag-palette-4 { background: rgba(248, 113, 113, 0.35); color: #b91c1c; } :host ::ng-deep .md-tag-color-4 { background: rgba(248, 113, 113, 0.35); color: #b91c1c; }
:host ::ng-deep .md-tag-palette-5 { background: rgba(129, 140, 248, 0.35); color: #4338ca; } :host ::ng-deep .md-tag-color-5 { background: rgba(129, 140, 248, 0.35); color: #4338ca; }
:host ::ng-deep .md-tag-palette-6 { background: rgba(233, 213, 255, 0.35); color: #7e22ce; } :host ::ng-deep .md-tag-color-6 { background: rgba(233, 213, 255, 0.35); color: #7e22ce; }
:host ::ng-deep .md-tag-palette-7 { background: rgba(209, 250, 229, 0.35); color: #047857; } :host ::ng-deep .md-tag-color-7 { background: rgba(209, 250, 229, 0.35); color: #047857; }
:host ::ng-deep .md-tag-palette-8 { background: rgba(165, 180, 252, 0.35); color: #1d4ed8; } :host ::ng-deep .md-tag-color-8 { background: rgba(165, 180, 252, 0.35); color: #1d4ed8; }
:host ::ng-deep .md-tag-palette-9 { background: rgba(253, 224, 71, 0.35); color: #92400e; } :host ::ng-deep .md-tag-color-9 { background: rgba(253, 224, 71, 0.35); color: #92400e; }
:host ::ng-deep .md-tag-palette-10 { background: rgba(244, 114, 182, 0.35); color: #be185d; } :host ::ng-deep .md-tag-color-10 { background: rgba(244, 114, 182, 0.35); color: #be185d; }
:host ::ng-deep .md-tag-palette-11 { background: rgba(148, 163, 184, 0.35); color: #1f2937; } :host ::ng-deep .md-tag-color-11 { background: rgba(148, 163, 184, 0.35); color: #1f2937; }
:host-context(.dark) ::ng-deep .md-tag-palette-0 { background: rgba(132, 204, 22, 0.35); color: #d9f99d; } :host-context(.dark) ::ng-deep .md-tag-color-0 { background: rgba(132, 204, 22, 0.35); color: #d9f99d; }
:host-context(.dark) ::ng-deep .md-tag-palette-1 { background: rgba(45, 212, 191, 0.3); color: #d1fae5; } :host-context(.dark) ::ng-deep .md-tag-color-1 { background: rgba(45, 212, 191, 0.3); color: #d1fae5; }
:host-context(.dark) ::ng-deep .md-tag-palette-2 { background: rgba(168, 85, 247, 0.28); color: #ede9fe; } :host-context(.dark) ::ng-deep .md-tag-color-2 { background: rgba(168, 85, 247, 0.28); color: #ede9fe; }
:host-context(.dark) ::ng-deep .md-tag-palette-3 { background: rgba(245, 158, 11, 0.28); color: #fde68a; } :host-context(.dark) ::ng-deep .md-tag-color-3 { background: rgba(245, 158, 11, 0.28); color: #fde68a; }
:host-context(.dark) ::ng-deep .md-tag-palette-4 { background: rgba(248, 113, 113, 0.3); color: #fee2e2; } :host-context(.dark) ::ng-deep .md-tag-color-4 { background: rgba(248, 113, 113, 0.3); color: #fee2e2; }
:host-context(.dark) ::ng-deep .md-tag-palette-5 { background: rgba(99, 102, 241, 0.3); color: #e0e7ff; } :host-context(.dark) ::ng-deep .md-tag-color-5 { background: rgba(99, 102, 241, 0.3); color: #e0e7ff; }
:host-context(.dark) ::ng-deep .md-tag-palette-6 { background: rgba(217, 70, 239, 0.28); color: #f5d0fe; } :host-context(.dark) ::ng-deep .md-tag-color-6 { background: rgba(217, 70, 239, 0.28); color: #f5d0fe; }
:host-context(.dark) ::ng-deep .md-tag-palette-7 { background: rgba(34, 197, 94, 0.3); color: #bbf7d0; } :host-context(.dark) ::ng-deep .md-tag-color-7 { background: rgba(34, 197, 94, 0.3); color: #bbf7d0; }
:host-context(.dark) ::ng-deep .md-tag-palette-8 { background: rgba(59, 130, 246, 0.28); color: #bfdbfe; } :host-context(.dark) ::ng-deep .md-tag-color-8 { background: rgba(59, 130, 246, 0.28); color: #bfdbfe; }
:host-context(.dark) ::ng-deep .md-tag-palette-9 { background: rgba(250, 204, 21, 0.28); color: #fef08a; } :host-context(.dark) ::ng-deep .md-tag-color-9 { background: rgba(250, 204, 21, 0.28); color: #fef08a; }
:host-context(.dark) ::ng-deep .md-tag-palette-10 { background: rgba(236, 72, 153, 0.3); color: #fbcfe8; } :host-context(.dark) ::ng-deep .md-tag-color-10 { background: rgba(236, 72, 153, 0.3); color: #fbcfe8; }
:host-context(.dark) ::ng-deep .md-tag-palette-11 { background: rgba(107, 114, 128, 0.3); color: #cbd5f5; } :host-context(.dark) ::ng-deep .md-tag-color-11 { background: rgba(107, 114, 128, 0.3); color: #cbd5f5; }
:host ::ng-deep .metadata-panel__item .md-tag-badge { :host ::ng-deep .metadata-panel__item .md-tag-badge {
cursor: default; cursor: default;

View File

@ -45,5 +45,6 @@
[noteId]="noteId" [noteId]="noteId"
[tags]="tags" [tags]="tags"
(saved)="tagsChange.emit($event)" (saved)="tagsChange.emit($event)"
(tagSelected)="tagSelected.emit($event)"
></app-tag-manager> ></app-tag-manager>
</header> </header>

View File

@ -24,6 +24,7 @@ export class NoteHeaderComponent implements AfterViewInit, OnDestroy {
@Output() openDirectory = new EventEmitter<void>(); @Output() openDirectory = new EventEmitter<void>();
@Output() copyRequested = new EventEmitter<void>(); @Output() copyRequested = new EventEmitter<void>();
@Output() tagsChange = new EventEmitter<string[]>(); @Output() tagsChange = new EventEmitter<string[]>();
@Output() tagSelected = new EventEmitter<string>();
pathParts: { prefix: string; filename: string } = { prefix: '', filename: '' }; pathParts: { prefix: string; filename: string } = { prefix: '', filename: '' };

View File

@ -92,30 +92,21 @@
.mode-button { .mode-button {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.45rem;
padding: 0.625rem 1rem;
background: var(--bg-muted);
border: 1.5px solid var(--border);
border-radius: 0.5rem;
color: var(--text-main);
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all var(--transition-base);
min-width: 6.5rem;
justify-content: center; justify-content: center;
min-width: 6.5rem;
cursor: pointer;
border: none;
background: transparent;
color: inherit;
padding: 0;
font-size: 0.85rem;
font-weight: 500;
text-decoration: none;
} }
.mode-button:hover { .mode-button:focus-visible {
background: var(--bg-main); outline: none;
border-color: var(--primary);
transform: translateY(-1px);
}
.mode-button.active {
background: var(--primary);
border-color: var(--primary);
color: white;
} }
.button-icon { .button-icon {
@ -126,6 +117,66 @@
flex-shrink: 0; flex-shrink: 0;
} }
/* Colored shadows small buttons (shared look with tag manager) */
.btn-colored-xs {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.35rem;
padding: 0.35rem 0.6rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.02em;
color: #0f172a;
background: linear-gradient(135deg,
color-mix(in oklab, var(--brand) 18%, transparent) 0%,
color-mix(in oklab, var(--brand) 8%, transparent) 100%);
box-shadow: 0 6px 14px -10px color-mix(in oklab, var(--brand) 65%, transparent);
border: 1px solid color-mix(in oklab, var(--brand) 22%, transparent);
transition: transform 0.15s ease, box-shadow 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}
.btn-colored-xs:hover,
.btn-colored-xs:focus-visible {
transform: translateY(-1px);
box-shadow: 0 10px 18px -10px color-mix(in oklab, var(--brand) 75%, transparent);
border-color: color-mix(in oklab, var(--brand) 35%, transparent);
}
:host-context(.dark) .btn-colored-xs {
color: #e2e8f0;
background: linear-gradient(135deg,
color-mix(in oklab, var(--brand-700) 22%, transparent) 0%,
color-mix(in oklab, var(--brand-700) 12%, transparent) 100%);
box-shadow: 0 10px 22px -14px color-mix(in oklab, var(--brand-700) 85%, transparent);
border-color: color-mix(in oklab, var(--brand-700) 30%, transparent);
}
.btn-primary {
color: white;
background: linear-gradient(135deg,
color-mix(in oklab, var(--brand) 60%, transparent) 0%,
color-mix(in oklab, var(--brand-700) 60%, transparent) 100%);
border-color: color-mix(in oklab, var(--brand-700) 50%, transparent);
}
.btn-neutral {
color: inherit;
background: linear-gradient(135deg,
color-mix(in oklab, var(--surface-1, #e5e7eb) 60%, transparent) 0%,
color-mix(in oklab, var(--surface-2, #d1d5db) 60%, transparent) 100%);
border-color: color-mix(in oklab, var(--border, #cbd5e1) 60%, transparent);
}
:host-context(.dark) .btn-neutral {
color: #e2e8f0;
background: linear-gradient(135deg,
color-mix(in oklab, var(--card, #111827) 60%, transparent) 0%,
color-mix(in oklab, var(--surface2, #0b1220) 60%, transparent) 100%);
border-color: color-mix(in oklab, var(--border, #334155) 60%, transparent);
}
/* Theme Grid */ /* Theme Grid */
.theme-grid { .theme-grid {
display: grid; display: grid;

View File

@ -31,8 +31,9 @@
<button <button
*ngFor="let m of modes" *ngFor="let m of modes"
(click)="setMode(m)" (click)="setMode(m)"
class="mode-button" class="mode-button btn-colored-xs"
[class.active]="prefs().mode === m" [class.btn-primary]="prefs().mode === m"
[class.btn-neutral]="prefs().mode !== m"
[attr.aria-label]="'Set mode to ' + m"> [attr.aria-label]="'Set mode to ' + m">
<svg *ngIf="m === 'system'" class="button-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <svg *ngIf="m === 'system'" class="button-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/> <rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
@ -100,8 +101,9 @@
<button <button
*ngFor="let l of languages" *ngFor="let l of languages"
(click)="setLanguage(l)" (click)="setLanguage(l)"
class="mode-button" class="mode-button btn-colored-xs"
[class.active]="prefs().language === l" [class.btn-primary]="prefs().language === l"
[class.btn-neutral]="prefs().language !== l"
[attr.aria-label]="'Set language to ' + l"> [attr.aria-label]="'Set language to ' + l">
<span class="button-text">{{ l === 'fr' ? 'Français' : 'English' }}</span> <span class="button-text">{{ l === 'fr' ? 'Français' : 'English' }}</span>
</button> </button>

View File

@ -24,7 +24,7 @@ import { GraphSettingsAccordionComponent } from '../../../components/graph-setti
<!-- Expand all --> <!-- Expand all -->
<button <button
type="button" type="button"
class="btn-standard-icon" class="btn-colored-xs btn-neutral"
title="Expand all" title="Expand all"
(click)="accordion?.expandAll()"> (click)="accordion?.expandAll()">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -35,7 +35,7 @@ import { GraphSettingsAccordionComponent } from '../../../components/graph-setti
<!-- Collapse all --> <!-- Collapse all -->
<button <button
type="button" type="button"
class="btn-standard-icon" class="btn-colored-xs btn-neutral"
title="Collapse all" title="Collapse all"
(click)="accordion?.collapseAll()"> (click)="accordion?.collapseAll()">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -46,7 +46,7 @@ import { GraphSettingsAccordionComponent } from '../../../components/graph-setti
<button <button
type="button" type="button"
(click)="onResetAll()" (click)="onResetAll()"
class="btn-standard-icon" class="btn-colored-xs btn-primary"
title="Reset all settings"> title="Reset all settings">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
@ -57,7 +57,7 @@ import { GraphSettingsAccordionComponent } from '../../../components/graph-setti
type="button" type="button"
(click)="close.emit()" (click)="close.emit()"
(keydown.escape)="close.emit()" (keydown.escape)="close.emit()"
class="btn-standard-icon" class="btn-colored-xs btn-neutral"
aria-label="Close settings" aria-label="Close settings"
title="Close"> title="Close">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -133,6 +133,61 @@ import { GraphSettingsAccordionComponent } from '../../../components/graph-setti
padding: 1rem; padding: 1rem;
} }
/* Colored shadows small buttons (shared look with tag manager) */
.btn-colored-xs {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.35rem;
padding: 0.35rem 0.6rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.02em;
color: #0f172a;
background: linear-gradient(135deg,
color-mix(in oklab, var(--brand) 18%, transparent) 0%,
color-mix(in oklab, var(--brand) 8%, transparent) 100%);
box-shadow: 0 6px 14px -10px color-mix(in oklab, var(--brand) 65%, transparent);
border: 1px solid color-mix(in oklab, var(--brand) 22%, transparent);
transition: transform 0.15s ease, box-shadow 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}
.btn-colored-xs:hover,
.btn-colored-xs:focus-visible {
transform: translateY(-1px);
box-shadow: 0 10px 18px -10px color-mix(in oklab, var(--brand) 75%, transparent);
border-color: color-mix(in oklab, var(--brand) 35%, transparent);
}
:host-context(.dark) .btn-colored-xs {
color: #e2e8f0;
background: linear-gradient(135deg,
color-mix(in oklab, var(--brand-700) 22%, transparent) 0%,
color-mix(in oklab, var(--brand-700) 12%, transparent) 100%);
box-shadow: 0 10px 22px -14px color-mix(in oklab, var(--brand-700) 85%, transparent);
border-color: color-mix(in oklab, var(--brand-700) 30%, transparent);
}
.btn-primary {
color: white;
background: linear-gradient(135deg,
color-mix(in oklab, var(--brand) 60%, transparent) 0%,
color-mix(in oklab, var(--brand-700) 60%, transparent) 100%);
border-color: color-mix(in oklab, var(--brand-700) 50%, transparent);
}
.btn-neutral {
color: inherit;
background: linear-gradient(135deg,
color-mix(in oklab, var(--surface-1, #e5e7eb) 60%, transparent) 0%,
color-mix(in oklab, var(--surface-2, #d1d5db) 60%, transparent) 100%);
border-color: color-mix(in oklab, var(--border, #cbd5e1) 60%, transparent);
}
:host-context(.dark) .btn-neutral {
color: #e2e8f0;
background: linear-gradient(135deg,
color-mix(in oklab, var(--card, #111827) 60%, transparent) 0%,
color-mix(in oklab, var(--surface2, #0b1220) 60%, transparent) 100%);
border-color: color-mix(in oklab, var(--border, #334155) 60%, transparent);
}
@media (max-width: 768px) { @media (max-width: 768px) {
.panel-content { .panel-content {
max-width: 100%; max-width: 100%;

View File

@ -1,2 +1,66 @@
/* Overlay styling relies on Tailwind utilities in the template. Keep minimal spacing fixes here if needed. */
:host { display: contents; } :host { display: contents; }
/* Small colored buttons with soft gradient and shadow ("Colored shadows") */
.btn-colored-xs {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.35rem;
padding: 0.35rem 0.6rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.02em;
color: #0f172a;
background: linear-gradient(135deg,
color-mix(in oklab, var(--brand) 18%, transparent) 0%,
color-mix(in oklab, var(--brand) 8%, transparent) 100%);
box-shadow: 0 6px 14px -10px color-mix(in oklab, var(--brand) 65%, transparent);
border: 1px solid color-mix(in oklab, var(--brand) 22%, transparent);
transition: transform 0.15s ease, box-shadow 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}
.btn-colored-xs:hover,
.btn-colored-xs:focus-visible {
transform: translateY(-1px);
box-shadow: 0 10px 18px -10px color-mix(in oklab, var(--brand) 75%, transparent);
border-color: color-mix(in oklab, var(--brand) 35%, transparent);
}
:host-context(.dark) .btn-colored-xs {
color: #e2e8f0;
background: linear-gradient(135deg,
color-mix(in oklab, var(--brand-700) 22%, transparent) 0%,
color-mix(in oklab, var(--brand-700) 12%, transparent) 100%);
box-shadow: 0 10px 22px -14px color-mix(in oklab, var(--brand-700) 85%, transparent);
border-color: color-mix(in oklab, var(--brand-700) 30%, transparent);
}
.btn-colored-active {
outline: 2px solid color-mix(in oklab, var(--brand-700) 45%, transparent);
}
/* Semantic variants for footer actions */
.btn-primary {
color: white;
background: linear-gradient(135deg,
color-mix(in oklab, var(--brand) 60%, transparent) 0%,
color-mix(in oklab, var(--brand-700) 60%, transparent) 100%);
border-color: color-mix(in oklab, var(--brand-700) 50%, transparent);
}
.btn-neutral {
color: inherit;
background: linear-gradient(135deg,
color-mix(in oklab, var(--surface-1, #e5e7eb) 60%, transparent) 0%,
color-mix(in oklab, var(--surface-2, #d1d5db) 60%, transparent) 100%);
border-color: color-mix(in oklab, var(--border, #cbd5e1) 60%, transparent);
}
:host-context(.dark) .btn-neutral {
color: #e2e8f0;
background: linear-gradient(135deg,
color-mix(in oklab, var(--card, #111827) 60%, transparent) 0%,
color-mix(in oklab, var(--surface2, #0b1220) 60%, transparent) 100%);
border-color: color-mix(in oklab, var(--border, #334155) 60%, transparent);
}

View File

@ -1,6 +1,6 @@
<div class="fixed inset-0 z-50 flex items-center justify-center"> <div class="fixed inset-0 z-50 flex items-end md:items-center justify-center">
<div class="absolute inset-0 bg-black/40"></div> <div class="absolute inset-0 bg-black/40"></div>
<div class="relative rounded-2xl shadow-xl border border-border dark:border-border bg-card dark:bg-main p-4 md:p-5 w-[min(780px,92vw)]"> <div class="relative rounded-2xl shadow-xl border border-border dark:border-border bg-card dark:bg-main p-4 md:p-5 w-[min(880px,96vw)] md:max-h-[85vh] overflow-auto">
<div class="flex items-center justify-between mb-3"> <div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<svg class="h-5 w-5 text-muted" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 7h.01"/><path d="M3 7h5a2 2 0 0 1 1.414.586l7 7a2 2 0 0 1 0 2.828l-3.172 3.172a2 2 0 0 1-2.828 0l-7-7A2 2 0 0 1 3 12V7Z"/></svg> <svg class="h-5 w-5 text-muted" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 7h.01"/><path d="M3 7h5a2 2 0 0 1 1.414.586l7 7a2 2 0 0 1 0 2.828l-3.172 3.172a2 2 0 0 1-2.828 0l-7-7A2 2 0 0 1 3 12V7Z"/></svg>
@ -12,38 +12,55 @@
</button> </button>
</div> </div>
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-4">
<div class="flex flex-wrap gap-2 rounded-xl border border-border dark:border-border p-3 min-h-[44px]"> <!-- Alphabet filter -->
<div class="not-prose">
<div class="flex flex-wrap items-center gap-1 md:gap-1.5">
<button type="button" class="btn-colored-xs" [class.btn-colored-active]="letter()==='all'" (click)="letter.set('all')">Tous</button>
@for (L of letters; track L) {
<button type="button" class="btn-colored-xs" [class.btn-colored-active]="letter()===L" (click)="letter.set(L)">{{ L }}</button>
}
<button type="button" class="btn-colored-xs" [class.btn-colored-active]="letter()==='#'" (click)="letter.set('#')">#</button>
<button type="button" class="btn-colored-xs" [class.btn-colored-active]="letter()==='other'" (click)="letter.set('other')">Autre</button>
</div>
</div>
<!-- Selected tags + input -->
<div class="flex flex-wrap items-center gap-2 rounded-xl border border-border dark:border-border p-3 min-h-[48px]">
@for (t of working(); track t) { @for (t of working(); track t) {
<span class="inline-flex items-center gap-2 rounded-full px-3 py-1.5 text-sm font-medium bg-surface1 dark:bg-card"> <span class="md-tag-badge" [ngClass]="tagColorClass(t)" [attr.data-tag]="t">
{{ t }} {{ t }}
<button type="button" class="w-6 h-6 inline-flex items-center justify-center rounded-full hover:bg-surface1 dark:hover:bg-surface2" (click)="removeTag(t)" aria-label="Retirer"> <button type="button" class="ml-1 inline-flex items-center justify-center w-5 h-5 rounded-full border border-transparent hover:border-border/60" (click)="removeTag(t)" aria-label="Retirer">
<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>
</span> </span>
} }
<input data-tag-input type="text" [value]="inputValue()" (input)="inputValue.set($any($event.target).value)" (keydown)="onKeydown($event)" placeholder="Ajouter un tag..." class="min-w-[200px] flex-1 bg-transparent outline-none px-2 py-1.5" /> <input data-tag-input
type="text"
[value]="inputValue()"
(input)="inputValue.set($any($event.target).value)"
(keydown)="onKeydown($event)"
placeholder="Ajouter un tag..."
class="min-w-[200px] flex-1 w-full md:w-auto rounded-full border border-border bg-bg-muted/70 py-2.5 px-4 text-sm text-text-main placeholder:text-text-muted shadow-subtle focus:outline-none focus:ring-2 focus:ring-ring transition-all dark:border-border dark:bg-card/80 dark:text-gray-100 dark:placeholder-gray-400 dark:focus:ring-ring" />
</div> </div>
<div class="rounded-xl border border-border dark:border-border overflow-hidden"> <!-- Suggestions -->
<div class="max-h-64 overflow-y-auto"> <div class="rounded-xl border border-border dark:border-border p-3">
@if (suggestions().length === 0) { @if (suggestions().length === 0) {
<div class="px-3 py-2 text-sm text-muted">Aucune suggestion</div> <div class="px-1 py-0.5 text-sm text-muted">Aucune suggestion</div>
} @else { } @else {
<ul> <div class="flex flex-wrap gap-2">
@for (s of suggestions(); track s) { @for (s of suggestions(); track s) {
<li> <button type="button" class="md-tag-badge btn-colored-xs" [ngClass]="tagColorClass(s)" (click)="pickSuggestion(s)" [attr.data-tag]="s" [title]="'Ajouter #'+s">{{ s }}</button>
<button type="button" class="w-full text-left px-3 py-2 hover:bg-surface1 dark:hover:bg-card" (click)="pickSuggestion(s)">{{ s }}</button>
</li>
}
</ul>
} }
</div> </div>
}
</div> </div>
<!-- Actions -->
<div class="flex items-center justify-end gap-2 pt-1"> <div class="flex items-center justify-end gap-2 pt-1">
<button type="button" class="px-3 py-1.5 rounded-lg border border-border dark:border-border hover:bg-surface1 dark:hover:bg-card" (click)="close.emit()">Annuler</button> <button type="button" class="btn-colored-xs btn-neutral" (click)="close.emit()">Annuler</button>
<button type="button" class="px-3 py-1.5 rounded-lg bg-primary hover:bg-brand-700 text-white disabled:opacity-60" [disabled]="saving()" (click)="save()">Enregistrer</button> <button type="button" class="btn-colored-xs btn-primary" [disabled]="saving()" (click)="save()">Enregistrer</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -28,10 +28,28 @@ export class TagEditorOverlayComponent {
working = signal<string[]>([]); working = signal<string[]>([]);
count = computed(() => this.working().length); count = computed(() => this.working().length);
private readonly tagPaletteSize = 12;
private readonly tagColorCache = new Map<string, number>();
// Alphabet filter state: 'all' | 'other' | '#', or 'a'..'z'
letter = signal<string>('all');
readonly letters = Array.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ');
ngOnInit() { ngOnInit() {
this.working.set(uniqueTags(this.value || [])); this.working.set(uniqueTags(this.value || []));
} }
tagColorClass(tag: string): string {
if (!tag) return '';
const normalized = `${tag}`.toLowerCase();
if (this.tagColorCache.has(normalized)) return `md-tag-color-${this.tagColorCache.get(normalized)}`;
let hash = 0;
for (let i = 0; i < normalized.length; i++) hash = (hash * 31 + normalized.charCodeAt(i)) >>> 0;
const colorIndex = hash % this.tagPaletteSize;
this.tagColorCache.set(normalized, colorIndex);
return `md-tag-color-${colorIndex}`;
}
private allKnownTags = computed(() => { private allKnownTags = computed(() => {
const provided = this.allTags || []; const provided = this.allTags || [];
if (provided.length > 0) return provided; if (provided.length > 0) return provided;
@ -44,10 +62,21 @@ export class TagEditorOverlayComponent {
suggestions = computed(() => { suggestions = computed(() => {
const q = this.inputValue().trim().toLowerCase(); const q = this.inputValue().trim().toLowerCase();
const selected = this.letter();
const source = this.allKnownTags(); const source = this.allKnownTags();
const pool = q ? source.filter(t => t.toLowerCase().includes(q)) : source;
const matchesLetter = (t: string) => {
if (selected === 'all') return true;
const first = (t?.[0] || '').toUpperCase();
if (selected === '#') return /[0-9]/.test(first);
if (selected === 'other') return !/[A-Z0-9]/.test(first);
return first === selected.toUpperCase();
};
const byLetter = source.filter(matchesLetter);
const byQuery = q ? byLetter.filter(t => t.toLowerCase().includes(q)) : byLetter;
const exist = new Set(this.working().map(t => t.toLowerCase())); const exist = new Set(this.working().map(t => t.toLowerCase()));
return pool.filter(t => !exist.has(t.toLowerCase())).slice(0, 50); return byQuery.filter(t => !exist.has(t.toLowerCase())).slice(0, 100);
}); });
onKeydown(ev: KeyboardEvent) { onKeydown(ev: KeyboardEvent) {

View File

@ -12,9 +12,11 @@
<div class="flex flex-wrap items-center gap-1"> <div class="flex flex-wrap items-center gap-1">
<button <button
type="button" type="button"
class="px-3 py-1.5 rounded-full bg-slate-700/10 dark:bg-muted/10 text-sm font-medium hover:bg-slate-500/10 dark:hover:bg-surface2/10" class="md-tag-badge"
*ngFor="let tag of normalizedTags()" *ngFor="let tag of normalizedTags()"
[ngClass]="tagColorClass(tag)"
(click)="onChipClick(tag)" (click)="onChipClick(tag)"
[attr.data-tag]="tag"
[title]="'Voir les notes #'+tag"> [title]="'Voir les notes #'+tag">
{{ tag }} {{ tag }}
</button> </button>

View File

@ -32,6 +32,20 @@ export class TagManagerComponent {
isEditing = signal(false); isEditing = signal(false);
readonly normalizedTags = computed(() => uniqueTags(this.tagsSignal())); readonly normalizedTags = computed(() => uniqueTags(this.tagsSignal()));
private readonly tagPaletteSize = 12;
private readonly tagColorCache = new Map<string, number>();
tagColorClass(tag: string): string {
if (!tag) return '';
const normalized = `${tag}`.toLowerCase();
if (this.tagColorCache.has(normalized)) return `md-tag-color-${this.tagColorCache.get(normalized)}`;
let hash = 0;
for (let i = 0; i < normalized.length; i++) hash = (hash * 31 + normalized.charCodeAt(i)) >>> 0;
const colorIndex = hash % this.tagPaletteSize;
this.tagColorCache.set(normalized, colorIndex);
return `md-tag-color-${colorIndex}`;
}
toggleEditor(): void { toggleEditor(): void {
const next = !this.isEditing(); const next = !this.isEditing();
this.isEditing.set(next); this.isEditing.set(next);

View File

@ -59,6 +59,7 @@ export interface WikiLinkActivation {
(copyRequested)="copyPath()" (copyRequested)="copyPath()"
(openDirectory)="directoryClicked.emit(getDirectoryFromPath(note().filePath))" (openDirectory)="directoryClicked.emit(getDirectoryFromPath(note().filePath))"
(tagsChange)="onTagsChange($event)" (tagsChange)="onTagsChange($event)"
(tagSelected)="tagClicked.emit($event)"
></app-note-header> ></app-note-header>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
@ -116,15 +117,15 @@ export interface WikiLinkActivation {
</button> </button>
@if (menuOpen()) { @if (menuOpen()) {
<div class="absolute right-0 mt-2 w-56 rounded-md border border-border bg-card shadow-subtle not-prose z-10"> <div class="absolute right-0 mt-2 w-56 rounded-xl border border-border bg-card shadow-subtle not-prose z-10 overflow-hidden">
<button type="button" class="block w-full text-left px-3 py-2 hover:bg-muted" (click)="parametersRequested.emit(); closeMenu()"> <button type="button" class="block w-full text-left px-3 py-2 text-sm transition-colors hover:bg-[rgba(99,102,241,0.12)] dark:hover:bg-[rgba(99,102,241,0.28)] focus-visible:bg-[rgba(99,102,241,0.16)] dark:focus-visible:bg-[rgba(99,102,241,0.35)]" (click)="parametersRequested.emit(); closeMenu()">
<svg xmlns="http://www.w3.org/2000/svg" class="inline h-4 w-4 mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg xmlns="http://www.w3.org/2000/svg" class="inline h-4 w-4 mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"/> <circle cx="12" cy="12" r="3"/>
<path d="M12 1v6m0 6v6m-6-6h6m6 0h-6M4.93 4.93l4.24 4.24m5.66 5.66l4.24 4.24m-4.24-14.14l4.24-4.24M4.93 19.07l4.24-4.24"/> <path d="M12 1v6m0 6v6m-6-6h6m6 0h-6M4.93 4.93l4.24 4.24m5.66 5.66l4.24 4.24m-4.24-14.14l4.24-4.24M4.93 19.07l4.24-4.24"/>
</svg> </svg>
Parameters Parameters
</button> </button>
<button type="button" class="block w-full text-left px-3 py-2 hover:bg-muted" (click)="legacyRequested.emit(); closeMenu()">🔧 Legacy</button> <button type="button" class="block w-full text-left px-3 py-2 text-sm transition-colors hover:bg-[rgba(99,102,241,0.12)] dark:hover:bg-[rgba(99,102,241,0.28)] focus-visible:bg-[rgba(99,102,241,0.16)] dark:focus-visible:bg-[rgba(99,102,241,0.35)]" (click)="legacyRequested.emit(); closeMenu()">🔧 Legacy</button>
</div> </div>
} }
</div> </div>

View File

@ -126,7 +126,9 @@ export class MarkdownService {
if (this.canUseFastPath(markdown)) { if (this.canUseFastPath(markdown)) {
const env: MarkdownRenderEnv = { codeBlockIndex: 0 }; const env: MarkdownRenderEnv = { codeBlockIndex: 0 };
this.activeSlugState = new Map<string, number>(); this.activeSlugState = new Map<string, number>();
return this.md.render(markdown, env); // Even on fast path, decorate inline #tags so they render as clickable badges
const decoratedFast = this.decorateInlineTags(markdown);
return this.md.render(decoratedFast, env);
} }
const env: MarkdownRenderEnv = { codeBlockIndex: 0 }; const env: MarkdownRenderEnv = { codeBlockIndex: 0 };

View File

@ -4,7 +4,10 @@ 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: [] tags:
- configuration
- test
- tag3
aliases: [] aliases: []
status: en-cours status: en-cours
publish: false publish: false
@ -17,4 +20,7 @@ private: false
--- ---
# Archived Note # Archived Note
#bruno
This note was archived and moved to trash. This note was archived and moved to trash.

View File

@ -4,7 +4,10 @@ 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: [] tags:
- configuration
- test
- tag3
aliases: [] aliases: []
status: en-cours status: en-cours
publish: false publish: false