refactor: redesign editor buttons and improve scrollbar theming
This commit is contained in:
parent
0d0607577d
commit
eeb957cf17
@ -14,20 +14,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:host-context(.dark) ::ng-deep .note-content-area::-webkit-scrollbar-thumb {
|
/* Theme-aware subtle scrollbar for note area */
|
||||||
background: rgba(148, 163, 184, 0.4);
|
:host ::ng-deep .note-content-area {
|
||||||
|
scrollbar-width: thin; /* Firefox */
|
||||||
|
scrollbar-color: color-mix(in oklab, var(--scrollbar-thumb, rgba(148,163,184,0.45)) 80%, transparent) transparent; /* Firefox */
|
||||||
}
|
}
|
||||||
|
|
||||||
:host-context(.dark) ::ng-deep .note-content-area::-webkit-scrollbar-thumb:hover {
|
:host ::ng-deep .note-content-area::-webkit-scrollbar-thumb {
|
||||||
background: rgba(148, 163, 184, 0.65);
|
background: color-mix(in oklab, var(--scrollbar-thumb, rgba(148,163,184,0.45)) 80%, transparent);
|
||||||
|
border-radius: 9999px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:not(.dark) ::ng-deep .note-content-area::-webkit-scrollbar-thumb {
|
:host ::ng-deep .note-content-area::-webkit-scrollbar-thumb:hover {
|
||||||
background: rgba(100, 116, 139, 0.28);
|
background: color-mix(in oklab, var(--scrollbar-thumb, rgba(148,163,184,0.45)) 95%, transparent);
|
||||||
}
|
|
||||||
|
|
||||||
:not(.dark) ::ng-deep .note-content-area::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: rgba(100, 116, 139, 0.45);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:host ::ng-deep .md-tag-group {
|
:host ::ng-deep .md-tag-group {
|
||||||
@ -166,30 +165,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Custom scrollbar for webkit browsers */
|
/* Global scrollbar rules moved to styles.css for true global scope */
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
}
|
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
.dark ::-webkit-scrollbar-thumb {
|
|
||||||
background: #3c3d3f;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.dark ::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #5c6166;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Light mode scrollbar */
|
|
||||||
:not(.dark) ::-webkit-scrollbar-thumb {
|
|
||||||
background: #d8dbe0;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
:not(.dark) ::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #b8bcc2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resize-handle {
|
.resize-handle {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
|
|||||||
@ -555,7 +555,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
const isDesktop = this.isDesktopView();
|
const isDesktop = this.isDesktopView();
|
||||||
if (isDesktop && !this.wasDesktop) {
|
if (isDesktop && !this.wasDesktop) {
|
||||||
this.isSidebarOpen.set(true);
|
this.isSidebarOpen.set(true);
|
||||||
this.isOutlineOpen.set(true);
|
// Keep existing outline state to avoid forcing it open
|
||||||
}
|
}
|
||||||
if (!isDesktop && this.wasDesktop) {
|
if (!isDesktop && this.wasDesktop) {
|
||||||
this.isSidebarOpen.set(false);
|
this.isSidebarOpen.set(false);
|
||||||
|
|||||||
@ -54,42 +54,42 @@ import { EditorHighlightService } from '../../shared/editor/editor-highlight.ser
|
|||||||
<!-- Save Button -->
|
<!-- Save Button -->
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-solid btn-sm"
|
class="editor-btn editor-btn--primary"
|
||||||
[disabled]="isSaving()"
|
[disabled]="isSaving()"
|
||||||
(click)="save()"
|
(click)="save()"
|
||||||
[attr.aria-label]="'Save (Ctrl+S)'">
|
[attr.aria-label]="'Save (Ctrl+S)'">
|
||||||
<svg *ngIf="!isSaving()" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg *ngIf="!isSaving()" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="editor-btn__icon">
|
||||||
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
|
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
|
||||||
<polyline points="17 21 17 13 7 13 7 21"/>
|
<polyline points="17 21 17 13 7 13 7 21"/>
|
||||||
<polyline points="7 3 7 8 15 8"/>
|
<polyline points="7 3 7 8 15 8"/>
|
||||||
</svg>
|
</svg>
|
||||||
<svg *ngIf="isSaving()" class="animate-spin" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg *ngIf="isSaving()" class="animate-spin editor-btn__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<circle cx="12" cy="12" r="10" stroke-dasharray="60" stroke-dashoffset="15"/>
|
<circle cx="12" cy="12" r="10" stroke-dasharray="60" stroke-dashoffset="15"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="ml-1 hidden sm:inline">{{ isSaving() ? 'Saving...' : 'Save' }}</span>
|
<span class="editor-btn__label hidden sm:inline">{{ isSaving() ? 'Saving...' : 'Save' }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Wrap Toggle -->
|
<!-- Wrap Toggle -->
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-outline btn-sm"
|
class="editor-btn editor-btn--neutral"
|
||||||
(click)="toggleWordWrap()"
|
(click)="toggleWordWrap()"
|
||||||
[attr.aria-label]="'Toggle word wrap'">
|
[attr.aria-label]="'Toggle word wrap'">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg class="editor-btn__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<polyline points="4 7 4 4 20 4 20 7"/>
|
<polyline points="4 7 4 4 20 4 20 7"/>
|
||||||
<line x1="9" y1="20" x2="15" y2="20"/>
|
<line x1="9" y1="20" x2="15" y2="20"/>
|
||||||
<line x1="12" y1="4" x2="12" y2="20"/>
|
<line x1="12" y1="4" x2="12" y2="20"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="ml-1 hidden md:inline">Wrap</span>
|
<span class="editor-btn__label hidden md:inline">Wrap</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Undo -->
|
<!-- Undo -->
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-ghost btn-sm hidden sm:flex"
|
class="editor-btn editor-btn--ghost editor-btn--icon hidden sm:inline-flex"
|
||||||
(click)="undo()"
|
(click)="undo()"
|
||||||
[attr.aria-label]="'Undo (Ctrl+Z)'">
|
[attr.aria-label]="'Undo (Ctrl+Z)'">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg class="editor-btn__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<polyline points="1 4 1 10 7 10"/>
|
<polyline points="1 4 1 10 7 10"/>
|
||||||
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
|
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
|
||||||
</svg>
|
</svg>
|
||||||
@ -98,10 +98,10 @@ import { EditorHighlightService } from '../../shared/editor/editor-highlight.ser
|
|||||||
<!-- Redo -->
|
<!-- Redo -->
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-ghost btn-sm hidden sm:flex"
|
class="editor-btn editor-btn--ghost editor-btn--icon hidden sm:inline-flex"
|
||||||
(click)="redo()"
|
(click)="redo()"
|
||||||
[attr.aria-label]="'Redo (Ctrl+Y)'">
|
[attr.aria-label]="'Redo (Ctrl+Y)'">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg class="editor-btn__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<polyline points="23 4 23 10 17 10"/>
|
<polyline points="23 4 23 10 17 10"/>
|
||||||
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/>
|
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/>
|
||||||
</svg>
|
</svg>
|
||||||
@ -110,14 +110,14 @@ import { EditorHighlightService } from '../../shared/editor/editor-highlight.ser
|
|||||||
<!-- Close/Cancel -->
|
<!-- Close/Cancel -->
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-outline btn-sm"
|
class="editor-btn editor-btn--neutral"
|
||||||
(click)="close()"
|
(click)="close()"
|
||||||
[attr.aria-label]="'Close editor (Esc)'">
|
[attr.aria-label]="'Close editor (Esc)'">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg class="editor-btn__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<line x1="18" y1="6" x2="6" y2="18"/>
|
<line x1="18" y1="6" x2="6" y2="18"/>
|
||||||
<line x1="6" y1="6" x2="18" y2="18"/>
|
<line x1="6" y1="6" x2="18" y2="18"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="ml-1 hidden sm:inline">Close</span>
|
<span class="editor-btn__label hidden sm:inline">Close</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -171,7 +171,108 @@ import { EditorHighlightService } from '../../shared/editor/editor-highlight.ser
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editor-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.45rem;
|
||||||
|
padding: 0.45rem 0.85rem;
|
||||||
|
border-radius: 9999px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease, border-color 0.18s ease, color 0.18s ease;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
box-shadow: 0 6px 14px -12px color-mix(in oklab, var(--brand) 70%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-btn:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 3px color-mix(in oklab, var(--brand) 35%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-btn:disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: not-allowed;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-btn__icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-btn__label {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-btn--icon {
|
||||||
|
padding: 0.4rem;
|
||||||
|
border-radius: 9999px;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-btn--primary {
|
||||||
|
color: #0f172a;
|
||||||
|
background: linear-gradient(135deg,
|
||||||
|
color-mix(in oklab, var(--brand) 55%, transparent) 0%,
|
||||||
|
color-mix(in oklab, var(--brand-700) 45%, transparent) 100%);
|
||||||
|
border-color: color-mix(in oklab, var(--brand-700) 40%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-btn--primary:hover:not(:disabled) {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 12px 22px -12px color-mix(in oklab, var(--brand) 80%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-btn--neutral {
|
||||||
|
color: inherit;
|
||||||
|
background: linear-gradient(135deg,
|
||||||
|
color-mix(in oklab, var(--surface-1, #e5e7eb) 65%, transparent) 0%,
|
||||||
|
color-mix(in oklab, var(--surface-2, #d1d5db) 55%, transparent) 100%);
|
||||||
|
border-color: color-mix(in oklab, var(--border, #cbd5e1) 55%, transparent);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-btn--neutral:hover:not(:disabled) {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 8px 18px -14px color-mix(in oklab, var(--border, #cbd5e1) 65%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-btn--ghost {
|
||||||
|
color: var(--brand-500, var(--btn-bg));
|
||||||
|
background: transparent;
|
||||||
|
border-color: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-btn--ghost:hover:not(:disabled) {
|
||||||
|
background: color-mix(in srgb, var(--brand) 10%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context(.dark) .editor-btn--primary {
|
||||||
|
color: #e2e8f0;
|
||||||
|
background: linear-gradient(135deg,
|
||||||
|
color-mix(in oklab, var(--brand-700) 55%, transparent) 0%,
|
||||||
|
color-mix(in oklab, var(--brand-900, #1e1b4b) 55%, transparent) 100%);
|
||||||
|
border-color: color-mix(in oklab, var(--brand-700) 45%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context(.dark) .editor-btn--neutral {
|
||||||
|
color: #e2e8f0;
|
||||||
|
background: linear-gradient(135deg,
|
||||||
|
color-mix(in oklab, var(--card, #111827) 65%, transparent) 0%,
|
||||||
|
color-mix(in oklab, var(--surface2, #0b1220) 55%, transparent) 100%);
|
||||||
|
border-color: color-mix(in oklab, var(--border, #334155) 60%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context(.dark) .editor-btn--ghost {
|
||||||
|
color: color-mix(in oklab, var(--brand-200, #c7d2fe) 75%, #93c5fd 25%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-btn--ghost.editor-btn--icon:hover:not(:disabled) {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
.markdown-editor__container {
|
.markdown-editor__container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|||||||
@ -30,8 +30,20 @@ import { ScrollableOverlayDirective } from '../../shared/overlay-scrollbar/scrol
|
|||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
(click)="$event.stopPropagation()">
|
(click)="$event.stopPropagation()">
|
||||||
|
|
||||||
<div class="flex items-center justify-center py-3 sm:py-2">
|
<div class="flex items-center justify-between px-4 pt-3 pb-2 sm:pt-3 sm:pb-2">
|
||||||
<div class="h-1.5 w-12 rounded-full bg-muted dark:bg-surface2"></div>
|
<div class="flex-1 flex justify-center">
|
||||||
|
<div class="h-1.5 w-12 rounded-full bg-muted dark:bg-surface2"></div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="ml-3 inline-flex items-center justify-center w-9 h-9 rounded-full bg-transparent hover:bg-surface1 dark:hover:bg-card transition"
|
||||||
|
aria-label="Fermer le sommaire"
|
||||||
|
(click)="close.emit()">
|
||||||
|
<svg class="w-4 h-4" 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>
|
||||||
|
|
||||||
<div class="px-4 pb-4 overflow-y-auto max-h-[75vh] sm:max-h-[70vh]" appScrollableOverlay>
|
<div class="px-4 pb-4 overflow-y-auto max-h-[75vh] sm:max-h-[70vh]" appScrollableOverlay>
|
||||||
|
|||||||
@ -168,6 +168,7 @@ export class NoteHeaderComponent implements AfterViewInit, OnDestroy {
|
|||||||
const note = this.vaultService.getNoteById(this.noteId);
|
const note = this.vaultService.getNoteById(this.noteId);
|
||||||
const props = this.frontmatterService.get(note);
|
const props = this.frontmatterService.get(note);
|
||||||
compRef.instance.props = props;
|
compRef.instance.props = props;
|
||||||
|
compRef.instance.noteId = this.noteId;
|
||||||
|
|
||||||
compRef.instance.requestClose.subscribe(() => this.scheduleClose());
|
compRef.instance.requestClose.subscribe(() => this.scheduleClose());
|
||||||
compRef.instance.cancelClose.subscribe(() => clearTimeout(this.closeTimer));
|
compRef.instance.cancelClose.subscribe(() => clearTimeout(this.closeTimer));
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
<section
|
<section
|
||||||
id="note-props-popover"
|
id="note-props-popover"
|
||||||
class="max-w-[420px] w-[min(92vw,420px)] p-4 text-sm leading-5 bg-popover text-popover-foreground border border-border rounded-2xl shadow-xl backdrop-blur"
|
class="max-w-[420px] w-[min(92vw,420px)] p-5 text-[0.9375rem] leading-6 bg-card text-popover-foreground border border-border rounded-2xl shadow-2xl"
|
||||||
(mouseenter)="cancelClose.emit()"
|
(mouseenter)="cancelClose.emit()"
|
||||||
(mouseleave)="requestClose.emit()"
|
(mouseleave)="requestClose.emit()"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-label="Propriétés du document">
|
aria-label="Propriétés du document">
|
||||||
<h3 class="font-semibold mb-3">Propriétés du document</h3>
|
<h3 class="font-semibold text-xl mb-3">Propriétés du document</h3>
|
||||||
|
<div class="border-b border-border mb-4"></div>
|
||||||
|
|
||||||
<ng-container *ngIf="props as current; else emptyState">
|
<ng-container *ngIf="props as current; else emptyState">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
@ -13,7 +14,7 @@
|
|||||||
<section class="not-prose">
|
<section class="not-prose">
|
||||||
<dl class="grid grid-cols-[auto_1fr] gap-x-3 gap-y-1.5">
|
<dl class="grid grid-cols-[auto_1fr] gap-x-3 gap-y-1.5">
|
||||||
<ng-container *ngFor="let row of summaryRows">
|
<ng-container *ngFor="let row of summaryRows">
|
||||||
<dt class="text-muted-foreground whitespace-nowrap">{{ row.label }}</dt>
|
<dt class="text-muted-foreground whitespace-nowrap font-semibold">{{ row.label }}</dt>
|
||||||
<dd class="font-medium break-words">{{ row.value }}</dd>
|
<dd class="font-medium break-words">{{ row.value }}</dd>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</dl>
|
</dl>
|
||||||
@ -22,26 +23,31 @@
|
|||||||
|
|
||||||
<ng-container *ngIf="current.tags.length">
|
<ng-container *ngIf="current.tags.length">
|
||||||
<section class="not-prose">
|
<section class="not-prose">
|
||||||
<h4 class="text-xs uppercase tracking-wide text-muted-foreground mb-1">Tags</h4>
|
<h4 class="text-xs uppercase tracking-wide text-muted-foreground font-semibold mb-1">Tags</h4>
|
||||||
<div class="flex flex-wrap gap-1">
|
<div class="flex flex-wrap gap-1.5">
|
||||||
<span *ngFor="let tag of current.tags" class="px-2 py-0.5 text-xs border rounded-full bg-muted/40">{{ tag }}</span>
|
<span *ngFor="let tag of current.tags" class="px-2.5 py-0.5 text-xs font-medium border border-border rounded-full bg-muted/70">{{ tag }}</span>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="current.aliases.length">
|
<ng-container *ngIf="current.aliases.length">
|
||||||
<section class="not-prose">
|
<section class="not-prose">
|
||||||
<h4 class="text-xs uppercase tracking-wide text-muted-foreground mb-1">Aliases</h4>
|
<h4 class="text-xs uppercase tracking-wide text-muted-foreground font-semibold mb-1">Aliases</h4>
|
||||||
<p class="text-sm leading-5 text-muted-foreground">{{ current.aliases.join(' · ') }}</p>
|
<p class="text-sm leading-5 text-muted-foreground">{{ current.aliases.join(' · ') }}</p>
|
||||||
</section>
|
</section>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="hasStates()">
|
<ng-container *ngIf="hasStates()">
|
||||||
<section class="not-prose">
|
<section class="not-prose">
|
||||||
<h4 class="text-xs uppercase tracking-wide text-muted-foreground mb-1">États</h4>
|
<h4 class="text-xs uppercase tracking-wide text-muted-foreground font-semibold mb-1">États</h4>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<ng-container *ngFor="let st of stateEntries">
|
<ng-container *ngFor="let st of stateEntries">
|
||||||
<app-state-chip [state]="st.key" [value]="st.value"></app-state-chip>
|
<button type="button"
|
||||||
|
class="focus:outline-none"
|
||||||
|
(click)="toggleState(st)"
|
||||||
|
title="Basculer l'état">
|
||||||
|
<app-state-chip [state]="st.key" [value]="st.value"></app-state-chip>
|
||||||
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -49,10 +55,10 @@
|
|||||||
|
|
||||||
<ng-container *ngIf="hasAdditional()">
|
<ng-container *ngIf="hasAdditional()">
|
||||||
<section class="not-prose">
|
<section class="not-prose">
|
||||||
<h4 class="text-xs uppercase tracking-wide text-muted-foreground mb-2">Autres propriétés</h4>
|
<h4 class="text-xs uppercase tracking-wide text-muted-foreground font-semibold mb-2">Autres propriétés</h4>
|
||||||
<dl class="space-y-2">
|
<dl class="space-y-2">
|
||||||
<ng-container *ngFor="let entry of additionalEntries">
|
<ng-container *ngFor="let entry of additionalEntries">
|
||||||
<div class="rounded-lg border border-border/60 bg-card/60 px-3 py-2">
|
<div class="rounded-lg border border-border bg-card px-3 py-2">
|
||||||
<dt class="text-xs font-medium text-muted-foreground uppercase tracking-wide">{{ entry.label }}</dt>
|
<dt class="text-xs font-medium text-muted-foreground uppercase tracking-wide">{{ entry.label }}</dt>
|
||||||
<dd class="text-sm leading-5 mt-1" [class.whitespace-pre-wrap]="entry.multiline">
|
<dd class="text-sm leading-5 mt-1" [class.whitespace-pre-wrap]="entry.multiline">
|
||||||
{{ entry.displayValue }}
|
{{ entry.displayValue }}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
import { Component, EventEmitter, Input, Output, inject } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { StateChipComponent } from '../state-chip/state-chip.component';
|
import { StateChipComponent } from '../state-chip/state-chip.component';
|
||||||
import {
|
import {
|
||||||
@ -7,6 +7,8 @@ import {
|
|||||||
NotePropertyStates,
|
NotePropertyStates,
|
||||||
NotePropertySummary,
|
NotePropertySummary,
|
||||||
} from '../../shared/note-properties.model';
|
} from '../../shared/note-properties.model';
|
||||||
|
import { VaultService } from '../../../../../services/vault.service';
|
||||||
|
import { FrontmatterPropertiesService } from '../../shared/frontmatter-properties.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-properties-popover',
|
selector: 'app-properties-popover',
|
||||||
@ -16,9 +18,13 @@ import {
|
|||||||
})
|
})
|
||||||
export class PropertiesPopoverComponent {
|
export class PropertiesPopoverComponent {
|
||||||
@Input() props: NoteProperties | null = null;
|
@Input() props: NoteProperties | null = null;
|
||||||
|
@Input() noteId: string | null = null;
|
||||||
@Output() requestClose = new EventEmitter<void>();
|
@Output() requestClose = new EventEmitter<void>();
|
||||||
@Output() cancelClose = new EventEmitter<void>();
|
@Output() cancelClose = new EventEmitter<void>();
|
||||||
|
|
||||||
|
private vault = inject(VaultService);
|
||||||
|
private frontmatter = inject(FrontmatterPropertiesService);
|
||||||
|
|
||||||
private readonly summaryConfig: Array<{
|
private readonly summaryConfig: Array<{
|
||||||
key: keyof NotePropertySummary;
|
key: keyof NotePropertySummary;
|
||||||
label: string;
|
label: string;
|
||||||
@ -40,6 +46,8 @@ export class PropertiesPopoverComponent {
|
|||||||
{ key: 'archive', label: 'Archivé' },
|
{ key: 'archive', label: 'Archivé' },
|
||||||
{ key: 'draft', label: 'Brouillon' },
|
{ key: 'draft', label: 'Brouillon' },
|
||||||
{ key: 'private', label: 'Privé' },
|
{ key: 'private', label: 'Privé' },
|
||||||
|
{ key: 'template', label: 'Template' },
|
||||||
|
{ key: 'task', label: 'Tâche' },
|
||||||
];
|
];
|
||||||
|
|
||||||
get summaryRows(): Array<{ label: string; value: string }> {
|
get summaryRows(): Array<{ label: string; value: string }> {
|
||||||
@ -69,12 +77,9 @@ export class PropertiesPopoverComponent {
|
|||||||
return this.props?.aliases ?? [];
|
return this.props?.aliases ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
get stateEntries(): Array<{ key: keyof NotePropertyStates; value: boolean | undefined }> {
|
get stateEntries(): Array<{ key: keyof NotePropertyStates; value: boolean }> {
|
||||||
const states = this.props?.states;
|
const states = this.props?.states || {};
|
||||||
if (!states) return [];
|
return this.stateOrder.map(({ key }) => ({ key, value: states[key] ?? false }));
|
||||||
return this.stateOrder
|
|
||||||
.map(({ key }) => ({ key, value: states[key] }))
|
|
||||||
.filter(entry => entry.value !== undefined);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get additionalEntries(): NotePropertyEntry[] {
|
get additionalEntries(): NotePropertyEntry[] {
|
||||||
@ -97,4 +102,14 @@ export class PropertiesPopoverComponent {
|
|||||||
hasAdditional(): boolean {
|
hasAdditional(): boolean {
|
||||||
return this.additionalEntries.length > 0;
|
return this.additionalEntries.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async toggleState(entry: { key: keyof NotePropertyStates; value: boolean }): Promise<void> {
|
||||||
|
if (!this.noteId) return;
|
||||||
|
const next = !entry.value;
|
||||||
|
const ok = await this.vault.updateNoteStates(this.noteId, entry.key, next);
|
||||||
|
if (!ok) return;
|
||||||
|
// Refresh props from updated note
|
||||||
|
const note = this.vault.getNoteById(this.noteId);
|
||||||
|
this.props = this.frontmatter.get(note);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<span class="inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs"
|
<span class="inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs"
|
||||||
[class.opacity-60]="value === false">
|
[ngClass]="chipClass">
|
||||||
<span class="w-4 h-4 text-current inline-flex items-center justify-center">
|
<span class="w-4 h-4 text-current inline-flex items-center justify-center">
|
||||||
<!-- Minimal Lucide-like inline SVGs to avoid extra deps -->
|
<!-- Minimal Lucide-like inline SVGs to avoid extra deps -->
|
||||||
<svg *ngIf="icon==='globe'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-4 h-4" 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>
|
<svg *ngIf="icon==='globe'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-4 h-4" 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>
|
||||||
@ -9,6 +9,8 @@
|
|||||||
<svg *ngIf="icon==='file'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-4 h-4" 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>
|
<svg *ngIf="icon==='file'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-4 h-4" 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>
|
||||||
<svg *ngIf="icon==='lock'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
|
<svg *ngIf="icon==='lock'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
|
||||||
<svg *ngIf="icon==='lock-open'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 9.9-1"/></svg>
|
<svg *ngIf="icon==='lock-open'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 9.9-1"/></svg>
|
||||||
|
<svg *ngIf="icon==='template'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="6" rx="2"/><rect x="3" y="11" width="18" height="10" rx="2"/></svg>
|
||||||
|
<svg *ngIf="icon==='task'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="6" width="16" height="12" rx="2"/><path *ngIf="value" d="M8 12l3 3 5-5"/></svg>
|
||||||
</span>
|
</span>
|
||||||
<span class="capitalize">{{ label }}</span>
|
<span class="capitalize">{{ label }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { CommonModule } from '@angular/common';
|
|||||||
templateUrl: './state-chip.component.html'
|
templateUrl: './state-chip.component.html'
|
||||||
})
|
})
|
||||||
export class StateChipComponent {
|
export class StateChipComponent {
|
||||||
@Input() state!: 'publish' | 'favoris' | 'archive' | 'draft' | 'private';
|
@Input() state!: 'publish' | 'favoris' | 'archive' | 'draft' | 'private' | 'template' | 'task';
|
||||||
@Input() value: boolean | null | undefined;
|
@Input() value: boolean | null | undefined;
|
||||||
|
|
||||||
get label(): string {
|
get label(): string {
|
||||||
@ -18,14 +18,38 @@ export class StateChipComponent {
|
|||||||
case 'archive': return this.value ? 'Archivé' : 'Non archivé';
|
case 'archive': return this.value ? 'Archivé' : 'Non archivé';
|
||||||
case 'draft': return 'Brouillon';
|
case 'draft': return 'Brouillon';
|
||||||
case 'private': return 'Privé';
|
case 'private': return 'Privé';
|
||||||
|
case 'template': return 'Template';
|
||||||
|
case 'task': return this.value ? 'Tâche' : 'Tâche';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get icon(): 'archive' | 'box' | 'globe' | 'heart' | 'file' | 'lock' | 'lock-open' {
|
get icon(): 'archive' | 'box' | 'globe' | 'heart' | 'file' | 'lock' | 'lock-open' | 'template' | 'task' {
|
||||||
if (this.state === 'archive') return this.value ? 'archive' : 'box';
|
if (this.state === 'archive') return this.value ? 'archive' : 'box';
|
||||||
if (this.state === 'publish') return 'globe';
|
if (this.state === 'publish') return 'globe';
|
||||||
if (this.state === 'favoris') return 'heart';
|
if (this.state === 'favoris') return 'heart';
|
||||||
if (this.state === 'draft') return 'file';
|
if (this.state === 'draft') return 'file';
|
||||||
|
if (this.state === 'template') return 'template';
|
||||||
|
if (this.state === 'task') return 'task';
|
||||||
return this.value ? 'lock' : 'lock-open';
|
return this.value ? 'lock' : 'lock-open';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get chipClass(): string {
|
||||||
|
if (this.value === false) return 'text-muted-foreground border-border bg-transparent';
|
||||||
|
switch (this.state) {
|
||||||
|
case 'publish':
|
||||||
|
return 'text-emerald-400 border-emerald-600/40 bg-emerald-500/10';
|
||||||
|
case 'favoris':
|
||||||
|
return 'text-rose-400 border-rose-600/40 bg-rose-500/10';
|
||||||
|
case 'archive':
|
||||||
|
return 'text-amber-400 border-amber-600/40 bg-amber-500/10';
|
||||||
|
case 'draft':
|
||||||
|
return 'text-sky-400 border-sky-600/40 bg-sky-500/10';
|
||||||
|
case 'private':
|
||||||
|
return 'text-violet-400 border-violet-600/40 bg-violet-500/10';
|
||||||
|
case 'template':
|
||||||
|
return 'text-amber-400 border-amber-600/40 bg-amber-500/10';
|
||||||
|
case 'task':
|
||||||
|
return 'text-sky-400 border-sky-600/40 bg-sky-500/10';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,15 +32,20 @@ export class FrontmatterPropertiesService {
|
|||||||
category: this.toStr(frontmatter, consumedRawKeys, ['catégorie', 'categorie', 'category']),
|
category: this.toStr(frontmatter, consumedRawKeys, ['catégorie', 'categorie', 'category']),
|
||||||
};
|
};
|
||||||
|
|
||||||
const tags = this.toArray(frontmatter, consumedRawKeys, ['tags', 'tag']) ?? [];
|
// Merge tags from frontmatter and from the parsed note content to ensure completeness
|
||||||
|
const fmTags = this.toArray(frontmatter, consumedRawKeys, ['tags', 'tag']) ?? [];
|
||||||
|
const noteTags = Array.isArray(note.tags) ? note.tags : [];
|
||||||
|
const tags = Array.from(new Set([...(noteTags ?? []), ...fmTags]));
|
||||||
const aliases = this.toArray(frontmatter, consumedRawKeys, ['aliases', 'alias']) ?? [];
|
const aliases = this.toArray(frontmatter, consumedRawKeys, ['aliases', 'alias']) ?? [];
|
||||||
|
|
||||||
const states: NotePropertyStates = {};
|
const states: NotePropertyStates = {};
|
||||||
this.assignState(states, 'publish', frontmatter, consumedRawKeys, ['publish', 'publié']);
|
this.assignState(states, 'publish', frontmatter, consumedRawKeys, ['publish', 'publié', 'publie', 'published']);
|
||||||
this.assignState(states, 'favoris', frontmatter, consumedRawKeys, ['favoris', 'favorite', 'favourite']);
|
this.assignState(states, 'favoris', frontmatter, consumedRawKeys, ['favoris', 'favori', 'favorite', 'favourite', 'fav', 'star', 'starred']);
|
||||||
this.assignState(states, 'archive', frontmatter, consumedRawKeys, ['archive', 'archived']);
|
this.assignState(states, 'archive', frontmatter, consumedRawKeys, ['archive', 'archived', 'archivé', 'archivee']);
|
||||||
this.assignState(states, 'draft', frontmatter, consumedRawKeys, ['draft', 'brouillon']);
|
this.assignState(states, 'draft', frontmatter, consumedRawKeys, ['draft', 'brouillon']);
|
||||||
this.assignState(states, 'private', frontmatter, consumedRawKeys, ['private', 'privé', 'prive']);
|
this.assignState(states, 'private', frontmatter, consumedRawKeys, ['private', 'privé', 'prive', 'privée', 'privee']);
|
||||||
|
this.assignState(states, 'template', frontmatter, consumedRawKeys, ['template', 'modèle', 'modele']);
|
||||||
|
this.assignState(states, 'task', frontmatter, consumedRawKeys, ['task', 'tâche', 'tache']);
|
||||||
|
|
||||||
const additional = this.buildAdditionalEntries(frontmatter, consumedRawKeys);
|
const additional = this.buildAdditionalEntries(frontmatter, consumedRawKeys);
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,8 @@ export interface NotePropertyStates {
|
|||||||
archive?: boolean;
|
archive?: boolean;
|
||||||
draft?: boolean;
|
draft?: boolean;
|
||||||
private?: boolean;
|
private?: boolean;
|
||||||
|
template?: boolean;
|
||||||
|
task?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NotePropertyEntry {
|
export interface NotePropertyEntry {
|
||||||
|
|||||||
@ -79,7 +79,7 @@ import { VaultService } from '../../../services/vault.service';
|
|||||||
<div *ngIf="open.tags" class="px-2 py-2">
|
<div *ngIf="open.tags" class="px-2 py-2">
|
||||||
<ul class="space-y-0.5 text-sm">
|
<ul class="space-y-0.5 text-sm">
|
||||||
<li *ngFor="let t of tags" class="flex items-center gap-2">
|
<li *ngFor="let t of tags" class="flex items-center gap-2">
|
||||||
<button (click)="tagSelected.emit(t.name)" class="flex-1 text-left px-2 py-1 rounded hover:bg-surface1 dark:hover:bg-card truncate">
|
<button (click)="tagSelected.emit(t.name)" class="flex-1 text-left px-2.5 py-1.5 rounded-lg transition-colors hover:bg-slate-500/10 dark:hover:bg-surface2/15 truncate">
|
||||||
<span>🏷️</span>
|
<span>🏷️</span>
|
||||||
<span class="ml-1">{{ t.name }}</span>
|
<span class="ml-1">{{ t.name }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -142,11 +142,32 @@ import { ParametersPage } from '../../features/parameters/parameters.page';
|
|||||||
></app-note-viewer>
|
></app-note-viewer>
|
||||||
</div>
|
</div>
|
||||||
<aside class="hidden xl:block border-l border-border dark:border-gray-800 overflow-y-auto transition-all duration-300 ease-in-out" appScrollableOverlay [style.width.px]="isOutlineOpen ? rightSidebarWidth : 0" [class.opacity-0]="!isOutlineOpen" [class.pointer-events-none]="!isOutlineOpen">
|
<aside class="hidden xl:block border-l border-border dark:border-gray-800 overflow-y-auto transition-all duration-300 ease-in-out" appScrollableOverlay [style.width.px]="isOutlineOpen ? rightSidebarWidth : 0" [class.opacity-0]="!isOutlineOpen" [class.pointer-events-none]="!isOutlineOpen">
|
||||||
<div class="p-3">
|
<div class="p-3 space-y-3">
|
||||||
<h2 class="text-sm font-semibold mb-2">Sommaire</h2>
|
<div class="flex items-center justify-between">
|
||||||
|
<h2 class="text-sm font-semibold">Sommaire</h2>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="inline-flex items-center justify-center w-8 h-8 rounded-full hover:bg-surface1 dark:hover:bg-card transition"
|
||||||
|
aria-label="Fermer le sommaire"
|
||||||
|
(click)="toggleOutlineRequest.emit()">
|
||||||
|
<svg class="w-4 h-4" 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>
|
||||||
<ul class="space-y-1 text-sm text-muted dark:text-main">
|
<ul class="space-y-1 text-sm text-muted dark:text-main">
|
||||||
<li *ngFor="let h of tableOfContents">
|
<li *ngFor="let h of tableOfContents" class="leading-tight">
|
||||||
<a class="block truncate hover:text-main dark:hover:text-white cursor-pointer" (click)="navigateHeading.emit(h.id)" [style.paddingLeft.rem]="(h.level - 1) * 0.75">{{ h.text }}</a>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="w-full text-left block px-3 py-2 rounded-lg transition-colors hover:bg-slate-500/10 dark:hover:bg-surface2/15 focus:outline-none"
|
||||||
|
(click)="navigateHeading.emit(h.id)"
|
||||||
|
[style.paddingLeft.rem]="(h.level - 1) * 0.75"
|
||||||
|
>
|
||||||
|
<span class="block truncate text-muted dark:text-main hover:text-main dark:hover:text-white">
|
||||||
|
{{ h.text }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -207,18 +228,8 @@ import { ParametersPage } from '../../features/parameters/parameters.page';
|
|||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<div class="note-content-area h-full px-3 py-3 animate-fadeIn" style="overflow-y: auto !important;" appScrollableOverlay>
|
<div class="note-content-area h-full px-3 py-3 animate-fadeIn" style="overflow-y: auto !important;" appScrollableOverlay>
|
||||||
<div class="flex items-center justify-between mb-3 sticky top-0 bg-card dark:bg-main py-2 -mt-2 z-10">
|
|
||||||
<h2 class="text-base font-semibold truncate">{{ selectedNote?.title || 'Aucune page' }}</h2>
|
|
||||||
<button
|
|
||||||
*ngIf="tableOfContents.length > 0"
|
|
||||||
(pointerdown)="$event.stopPropagation(); mobileNav.toggleToc()"
|
|
||||||
(click)="$event.preventDefault()"
|
|
||||||
class="p-2 rounded-lg hover:bg-surface1 dark:hover:bg-card active:bg-surface2 dark:active:bg-gray-700 transition-all active:scale-95 transform flex-shrink-0">
|
|
||||||
📋
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
@if (selectedNote) {
|
@if (selectedNote) {
|
||||||
<app-note-viewer [note]="selectedNote || null" [noteHtmlContent]="renderedNoteContent" [allNotes]="vault.allNotes()" (noteLinkClicked)="noteSelected.emit($event)" (tagClicked)="tagClicked.emit($event)" (wikiLinkActivated)="wikiLinkActivated.emit($event)" (fullScreenRequested)="toggleNoteFullScreen()" (parametersRequested)="onParametersOpen()"></app-note-viewer>
|
<app-note-viewer [note]="selectedNote || null" [noteHtmlContent]="renderedNoteContent" [allNotes]="vault.allNotes()" (noteLinkClicked)="noteSelected.emit($event)" (tagClicked)="onTagSelected($event)" (wikiLinkActivated)="wikiLinkActivated.emit($event)" (fullScreenRequested)="toggleNoteFullScreen()" (parametersRequested)="onParametersOpen()"></app-note-viewer>
|
||||||
} @else {
|
} @else {
|
||||||
<div class="mt-10 text-center text-sm text-muted dark:text-muted">
|
<div class="mt-10 text-center text-sm text-muted dark:text-muted">
|
||||||
<div class="text-4xl mb-3">📄</div>
|
<div class="text-4xl mb-3">📄</div>
|
||||||
|
|||||||
@ -5,6 +5,92 @@ function normalizeTag(tag: string): string {
|
|||||||
return tag.trim().replace(/\s+/g, ' ');
|
return tag.trim().replace(/\s+/g, ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Réécrit des clés booléennes dans le front-matter YAML.
|
||||||
|
* - Crée la section --- ... --- si absente
|
||||||
|
* - Met à jour/insère les clés fournies (value true/false)
|
||||||
|
* - Supprime la clé si la valeur est undefined
|
||||||
|
* - Préserve les autres propriétés et le corps
|
||||||
|
*/
|
||||||
|
export function rewriteBooleanFrontmatter(
|
||||||
|
rawMarkdown: string,
|
||||||
|
updates: Record<string, boolean | undefined>
|
||||||
|
): string {
|
||||||
|
const content = rawMarkdown.replace(/^\uFEFF/, '').replace(/\r\n?/g, '\n');
|
||||||
|
|
||||||
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)/);
|
||||||
|
|
||||||
|
const normalizeKey = (k: string) => k.trim();
|
||||||
|
const toYamlBool = (v: boolean) => (v ? 'true' : 'false');
|
||||||
|
|
||||||
|
const applyUpdatesToText = (fmText: string): string => {
|
||||||
|
const lines = fmText.split('\n');
|
||||||
|
const kept: string[] = [];
|
||||||
|
const updatedKeys = new Set<string>(Object.keys(updates).map(normalizeKey));
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!trimmed || trimmed.startsWith('#')) {
|
||||||
|
kept.push(line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const colonIndex = line.indexOf(':');
|
||||||
|
if (colonIndex === -1) {
|
||||||
|
kept.push(line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const key = normalizeKey(line.slice(0, colonIndex));
|
||||||
|
if (!updatedKeys.has(key)) {
|
||||||
|
kept.push(line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const next = updates[key];
|
||||||
|
if (next === undefined) {
|
||||||
|
// remove this key (skip)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
kept.push(`${key}: ${toYamlBool(next)}`);
|
||||||
|
// Mark as handled
|
||||||
|
updatedKeys.delete(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append remaining keys that didn't exist
|
||||||
|
for (const key of Object.keys(updates)) {
|
||||||
|
const norm = normalizeKey(key);
|
||||||
|
const val = updates[key];
|
||||||
|
if (val === undefined) continue;
|
||||||
|
// If not already written (because it existed), append
|
||||||
|
if (!kept.some(l => normalizeKey(l.split(':')[0] || '') === norm)) {
|
||||||
|
kept.push(`${norm}: ${toYamlBool(val)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean multiple empty lines
|
||||||
|
let out = kept.join('\n').replace(/\n{2,}/g, '\n').trim();
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!fmMatch) {
|
||||||
|
// No frontmatter: create one only if there is at least one defined update
|
||||||
|
const definedEntries = Object.entries(updates).filter(([, v]) => v !== undefined) as Array<[
|
||||||
|
string,
|
||||||
|
boolean
|
||||||
|
]>;
|
||||||
|
if (!definedEntries.length) return content;
|
||||||
|
const lines = definedEntries.map(([k, v]) => `${normalizeKey(k)}: ${toYamlBool(v)}`).join('\n');
|
||||||
|
return `---\n${lines}\n---\n${content}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fmText = fmMatch[1] || '';
|
||||||
|
const body = fmMatch[2] || '';
|
||||||
|
const updatedFm = applyUpdatesToText(fmText);
|
||||||
|
if (!updatedFm.trim()) {
|
||||||
|
// If frontmatter became empty, drop the section entirely
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
return `---\n${updatedFm}\n---\n${body}`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Déduplique les tags (case-insensitive) en préservant la première occurrence
|
* Déduplique les tags (case-insensitive) en préservant la première occurrence
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<div class="fixed inset-0 z-50 flex items-end md: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(880px,96vw)] md:max-h-[85vh] overflow-auto">
|
<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)] h-[92vh] md:h-auto md:max-h-[85vh] overflow-auto pb-24 md:pb-5">
|
||||||
<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>
|
||||||
|
|||||||
@ -9,7 +9,30 @@
|
|||||||
<svg class="text-xl" 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="text-xl" 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>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="flex flex-wrap items-center gap-1">
|
<!-- Mobile: show first two + '+N' button -->
|
||||||
|
<div class="flex sm:hidden flex-wrap items-center gap-1">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="md-tag-badge"
|
||||||
|
*ngFor="let tag of firstTwo()"
|
||||||
|
[ngClass]="tagColorClass(tag)"
|
||||||
|
(click)="onChipClick(tag)"
|
||||||
|
[attr.data-tag]="tag"
|
||||||
|
[title]="'Voir les notes #'+tag">
|
||||||
|
{{ tag }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
*ngIf="remainingCount() > 0"
|
||||||
|
type="button"
|
||||||
|
class="btn-colored-xs btn-neutral"
|
||||||
|
(click)="toggleEditor()"
|
||||||
|
[title]="'Voir tous les tags'">
|
||||||
|
+{{ remainingCount() }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Desktop: show full list -->
|
||||||
|
<div class="hidden sm:flex flex-wrap items-center gap-1">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="md-tag-badge"
|
class="md-tag-badge"
|
||||||
|
|||||||
@ -31,6 +31,8 @@ export class TagManagerComponent {
|
|||||||
|
|
||||||
isEditing = signal(false);
|
isEditing = signal(false);
|
||||||
readonly normalizedTags = computed(() => uniqueTags(this.tagsSignal()));
|
readonly normalizedTags = computed(() => uniqueTags(this.tagsSignal()));
|
||||||
|
readonly firstTwo = computed(() => this.normalizedTags().slice(0, 2));
|
||||||
|
readonly remainingCount = computed(() => Math.max(0, this.normalizedTags().length - 2));
|
||||||
|
|
||||||
private readonly tagPaletteSize = 12;
|
private readonly tagPaletteSize = 12;
|
||||||
private readonly tagColorCache = new Map<string, number>();
|
private readonly tagColorCache = new Map<string, number>();
|
||||||
|
|||||||
@ -51,7 +51,7 @@ export interface WikiLinkActivation {
|
|||||||
<div class="relative p-1 prose prose-lg dark:prose-invert max-w-none prose-p:leading-[1] prose-li:leading-[1] prose-blockquote:leading-[1]">
|
<div class="relative p-1 prose prose-lg dark:prose-invert max-w-none prose-p:leading-[1] prose-li:leading-[1] prose-blockquote:leading-[1]">
|
||||||
<div class="sr-only" role="status" aria-live="polite">{{ copyStatus() }}</div>
|
<div class="sr-only" role="status" aria-live="polite">{{ copyStatus() }}</div>
|
||||||
<!-- Compact Top Bar -->
|
<!-- Compact Top Bar -->
|
||||||
<div class="flex items-center justify-between gap-2 pl-1 pr-2 py-1 mb-2 text-text-muted text-xs">
|
<div class="flex items-start justify-between gap-2 pl-1 pr-2 py-1 mb-2 text-text-muted text-xs">
|
||||||
<app-note-header class="flex-1 min-w-0"
|
<app-note-header class="flex-1 min-w-0"
|
||||||
[fullPath]="note().filePath"
|
[fullPath]="note().filePath"
|
||||||
[noteId]="note().id"
|
[noteId]="note().id"
|
||||||
@ -62,7 +62,7 @@ export interface WikiLinkActivation {
|
|||||||
(tagSelected)="tagClicked.emit($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 self-start pt-1">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="note-toolbar-icon"
|
class="note-toolbar-icon"
|
||||||
@ -85,15 +85,17 @@ export interface WikiLinkActivation {
|
|||||||
{{ fullScreenActive() ? '⤢' : '⤢' }}
|
{{ fullScreenActive() ? '⤢' : '⤢' }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<div class="hidden sm:block">
|
||||||
type="button"
|
<button
|
||||||
class="note-toolbar-icon"
|
type="button"
|
||||||
(click)="showToc.emit()"
|
class="note-toolbar-icon"
|
||||||
[attr.title]="tocOpen() ? 'Cacher sommaire' : 'Afficher sommaire'"
|
(click)="showToc.emit()"
|
||||||
[attr.aria-label]="tocOpen() ? 'Cacher sommaire' : 'Afficher sommaire'"
|
[attr.title]="tocOpen() ? 'Cacher sommaire' : 'Afficher sommaire'"
|
||||||
>
|
[attr.aria-label]="tocOpen() ? 'Cacher sommaire' : 'Afficher sommaire'"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" [ngClass]="tocOpen() ? 'toc-toggle--active' : 'toc-toggle--idle'"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="M13 8h5"/><path d="M13 12h5"/><path d="M13 16h5"/></svg>
|
>
|
||||||
</button>
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" [ngClass]="tocOpen() ? 'toc-toggle--active' : 'toc-toggle--idle'"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="M13 8h5"/><path d="M13 12h5"/><path d="M13 16h5"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -155,23 +157,29 @@ export interface WikiLinkActivation {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="not-prose flex items-center gap-3 text-sm text-text-muted my-4">
|
<div class="not-prose flex flex-col gap-2 text-sm text-text-muted my-4">
|
||||||
<span class="inline-flex items-center gap-1">
|
<!-- Row 1: date + author -->
|
||||||
|
<div class="flex flex-wrap items-center gap-4">
|
||||||
|
<span class="inline-flex items-center gap-1">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2h-1.5a1.5 1.5 0 01-3 0h-5a1.5 1.5 0 01-3 0H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2h-1.5a1.5 1.5 0 01-3 0h-5a1.5 1.5 0 01-3 0H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||||
</svg>
|
</svg>
|
||||||
{{ note().updatedAt | date:'medium' }}
|
{{ note().updatedAt | date:'medium' }}
|
||||||
</span>
|
</span>
|
||||||
<span class="inline-flex items-center gap-1">
|
<span class="inline-flex items-center gap-1">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
d="M12 8c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM5.5 21a6.5 6.5 0 0113 0" />
|
d="M12 8c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM5.5 21a6.5 6.5 0 0113 0" />
|
||||||
</svg>
|
</svg>
|
||||||
{{ getAuthorFromFrontmatter() ?? note().author ?? 'Auteur inconnu' }}
|
{{ getAuthorFromFrontmatter() ?? note().author ?? 'Auteur inconnu' }}
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Row 2: state icons (toggle buttons) -->
|
||||||
|
<div class="flex flex-wrap items-center gap-3">
|
||||||
@if (hasState('favoris')) {
|
@if (hasState('favoris')) {
|
||||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('favoris') ? 'text-rose-500' : 'text-muted'" title="{{ state('favoris') ? 'Ajouté aux favoris' : 'Non favori' }}" role="img" aria-label="{{ state('favoris') ? 'Ajouté aux favoris' : 'Non favori' }}">
|
<button type="button" class="inline-flex items-center gap-1 transition-colors focus:outline-none" [ngClass]="state('favoris') ? 'text-rose-500' : 'text-muted'" title="{{ state('favoris') ? 'Ajouté aux favoris' : 'Non favori' }}" aria-label="{{ state('favoris') ? 'Ajouté aux favoris' : 'Non favori' }}" (click)="toggleState('favoris')">
|
||||||
@if (state('favoris')) {
|
@if (state('favoris')) {
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
||||||
<path d="M19 14.5c1.5-1.5 2-4 0-6s-5-2-7 1c-2-3-5-3.5-7-1s-1.5 4.5 0 6l7 6z" />
|
<path d="M19 14.5c1.5-1.5 2-4 0-6s-5-2-7 1c-2-3-5-3.5-7-1s-1.5 4.5 0 6l7 6z" />
|
||||||
@ -181,19 +189,19 @@ export interface WikiLinkActivation {
|
|||||||
<path d="M19 14.5c1.5-1.5 2-4 0-6s-5-2-7 1c-2-3-5-3.5-7-1s-1.5 4.5 0 6l7 6z" />
|
<path d="M19 14.5c1.5-1.5 2-4 0-6s-5-2-7 1c-2-3-5-3.5-7-1s-1.5 4.5 0 6l7 6z" />
|
||||||
</svg>
|
</svg>
|
||||||
}
|
}
|
||||||
</span>
|
</button>
|
||||||
}
|
}
|
||||||
@if (hasState('publish')) {
|
@if (hasState('publish')) {
|
||||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('publish') ? 'text-green-500' : 'text-muted'" title="{{ state('publish') ? 'Publié sur le web' : 'Non publié' }}" role="img" aria-label="{{ state('publish') ? 'Publié sur le web' : 'Non publié' }}">
|
<button type="button" class="inline-flex items-center gap-1 transition-colors focus:outline-none" [ngClass]="state('publish') ? 'text-green-500' : 'text-muted'" title="{{ state('publish') ? 'Publié sur le web' : 'Non publié' }}" aria-label="{{ state('publish') ? 'Publié sur le web' : 'Non publié' }}" (click)="toggleState('publish')">
|
||||||
<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">
|
<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" />
|
<circle cx="12" cy="12" r="10" />
|
||||||
<path d="M2 12h20" />
|
<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" />
|
<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>
|
</svg>
|
||||||
</span>
|
</button>
|
||||||
}
|
}
|
||||||
@if (hasState('draft')) {
|
@if (hasState('draft')) {
|
||||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('draft') ? 'text-yellow-500' : 'text-muted'" title="{{ state('draft') ? 'Brouillon actif' : 'Pas un brouillon' }}" role="img" aria-label="{{ state('draft') ? 'Brouillon actif' : 'Pas un brouillon' }}">
|
<button type="button" class="inline-flex items-center gap-1 transition-colors focus:outline-none" [ngClass]="state('draft') ? 'text-yellow-500' : 'text-muted'" title="{{ state('draft') ? 'Brouillon actif' : 'Pas un brouillon' }}" aria-label="{{ state('draft') ? 'Brouillon actif' : 'Pas un brouillon' }}" (click)="toggleState('draft')">
|
||||||
@if (state('draft')) {
|
@if (state('draft')) {
|
||||||
<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">
|
<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="M3 3h18v4H3z" />
|
<path d="M3 3h18v4H3z" />
|
||||||
@ -205,10 +213,10 @@ export interface WikiLinkActivation {
|
|||||||
<rect x="3" y="4" width="18" height="16" rx="2" />
|
<rect x="3" y="4" width="18" height="16" rx="2" />
|
||||||
</svg>
|
</svg>
|
||||||
}
|
}
|
||||||
</span>
|
</button>
|
||||||
}
|
}
|
||||||
@if (hasState('template')) {
|
@if (hasState('template')) {
|
||||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('template') ? 'text-amber-500' : 'text-muted'" title="{{ state('template') ? 'Modèle' : 'Non modèle' }}" role="img" aria-label="{{ state('template') ? 'Modèle' : 'Non modèle' }}">
|
<button type="button" class="inline-flex items-center gap-1 transition-colors focus:outline-none" [ngClass]="state('template') ? 'text-amber-500' : 'text-muted'" title="{{ state('template') ? 'Modèle' : 'Non modèle' }}" aria-label="{{ state('template') ? 'Modèle' : 'Non modèle' }}" (click)="toggleState('template')">
|
||||||
@if (state('template')) {
|
@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">
|
<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" />
|
<rect x="3" y="4" width="18" height="14" rx="2" />
|
||||||
@ -220,10 +228,10 @@ export interface WikiLinkActivation {
|
|||||||
<rect x="3" y="5" width="18" height="12" rx="2" />
|
<rect x="3" y="5" width="18" height="12" rx="2" />
|
||||||
</svg>
|
</svg>
|
||||||
}
|
}
|
||||||
</span>
|
</button>
|
||||||
}
|
}
|
||||||
@if (hasState('task')) {
|
@if (hasState('task')) {
|
||||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('task') ? 'text-indigo-500' : 'text-muted'" title="{{ state('task') ? 'Tâche' : 'Pas une tâche' }}" role="img" aria-label="{{ state('task') ? 'Tâche' : 'Pas une tâche' }}">
|
<button type="button" class="inline-flex items-center gap-1 transition-colors focus:outline-none" [ngClass]="state('task') ? 'text-indigo-500' : 'text-muted'" title="{{ state('task') ? 'Tâche' : 'Pas une tâche' }}" aria-label="{{ state('task') ? 'Tâche' : 'Pas une tâche' }}" (click)="toggleState('task')">
|
||||||
@if (state('task')) {
|
@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">
|
<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" />
|
<rect x="3" y="4" width="18" height="16" rx="2" />
|
||||||
@ -234,10 +242,10 @@ export interface WikiLinkActivation {
|
|||||||
<rect x="3" y="4" width="18" height="16" rx="2" />
|
<rect x="3" y="4" width="18" height="16" rx="2" />
|
||||||
</svg>
|
</svg>
|
||||||
}
|
}
|
||||||
</span>
|
</button>
|
||||||
}
|
}
|
||||||
@if (hasState('private')) {
|
@if (hasState('private')) {
|
||||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('private') ? 'text-purple-500' : 'text-muted'" title="{{ state('private') ? 'Privé' : 'Public' }}" role="img" aria-label="{{ state('private') ? 'Privé' : 'Public' }}">
|
<button type="button" class="inline-flex items-center gap-1 transition-colors focus:outline-none" [ngClass]="state('private') ? 'text-purple-500' : 'text-muted'" title="{{ state('private') ? 'Privé' : 'Public' }}" aria-label="{{ state('private') ? 'Privé' : 'Public' }}" (click)="toggleState('private')">
|
||||||
@if (state('private')) {
|
@if (state('private')) {
|
||||||
<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">
|
<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="11" width="18" height="11" rx="2" />
|
<rect x="3" y="11" width="18" height="11" rx="2" />
|
||||||
@ -249,10 +257,10 @@ export interface WikiLinkActivation {
|
|||||||
<path d="M7 11V7a5 5 0 0 1 9.9-1" />
|
<path d="M7 11V7a5 5 0 0 1 9.9-1" />
|
||||||
</svg>
|
</svg>
|
||||||
}
|
}
|
||||||
</span>
|
</button>
|
||||||
}
|
}
|
||||||
@if (hasState('archive')) {
|
@if (hasState('archive')) {
|
||||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('archive') ? 'text-amber-600' : 'text-muted'" title="{{ state('archive') ? 'Document archivé' : 'Document non archivé' }}" role="img" aria-label="{{ state('archive') ? 'Document archivé' : 'Document non archivé' }}">
|
<button type="button" class="inline-flex items-center gap-1 transition-colors focus:outline-none" [ngClass]="state('archive') ? 'text-amber-600' : 'text-muted'" title="{{ state('archive') ? 'Document archivé' : 'Document non archivé' }}" aria-label="{{ state('archive') ? 'Document archivé' : 'Document non archivé' }}" (click)="toggleState('archive')">
|
||||||
@if (state('archive')) {
|
@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">
|
<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"/>
|
<polyline points="22,12 18,12 18,8"/>
|
||||||
@ -266,8 +274,9 @@ export interface WikiLinkActivation {
|
|||||||
<line x1="10" y1="12" x2="14" y2="12"/>
|
<line x1="10" y1="12" x2="14" y2="12"/>
|
||||||
</svg>
|
</svg>
|
||||||
}
|
}
|
||||||
</span>
|
</button>
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div [innerHTML]="sanitizedHtmlContent()"></div>
|
<div [innerHTML]="sanitizedHtmlContent()"></div>
|
||||||
@ -359,6 +368,23 @@ export class NoteViewerComponent implements OnDestroy {
|
|||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async toggleState(key: 'publish' | 'favoris' | 'archive' | 'draft' | 'private' | 'template' | 'task'): Promise<void> {
|
||||||
|
const currentNote = this.note();
|
||||||
|
if (!currentNote?.id) return;
|
||||||
|
const current = this.state(key);
|
||||||
|
const next = !current;
|
||||||
|
const ok = await this.vault.updateNoteStates(currentNote.id, key, next);
|
||||||
|
if (!ok) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fm = (currentNote.frontmatter ??= {} as Note['frontmatter']);
|
||||||
|
(fm as any)[key] = next;
|
||||||
|
} catch {
|
||||||
|
// ignore failures for optimistic update
|
||||||
|
}
|
||||||
|
|
||||||
|
this.vault.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
effect(() => {
|
effect(() => {
|
||||||
|
|||||||
@ -179,7 +179,7 @@ interface TagGroup {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
(click)="toggleGroup(group.label)"
|
(click)="toggleGroup(group.label)"
|
||||||
class="group-header w-full flex items-center justify-between px-3 py-2 text-left hover:bg-obs-l-bg-hover dark:hover:bg-obs-d-bg-hover"
|
class="group-header w-full flex items-center justify-between px-3 py-2 text-left rounded-lg transition-colors hover:bg-slate-500/10 dark:hover:bg-surface2/15"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<svg
|
<svg
|
||||||
@ -208,7 +208,7 @@ interface TagGroup {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
(click)="onTagClick(tag.name)"
|
(click)="onTagClick(tag.name)"
|
||||||
class="tag-item w-full flex items-center justify-between px-3 py-2 text-left hover:bg-obs-l-bg-hover dark:hover:bg-obs-d-bg-hover group"
|
class="tag-item w-full flex items-center justify-between px-3 py-2 text-left rounded-lg transition-colors hover:bg-slate-500/10 dark:hover:bg-surface2/15 group"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2 flex-1 min-w-0">
|
<div class="flex items-center gap-2 flex-1 min-w-0">
|
||||||
<svg class="h-3.5 w-3.5 text-obs-l-text-muted dark:text-obs-d-text-muted flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg class="h-3.5 w-3.5 text-obs-l-text-muted dark:text-obs-d-text-muted flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { HttpClient } from '@angular/common/http';
|
|||||||
import { Note, VaultNode, GraphData, TagInfo, VaultFolder, FileMetadata } from '../types';
|
import { Note, VaultNode, GraphData, TagInfo, VaultFolder, FileMetadata } from '../types';
|
||||||
import { VaultEventsService, VaultEventPayload } from './vault-events.service';
|
import { VaultEventsService, VaultEventPayload } from './vault-events.service';
|
||||||
import { Subscription, firstValueFrom } from 'rxjs';
|
import { Subscription, firstValueFrom } from 'rxjs';
|
||||||
import { rewriteTagsFrontmatter } from '../app/shared/markdown/markdown-frontmatter.util';
|
import { rewriteTagsFrontmatter, rewriteBooleanFrontmatter } from '../app/shared/markdown/markdown-frontmatter.util';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// INTERFACES
|
// INTERFACES
|
||||||
@ -135,6 +135,30 @@ export class VaultService implements OnDestroy {
|
|||||||
this.initialize();
|
this.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateNoteStates(
|
||||||
|
noteId: string,
|
||||||
|
key: 'publish' | 'favoris' | 'archive' | 'draft' | 'private' | 'template' | 'task',
|
||||||
|
nextValue: boolean
|
||||||
|
): Promise<boolean> {
|
||||||
|
const note = this.getNoteById(noteId);
|
||||||
|
if (!note?.filePath) return false;
|
||||||
|
|
||||||
|
const currentRaw = note.rawContent ?? this.recomposeMarkdownFromNote(note);
|
||||||
|
const updatedRaw = rewriteBooleanFrontmatter(currentRaw, { [key]: nextValue });
|
||||||
|
|
||||||
|
if (!await this.saveMarkdown(note.filePath, updatedRaw)) return false;
|
||||||
|
|
||||||
|
const updatedFrontmatter = { ...(note.frontmatter || {}) } as any;
|
||||||
|
if (nextValue === undefined as any) {
|
||||||
|
delete updatedFrontmatter[key];
|
||||||
|
} else {
|
||||||
|
updatedFrontmatter[key] = nextValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateNoteInMap(note, { rawContent: updatedRaw, frontmatter: updatedFrontmatter });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.cleanup();
|
this.cleanup();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
|
||||||
@import './styles-test.css';
|
@import './styles-test.css';
|
||||||
@import './styles/themes.css';
|
@import './styles/themes.css';
|
||||||
@import './styles/markdown.css';
|
@import './styles/markdown.css';
|
||||||
@ -7,6 +8,23 @@
|
|||||||
@import './styles/_overlay-scrollbar.css';
|
@import './styles/_overlay-scrollbar.css';
|
||||||
@import './styles/codemirror.css';
|
@import './styles/codemirror.css';
|
||||||
|
|
||||||
|
/* Local-only fallbacks: use installed fonts if available */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'gg sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 100 900;
|
||||||
|
src: local('gg sans'), local('ggsans'), local('GG Sans');
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Whitney';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 100 900;
|
||||||
|
src: local('Whitney'), local('Whitney Book'), local('Whitney Medium'), local('Whitney Semibold'), local('Whitney Bold');
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
/* Excalidraw CSS variables (thème sombre) */
|
/* Excalidraw CSS variables (thème sombre) */
|
||||||
/* .excalidraw {
|
/* .excalidraw {
|
||||||
--color-primary: #ffffff;
|
--color-primary: #ffffff;
|
||||||
@ -263,8 +281,9 @@ excalidraw-editor .excalidraw .layer-ui__wrapper {
|
|||||||
--btn-hover-background: color-mix(in srgb, var(--bg-muted) 42%, transparent);
|
--btn-hover-background: color-mix(in srgb, var(--bg-muted) 42%, transparent);
|
||||||
--btn-focus-ring: color-mix(in srgb, var(--border) 45%, transparent);
|
--btn-focus-ring: color-mix(in srgb, var(--border) 45%, transparent);
|
||||||
--btn-muted-text: var(--text-muted);
|
--btn-muted-text: var(--text-muted);
|
||||||
--scrollbar-thumb-color: rgba(148, 163, 184, 0.45);
|
/* Scrollbar colors subtly follow theme brand/accent */
|
||||||
--scrollbar-thumb-color-active: rgba(148, 163, 184, 0.75);
|
--scrollbar-thumb-color: color-mix(in oklab, var(--brand, var(--scrollbar-thumb, rgba(148, 163, 184, 0.45))) 22%, transparent);
|
||||||
|
--scrollbar-thumb-color-active: color-mix(in oklab, var(--brand, var(--scrollbar-thumb, rgba(148, 163, 184, 0.45))) 36%, transparent);
|
||||||
|
|
||||||
/* Theme accent + CodeMirror highlight tokens */
|
/* Theme accent + CodeMirror highlight tokens */
|
||||||
--color-accent: var(--primary, #3b82f6);
|
--color-accent: var(--primary, #3b82f6);
|
||||||
@ -300,8 +319,9 @@ excalidraw-editor .excalidraw .layer-ui__wrapper {
|
|||||||
--btn-hover-background: color-mix(in srgb, var(--bg-muted) 36%, transparent);
|
--btn-hover-background: color-mix(in srgb, var(--bg-muted) 36%, transparent);
|
||||||
--btn-focus-ring: color-mix(in srgb, var(--border) 55%, transparent);
|
--btn-focus-ring: color-mix(in srgb, var(--border) 55%, transparent);
|
||||||
--btn-muted-text: var(--text-muted);
|
--btn-muted-text: var(--text-muted);
|
||||||
--scrollbar-thumb-color: rgba(148, 163, 184, 0.35);
|
/* Dark-mode: slightly stronger mix for visibility on darker bg */
|
||||||
--scrollbar-thumb-color-active: rgba(226, 232, 240, 0.72);
|
--scrollbar-thumb-color: color-mix(in oklab, var(--brand, var(--scrollbar-thumb, rgba(148, 163, 184, 0.35))) 28%, transparent);
|
||||||
|
--scrollbar-thumb-color-active: color-mix(in oklab, var(--brand, var(--scrollbar-thumb, rgba(148, 163, 184, 0.35))) 42%, transparent);
|
||||||
|
|
||||||
/* Dark theme adjustment */
|
/* Dark theme adjustment */
|
||||||
--cm-hl-bg: color-mix(in srgb, var(--color-accent) 22%, transparent);
|
--cm-hl-bg: color-mix(in srgb, var(--color-accent) 22%, transparent);
|
||||||
@ -317,6 +337,21 @@ excalidraw-editor .excalidraw .layer-ui__wrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Global native scrollbar fallback (for any container not using appScrollableOverlay) */
|
||||||
|
html, body {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--scrollbar-thumb-color) transparent;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar { width: 10px; height: 10px; }
|
||||||
|
::-webkit-scrollbar-track { background: transparent; }
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--scrollbar-thumb-color);
|
||||||
|
border-radius: 9999px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--scrollbar-thumb-color-active);
|
||||||
|
}
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
/* CodeMirror markdown highlight class - theme driven */
|
/* CodeMirror markdown highlight class - theme driven */
|
||||||
.cm-md-highlight {
|
.cm-md-highlight {
|
||||||
|
|||||||
@ -5,10 +5,20 @@
|
|||||||
|
|
||||||
/* ---------- Thèmes & variables ---------- */
|
/* ---------- Thèmes & variables ---------- */
|
||||||
:root {
|
:root {
|
||||||
/* Couleurs (clair) */
|
/* Couleurs (clair) — alignées sur les tokens de thème */
|
||||||
--ovsb-track-bg: rgba(15, 23, 42, 0.12);
|
/* Track très discret */
|
||||||
--ovsb-thumb-bg: rgba(100, 116, 139, 0.55);
|
--ovsb-track-bg: color-mix(in oklab, var(--border, rgba(15,23,42,0.25)) 16%, transparent);
|
||||||
--ovsb-thumb-bg-active: rgba(59, 130, 246, 0.75);
|
/* Pouce (thumb) subtil basé sur --scrollbar-thumb, avec légère teinte brand */
|
||||||
|
--ovsb-thumb-bg: color-mix(
|
||||||
|
in oklab,
|
||||||
|
var(--scrollbar-thumb, rgba(100,116,139,0.55)) 80%,
|
||||||
|
color-mix(in oklab, var(--brand, #64748b) 18%, transparent) 20%
|
||||||
|
);
|
||||||
|
--ovsb-thumb-bg-active: color-mix(
|
||||||
|
in oklab,
|
||||||
|
var(--scrollbar-thumb, rgba(100,116,139,0.55)) 65%,
|
||||||
|
color-mix(in oklab, var(--brand-700, var(--brand, #64748b)) 40%, transparent) 35%
|
||||||
|
);
|
||||||
|
|
||||||
/* Géométrie & transitions */
|
/* Géométrie & transitions */
|
||||||
--ovsb-trans: 240ms ease;
|
--ovsb-trans: 240ms ease;
|
||||||
@ -27,9 +37,18 @@
|
|||||||
|
|
||||||
.dark,
|
.dark,
|
||||||
[data-theme="dark"] {
|
[data-theme="dark"] {
|
||||||
--ovsb-track-bg: rgba(148, 163, 184, 0.28);
|
/* Légèrement plus visible sur fond sombre mais toujours discret */
|
||||||
--ovsb-thumb-bg: rgba(226, 232, 240, 0.72);
|
--ovsb-track-bg: color-mix(in oklab, var(--border, rgba(148,163,184,0.35)) 22%, transparent);
|
||||||
--ovsb-thumb-bg-active: rgba(241, 245, 249, 0.92);
|
--ovsb-thumb-bg: color-mix(
|
||||||
|
in oklab,
|
||||||
|
var(--scrollbar-thumb, rgba(226,232,240,0.62)) 85%,
|
||||||
|
color-mix(in oklab, var(--brand-700, var(--brand, #94a3b8)) 24%, transparent) 15%
|
||||||
|
);
|
||||||
|
--ovsb-thumb-bg-active: color-mix(
|
||||||
|
in oklab,
|
||||||
|
var(--scrollbar-thumb, rgba(226,232,240,0.72)) 70%,
|
||||||
|
color-mix(in oklab, var(--brand-700, var(--brand, #94a3b8)) 45%, transparent) 30%
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reduced motion */
|
/* Reduced motion */
|
||||||
|
|||||||
@ -11,6 +11,9 @@
|
|||||||
--transition-fast: 120ms ease;
|
--transition-fast: 120ms ease;
|
||||||
--transition-slow: 200ms ease;
|
--transition-slow: 200ms ease;
|
||||||
|
|
||||||
|
/* UI Font (fallback default) */
|
||||||
|
--font-ui: system-ui, -apple-system, "Segoe UI", Roboto, Arial, "Helvetica Neue", Helvetica, sans-serif;
|
||||||
|
|
||||||
/* Focus */
|
/* Focus */
|
||||||
--focus-ring-size: 2px;
|
--focus-ring-size: 2px;
|
||||||
--focus-ring-offset: 2px;
|
--focus-ring-offset: 2px;
|
||||||
@ -87,6 +90,9 @@ html.dark[data-theme] {
|
|||||||
html:not(.dark)[data-theme="light"] {
|
html:not(.dark)[data-theme="light"] {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
|
|
||||||
|
/* Fonts — Pure White */
|
||||||
|
--font-ui: Arial, "Helvetica Neue", Helvetica, "Segoe UI", sans-serif;
|
||||||
|
|
||||||
/* Backgrounds */
|
/* Backgrounds */
|
||||||
--bg: #ffffff;
|
--bg: #ffffff;
|
||||||
--bg-main: #f7f7f7;
|
--bg-main: #f7f7f7;
|
||||||
@ -153,6 +159,9 @@ html:not(.dark)[data-theme="light"] {
|
|||||||
html.dark[data-theme="dark"] {
|
html.dark[data-theme="dark"] {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
|
|
||||||
|
/* Fonts — default dark */
|
||||||
|
--font-ui: "Segoe UI", Roboto, Arial, sans-serif;
|
||||||
|
|
||||||
/* Backgrounds */
|
/* Backgrounds */
|
||||||
--bg: #0b1220;
|
--bg: #0b1220;
|
||||||
--bg-main: #111827;
|
--bg-main: #111827;
|
||||||
@ -218,12 +227,177 @@ html.dark[data-theme="dark"] {
|
|||||||
--md-quote-bg: color-mix(in oklab, var(--surface-2) 88%, black 0%);
|
--md-quote-bg: color-mix(in oklab, var(--surface-2) 88%, black 0%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
THÈME BLUE - LIGHT
|
||||||
|
============================================================================ */
|
||||||
|
html:not(.dark)[data-theme="blue"] {
|
||||||
|
color-scheme: light;
|
||||||
|
|
||||||
|
/* Fonts — Blue */
|
||||||
|
--font-ui: "Segoe UI", Roboto, Arial, sans-serif;
|
||||||
|
|
||||||
|
/* Backgrounds */
|
||||||
|
--bg: #ffffff;
|
||||||
|
--bg-main: #f7faff;
|
||||||
|
--bg-muted: #eef2ff;
|
||||||
|
--card: #ffffff;
|
||||||
|
--card-bg: #ffffff;
|
||||||
|
--elevated: #ffffff;
|
||||||
|
--sidebar-bg: #f1f5ff;
|
||||||
|
--surface-1: #f1f5ff;
|
||||||
|
--surface-2: #e6edff;
|
||||||
|
|
||||||
|
/* Text */
|
||||||
|
--fg: #0f172a;
|
||||||
|
--text-main: #0f172a;
|
||||||
|
--text-muted: #475569;
|
||||||
|
--muted: #64748b;
|
||||||
|
|
||||||
|
/* Borders */
|
||||||
|
--border: #dbeafe;
|
||||||
|
|
||||||
|
/* Brand colors */
|
||||||
|
--primary: #2563eb;
|
||||||
|
--brand: #2563eb;
|
||||||
|
--brand-700: #1d4ed8;
|
||||||
|
--brand-800: #1e40af;
|
||||||
|
--secondary: #7c3aed;
|
||||||
|
--accent: #06b6d4;
|
||||||
|
|
||||||
|
/* Status */
|
||||||
|
--success: #16a34a;
|
||||||
|
--warning: #f59e0b;
|
||||||
|
--danger: #ef4444;
|
||||||
|
--info: #0ea5e9;
|
||||||
|
|
||||||
|
/* UI */
|
||||||
|
--chip-bg: #e2e8f0;
|
||||||
|
--link: #1d4ed8;
|
||||||
|
--link-hover: #1e40af;
|
||||||
|
--ring: #2563eb;
|
||||||
|
|
||||||
|
/* Shadows */
|
||||||
|
--shadow-color: rgba(15, 23, 42, 0.08);
|
||||||
|
--scrollbar-thumb: rgba(59, 130, 246, 0.35);
|
||||||
|
|
||||||
|
/* Editor */
|
||||||
|
--editor-bg: #ffffff;
|
||||||
|
--editor-fg: #0f172a;
|
||||||
|
--editor-selection: rgba(37, 99, 235, 0.2);
|
||||||
|
--editor-gutter-bg: #f1f5ff;
|
||||||
|
--editor-gutter-fg: #64748b;
|
||||||
|
--editor-cursor: #0f172a;
|
||||||
|
|
||||||
|
/* Markdown overrides */
|
||||||
|
--md-h1: #0f172a;
|
||||||
|
--md-h2: #1d4ed8;
|
||||||
|
--md-h3: #2563eb;
|
||||||
|
--md-h4: #7c3aed;
|
||||||
|
--md-h5: #475569;
|
||||||
|
--md-h6: #64748b;
|
||||||
|
--md-quote-bar: #2563eb;
|
||||||
|
--md-quote-bg: color-mix(in oklab, #e6edff 92%, white 0%);
|
||||||
|
--md-table-head-bg: color-mix(in oklab, #e6edff 90%, white 0%);
|
||||||
|
--md-table-row-alt: color-mix(in oklab, #f1f5ff 96%, white 0%);
|
||||||
|
--md-pre-bg: #f1f5ff;
|
||||||
|
--md-pre-border: #dbeafe;
|
||||||
|
--md-syntax-1: #ef4444;
|
||||||
|
--md-syntax-2: #7c3aed;
|
||||||
|
--md-syntax-3: #16a34a;
|
||||||
|
--md-syntax-4: #2563eb;
|
||||||
|
--md-syntax-5: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
THÈME BLUE - DARK
|
||||||
|
============================================================================ */
|
||||||
|
html.dark[data-theme="blue"] {
|
||||||
|
color-scheme: dark;
|
||||||
|
|
||||||
|
/* Fonts — Blue */
|
||||||
|
--font-ui: "Segoe UI", Roboto, Arial, sans-serif;
|
||||||
|
|
||||||
|
/* Backgrounds */
|
||||||
|
--bg: #0b1020;
|
||||||
|
--bg-main: #0b1020;
|
||||||
|
--bg-muted: #0f1a2e;
|
||||||
|
--card: #0f1a2e;
|
||||||
|
--card-bg: #0f1a2e;
|
||||||
|
--elevated: #112340;
|
||||||
|
--sidebar-bg: #0b1020;
|
||||||
|
--surface-1: #0b1020;
|
||||||
|
--surface-2: #0f1a2e;
|
||||||
|
|
||||||
|
/* Text */
|
||||||
|
--fg: #dbeafe;
|
||||||
|
--text-main: #dbeafe;
|
||||||
|
--text-muted: #93c5fd;
|
||||||
|
--muted: #93c5fd;
|
||||||
|
|
||||||
|
/* Borders */
|
||||||
|
--border: #1e3a8a;
|
||||||
|
|
||||||
|
/* Brand colors */
|
||||||
|
--primary: #3b82f6;
|
||||||
|
--brand: #3b82f6;
|
||||||
|
--brand-700: #1d4ed8;
|
||||||
|
--brand-800: #1e40af;
|
||||||
|
--secondary: #a78bfa;
|
||||||
|
--accent: #22d3ee;
|
||||||
|
|
||||||
|
/* Status */
|
||||||
|
--success: #22c55e;
|
||||||
|
--warning: #f59e0b;
|
||||||
|
--danger: #f87171;
|
||||||
|
--info: #93c5fd;
|
||||||
|
|
||||||
|
/* UI */
|
||||||
|
--chip-bg: #0f1a2e;
|
||||||
|
--link: #93c5fd;
|
||||||
|
--link-hover: #bfdbfe;
|
||||||
|
--ring: #3b82f6;
|
||||||
|
|
||||||
|
/* Shadows */
|
||||||
|
--shadow-color: rgba(0, 0, 0, 0.5);
|
||||||
|
--scrollbar-thumb: rgba(147, 197, 253, 0.35);
|
||||||
|
|
||||||
|
/* Editor */
|
||||||
|
--editor-bg: #0f1a2e;
|
||||||
|
--editor-fg: #dbeafe;
|
||||||
|
--editor-selection: rgba(59, 130, 246, 0.25);
|
||||||
|
--editor-gutter-bg: #0f1a2e;
|
||||||
|
--editor-gutter-fg: #93c5fd;
|
||||||
|
--editor-cursor: #dbeafe;
|
||||||
|
|
||||||
|
/* Markdown overrides */
|
||||||
|
--md-h1: #dbeafe;
|
||||||
|
--md-h2: #93c5fd;
|
||||||
|
--md-h3: #60a5fa;
|
||||||
|
--md-h4: #a78bfa;
|
||||||
|
--md-h5: #93c5fd;
|
||||||
|
--md-h6: #6ea8ff;
|
||||||
|
--md-quote-bar: #3b82f6;
|
||||||
|
--md-quote-bg: color-mix(in oklab, #0f1a2e 92%, black 0%);
|
||||||
|
--md-table-head-bg: color-mix(in oklab, #0f1a2e 90%, black 0%);
|
||||||
|
--md-table-row-alt: color-mix(in oklab, #0b1020 85%, black 0%);
|
||||||
|
--md-pre-bg: #0f1a2e;
|
||||||
|
--md-pre-border: #1e3a8a;
|
||||||
|
--md-syntax-1: #ff7b72;
|
||||||
|
--md-syntax-2: #d2a8ff;
|
||||||
|
--md-syntax-3: #a5d6ff;
|
||||||
|
--md-syntax-4: #79c0ff;
|
||||||
|
--md-syntax-5: #93c5fd;
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================================================
|
/* ============================================================================
|
||||||
THÈME OBSIDIAN - LIGHT
|
THÈME OBSIDIAN - LIGHT
|
||||||
============================================================================ */
|
============================================================================ */
|
||||||
html:not(.dark)[data-theme="obsidian"] {
|
html:not(.dark)[data-theme="obsidian"] {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
|
|
||||||
|
/* Fonts — Obsidian */
|
||||||
|
--font-ui: Inter, "Segoe UI", Arial, Helvetica, sans-serif;
|
||||||
|
|
||||||
--bg: #fbfbfb;
|
--bg: #fbfbfb;
|
||||||
--bg-main: #fafaf8;
|
--bg-main: #fafaf8;
|
||||||
--bg-muted: #f5f3ef;
|
--bg-muted: #f5f3ef;
|
||||||
@ -291,6 +465,9 @@ html:not(.dark)[data-theme="obsidian"] {
|
|||||||
html.dark[data-theme="obsidian"] {
|
html.dark[data-theme="obsidian"] {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
|
|
||||||
|
/* Fonts — Obsidian */
|
||||||
|
--font-ui: Inter, "Segoe UI", Arial, Helvetica, sans-serif;
|
||||||
|
|
||||||
--bg: #1b1b1b;
|
--bg: #1b1b1b;
|
||||||
--bg-main: #1e1e1e;
|
--bg-main: #1e1e1e;
|
||||||
--bg-muted: #252525;
|
--bg-muted: #252525;
|
||||||
@ -360,6 +537,10 @@ html.dark[data-theme="obsidian"] {
|
|||||||
html:not(.dark)[data-theme="nord"] {
|
html:not(.dark)[data-theme="nord"] {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
|
|
||||||
|
/* Fonts — Nord */
|
||||||
|
--font-ui: "Fira Code", "JetBrains Mono", Consolas, ui-monospace, monospace;
|
||||||
|
--cm-font-family: "Fira Code", "JetBrains Mono", Consolas, ui-monospace, monospace;
|
||||||
|
|
||||||
--bg: #eceff4;
|
--bg: #eceff4;
|
||||||
--bg-main: #eceff4;
|
--bg-main: #eceff4;
|
||||||
--bg-muted: #e5e9f0;
|
--bg-muted: #e5e9f0;
|
||||||
@ -422,6 +603,10 @@ html:not(.dark)[data-theme="nord"] {
|
|||||||
html.dark[data-theme="nord"] {
|
html.dark[data-theme="nord"] {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
|
|
||||||
|
/* Fonts — Nord */
|
||||||
|
--font-ui: "Fira Code", "JetBrains Mono", Consolas, ui-monospace, monospace;
|
||||||
|
--cm-font-family: "Fira Code", "JetBrains Mono", Consolas, ui-monospace, monospace;
|
||||||
|
|
||||||
--bg: #2e3440;
|
--bg: #2e3440;
|
||||||
--bg-main: #2e3440;
|
--bg-main: #2e3440;
|
||||||
--bg-muted: #3b4252;
|
--bg-muted: #3b4252;
|
||||||
@ -491,6 +676,9 @@ html.dark[data-theme="nord"] {
|
|||||||
html:not(.dark)[data-theme="notion"] {
|
html:not(.dark)[data-theme="notion"] {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
|
|
||||||
|
/* Fonts — Notion */
|
||||||
|
--font-ui: Inter, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
|
||||||
|
|
||||||
--bg: #ffffff;
|
--bg: #ffffff;
|
||||||
--bg-main: #ffffff;
|
--bg-main: #ffffff;
|
||||||
--bg-muted: #f7f7f5;
|
--bg-muted: #f7f7f5;
|
||||||
@ -560,6 +748,9 @@ html:not(.dark)[data-theme="notion"] {
|
|||||||
html.dark[data-theme="notion"] {
|
html.dark[data-theme="notion"] {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
|
|
||||||
|
/* Fonts — Notion */
|
||||||
|
--font-ui: Inter, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
|
||||||
|
|
||||||
--bg: #171717;
|
--bg: #171717;
|
||||||
--bg-main: #171717;
|
--bg-main: #171717;
|
||||||
--bg-muted: #1b1b1b;
|
--bg-muted: #1b1b1b;
|
||||||
@ -629,6 +820,9 @@ html.dark[data-theme="notion"] {
|
|||||||
html:not(.dark)[data-theme="github"] {
|
html:not(.dark)[data-theme="github"] {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
|
|
||||||
|
/* Fonts — GitHub */
|
||||||
|
--font-ui: "Segoe UI", Arial, -apple-system, system-ui, sans-serif;
|
||||||
|
|
||||||
--bg: #ffffff;
|
--bg: #ffffff;
|
||||||
--bg-main: #ffffff;
|
--bg-main: #ffffff;
|
||||||
--bg-muted: #f6f8fa;
|
--bg-muted: #f6f8fa;
|
||||||
@ -698,6 +892,9 @@ html:not(.dark)[data-theme="github"] {
|
|||||||
html.dark[data-theme="github"] {
|
html.dark[data-theme="github"] {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
|
|
||||||
|
/* Fonts — GitHub */
|
||||||
|
--font-ui: "Segoe UI", Arial, -apple-system, system-ui, sans-serif;
|
||||||
|
|
||||||
--bg: #0d1117;
|
--bg: #0d1117;
|
||||||
--bg-main: #0d1117;
|
--bg-main: #0d1117;
|
||||||
--bg-muted: #161b22;
|
--bg-muted: #161b22;
|
||||||
@ -767,6 +964,9 @@ html.dark[data-theme="github"] {
|
|||||||
html:not(.dark)[data-theme="discord"] {
|
html:not(.dark)[data-theme="discord"] {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
|
|
||||||
|
/* Fonts — Discord */
|
||||||
|
--font-ui: "gg sans", Whitney, "Segoe UI", system-ui, -apple-system, Arial, sans-serif;
|
||||||
|
|
||||||
--bg: #f6f7f9;
|
--bg: #f6f7f9;
|
||||||
--bg-main: #f6f7f9;
|
--bg-main: #f6f7f9;
|
||||||
--bg-muted: #eef0f5;
|
--bg-muted: #eef0f5;
|
||||||
@ -836,6 +1036,9 @@ html:not(.dark)[data-theme="discord"] {
|
|||||||
html.dark[data-theme="discord"] {
|
html.dark[data-theme="discord"] {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
|
|
||||||
|
/* Fonts — Discord */
|
||||||
|
--font-ui: "gg sans", Whitney, "Segoe UI", system-ui, -apple-system, Arial, sans-serif;
|
||||||
|
|
||||||
--bg: #2b2d31;
|
--bg: #2b2d31;
|
||||||
--bg-main: #2b2d31;
|
--bg-main: #2b2d31;
|
||||||
--bg-muted: #232428;
|
--bg-muted: #232428;
|
||||||
@ -905,6 +1108,10 @@ html.dark[data-theme="discord"] {
|
|||||||
html:not(.dark)[data-theme="monokai"] {
|
html:not(.dark)[data-theme="monokai"] {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
|
|
||||||
|
/* Fonts — Monokai */
|
||||||
|
--font-ui: Menlo, Monaco, Consolas, "Fira Code", ui-monospace, monospace;
|
||||||
|
--cm-font-family: Menlo, Monaco, Consolas, "Fira Code", ui-monospace, monospace;
|
||||||
|
|
||||||
--bg: #fbfbf7;
|
--bg: #fbfbf7;
|
||||||
--bg-main: #fbfbf7;
|
--bg-main: #fbfbf7;
|
||||||
--bg-muted: #f2f2ea;
|
--bg-muted: #f2f2ea;
|
||||||
@ -974,6 +1181,10 @@ html:not(.dark)[data-theme="monokai"] {
|
|||||||
html.dark[data-theme="monokai"] {
|
html.dark[data-theme="monokai"] {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
|
|
||||||
|
/* Fonts — Monokai */
|
||||||
|
--font-ui: Menlo, Monaco, Consolas, "Fira Code", ui-monospace, monospace;
|
||||||
|
--cm-font-family: Menlo, Monaco, Consolas, "Fira Code", ui-monospace, monospace;
|
||||||
|
|
||||||
--bg: #272822;
|
--bg: #272822;
|
||||||
--bg-main: #272822;
|
--bg-main: #272822;
|
||||||
--bg-muted: #23241f;
|
--bg-muted: #23241f;
|
||||||
@ -1043,5 +1254,6 @@ html.dark[data-theme="monokai"] {
|
|||||||
body {
|
body {
|
||||||
background-color: var(--bg-main);
|
background-color: var(--bg-main);
|
||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
|
font-family: var(--font-ui, system-ui, -apple-system, "Segoe UI", Roboto, Arial, "Helvetica Neue", Helvetica, sans-serif);
|
||||||
transition: background-color var(--transition-base), color var(--transition-base);
|
transition: background-color var(--transition-base), color var(--transition-base);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,10 +10,10 @@ tags:
|
|||||||
- tag3
|
- tag3
|
||||||
aliases: []
|
aliases: []
|
||||||
status: en-cours
|
status: en-cours
|
||||||
publish: false
|
publish: true
|
||||||
favoris: false
|
favoris: true
|
||||||
template: false
|
template: false
|
||||||
task: false
|
task: true
|
||||||
archive: false
|
archive: false
|
||||||
draft: false
|
draft: false
|
||||||
private: false
|
private: false
|
||||||
|
|||||||
@ -10,14 +10,17 @@ tags:
|
|||||||
- tag3
|
- tag3
|
||||||
aliases: []
|
aliases: []
|
||||||
status: en-cours
|
status: en-cours
|
||||||
publish: false
|
publish: true
|
||||||
favoris: false
|
favoris: true
|
||||||
template: false
|
template: false
|
||||||
task: false
|
task: true
|
||||||
archive: false
|
archive: false
|
||||||
draft: false
|
draft: false
|
||||||
private: false
|
private: false
|
||||||
---
|
---
|
||||||
# Archived Note
|
# Archived Note
|
||||||
|
|
||||||
|
#bruno
|
||||||
|
|
||||||
|
|
||||||
This note was archived and moved to trash.
|
This note was archived and moved to trash.
|
||||||
|
|||||||
@ -17,6 +17,9 @@ archive: true
|
|||||||
draft: true
|
draft: true
|
||||||
private: true
|
private: true
|
||||||
---
|
---
|
||||||
|
Allo ceci est un tests
|
||||||
|
toto
|
||||||
|
|
||||||
# Test 1 Markdown
|
# Test 1 Markdown
|
||||||
|
|
||||||
## Titres
|
## Titres
|
||||||
|
|||||||
@ -5,18 +5,21 @@ creation_date: 2025-10-19T21:42:53-04:00
|
|||||||
modification_date: 2025-10-19T21:43:06-04:00
|
modification_date: 2025-10-19T21:43:06-04:00
|
||||||
catégorie: markdown
|
catégorie: markdown
|
||||||
tags:
|
tags:
|
||||||
- allo
|
- allo
|
||||||
aliases:
|
aliases:
|
||||||
- nouveau
|
- nouveau
|
||||||
status: en-cours
|
status: en-cours
|
||||||
publish: true
|
publish: true
|
||||||
favoris: true
|
favoris: false
|
||||||
template: true
|
template: true
|
||||||
task: true
|
task: true
|
||||||
archive: true
|
archive: true
|
||||||
draft: true
|
draft: true
|
||||||
private: true
|
private: true
|
||||||
---
|
---
|
||||||
|
Allo ceci est un tests
|
||||||
|
toto
|
||||||
|
|
||||||
# Test 1 Markdown
|
# Test 1 Markdown
|
||||||
|
|
||||||
## Titres
|
## Titres
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user