feat: enhance Excalidraw editor UI and autosave functionality

- Redesigned editor toolbar with improved layout and accessibility attributes
- Added automatic save after 7 seconds of inactivity for better data protection
- Replaced emoji icons with SVG icons for consistent styling and accessibility
- Added success toasts for PNG/SVG exports with destination path
- Updated button styles and layout for better visual hierarchy
- Added aria-labels and improved keyboard navigation support
- Made action buttons optionally hideable via
This commit is contained in:
Bruno Charest 2025-10-28 22:25:40 -04:00
parent 5647b42d8a
commit b1f142c4f7
17 changed files with 335 additions and 284 deletions

View File

@ -3,25 +3,27 @@
<div class="flex items-center justify-between gap-2"> <div class="flex items-center justify-between gap-2">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<!-- Indicateur de sauvegarde (cliquable) --> <!-- Indicateur de sauvegarde (cliquable) -->
<button <button
type="button" *ngIf="showInlineActions"
class="flex items-center gap-2 px-3 py-2 rounded-md transition-colors" type="button"
class="btn btn-sm btn-ghost flex items-center gap-2"
[class.cursor-pointer]="!isLoading() && !isSaving()" [class.cursor-pointer]="!isLoading() && !isSaving()"
[class.cursor-wait]="isSaving()" [class.cursor-wait]="isSaving()"
[class.cursor-not-allowed]="isLoading()" [class.cursor-not-allowed]="isLoading()"
(click)="saveNow()" (click)="saveNow()"
[disabled]="isLoading()" [disabled]="isLoading()"
[attr.aria-label]="dirty() ? 'Sauver maintenant' : 'Déjà sauvegardé'"
title="{{dirty() ? 'Non sauvegardé - Cliquer pour sauvegarder (Ctrl+S)' : isSaving() ? 'Sauvegarde en cours...' : 'Sauvegardé'}}" title="{{dirty() ? 'Non sauvegardé - Cliquer pour sauvegarder (Ctrl+S)' : isSaving() ? 'Sauvegarde en cours...' : 'Sauvegardé'}}"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="20" width="16"
height="20" height="16"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
stroke-width="2" stroke-width="2"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
[class.text-red-500]="dirty() && !isSaving()" [class.text-red-500]="dirty() && !isSaving()"
[class.text-muted]="!dirty() && !isSaving()" [class.text-muted]="!dirty() && !isSaving()"
@ -32,33 +34,51 @@
<polyline points="17 21 17 13 7 13 7 21"></polyline> <polyline points="17 21 17 13 7 13 7 21"></polyline>
<polyline points="7 3 7 8 15 8"></polyline> <polyline points="7 3 7 8 15 8"></polyline>
</svg> </svg>
<span class="text-xs font-medium" [class.text-red-500]="dirty() && !isSaving()" [class.text-muted]="!dirty() && !isSaving()" [class.text-yellow-500]="isSaving()"> <span *ngIf="showInlineActions" class="text-xs font-medium" [class.text-red-500]="dirty() && !isSaving()" [class.text-muted]="!dirty() && !isSaving()" [class.text-yellow-500]="isSaving()">
{{isSaving() ? 'Sauvegarde...' : dirty() ? 'Non sauvegardé' : 'Sauvegardé'}} {{isSaving() ? 'Sauvegarde...' : dirty() ? 'Non sauvegardé' : 'Sauvegardé'}}
</span> </span>
</button> </button>
<button <button
type="button" *ngIf="showInlineActions"
class="btn btn-sm" type="button"
class="btn btn-sm flex items-center gap-2"
(click)="exportPNG()" (click)="exportPNG()"
[disabled]="isLoading() || isSaving() || !excalidrawReady" [disabled]="isLoading() || isSaving() || !excalidrawReady"
aria-label="Exporter en PNG"
> >
🖼️ Export PNG <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="18" height="14" rx="2" ry="2"></rect>
<circle cx="8" cy="8" r="2"></circle>
<path d="M21 17l-5-6-4 5-2-3-5 6"></path>
</svg>
Export PNG
</button> </button>
<button
type="button" <button
class="btn btn-sm" *ngIf="showInlineActions"
type="button"
class="btn btn-sm flex items-center gap-2"
(click)="exportSVG()" (click)="exportSVG()"
[disabled]="isLoading() || isSaving() || !excalidrawReady" [disabled]="isLoading() || isSaving() || !excalidrawReady"
aria-label="Exporter en SVG"
> >
🧩 Export SVG <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="7 10 12 15 17 10"/>
<line x1="12" y1="15" x2="12" y2="3"/>
</svg>
Export SVG
</button> </button>
<button
type="button" <button
class="btn btn-sm" *ngIf="showInlineActions"
type="button"
class="btn btn-sm flex items-center gap-2"
(click)="toggleFullscreen()" (click)="toggleFullscreen()"
[disabled]="isLoading()" [disabled]="isLoading()"
[title]="isFullscreen() ? 'Quitter le mode pleine écran' : 'Passer en mode pleine écran'" [title]="isFullscreen() ? 'Quitter le mode pleine écran' : 'Passer en mode pleine écran'"
aria-label="Basculer le mode plein écran"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -76,7 +96,7 @@
<polyline points="10,17 15,12 10,7"></polyline> <polyline points="10,17 15,12 10,7"></polyline>
<line x1="15" x2="3" y1="12" y2="12"></line> <line x1="15" x2="3" y1="12" y2="12"></line>
</svg> </svg>
{{isFullscreen() ? 'Quitter FS' : 'Pleine écran'}} {{isFullscreen() ? 'Quitter FS' : 'Plein écran'}}
</button> </button>
</div> </div>

View File

@ -16,7 +16,7 @@ import {
signal signal
} from '@angular/core'; } from '@angular/core';
import { firstValueFrom, fromEvent, of, Subscription, interval } from 'rxjs'; import { firstValueFrom, fromEvent, of, Subscription, interval } from 'rxjs';
import { debounceTime, distinctUntilChanged, map, switchMap, tap, catchError, filter } from 'rxjs/operators'; import { debounceTime, distinctUntilChanged, map, switchMap, tap, catchError, filter, mergeWith } from 'rxjs/operators';
import { DrawingsFileService, ExcalidrawScene } from './drawings-file.service'; import { DrawingsFileService, ExcalidrawScene } from './drawings-file.service';
import { ExcalidrawIoService } from './excalidraw-io.service'; import { ExcalidrawIoService } from './excalidraw-io.service';
import { DrawingsPreviewService } from './drawings-preview.service'; import { DrawingsPreviewService } from './drawings-preview.service';
@ -32,6 +32,7 @@ import { ThemeService } from '../../core/services/theme.service';
}) })
export class DrawingsEditorComponent implements OnInit, AfterViewInit, OnDestroy { export class DrawingsEditorComponent implements OnInit, AfterViewInit, OnDestroy {
@Input() path: string = ''; @Input() path: string = '';
@Input() showInlineActions: boolean = true;
@ViewChild('editorEl', { static: false }) editorEl?: ElementRef<HTMLElement & { @ViewChild('editorEl', { static: false }) editorEl?: ElementRef<HTMLElement & {
setScene: (scene: any) => void; setScene: (scene: any) => void;
@ -628,42 +629,76 @@ export class DrawingsEditorComponent implements OnInit, AfterViewInit, OnDestroy
} }
}); });
// Subscription 2: Handle auto-saving (disabled temporarily) // Subscription 2: Natural autosave after inactivity (idle-based)
// this.saveSub = sceneChange$.pipe( const triggers$ = fromEvent(host, 'pointermove').pipe(
// distinctUntilChanged((prev, curr) => prev.hash === curr.hash), mergeWith(
// debounceTime(2000), fromEvent(document, 'keydown'),
// filter(({ hash }) => this.lastSavedHash !== hash), fromEvent(host, 'wheel'),
// filter(() => !this.isSaving()), sceneChange$.pipe(map(() => null))
// tap(({ hash }) => { ),
// console.log('💾 Autosaving... hash:', hash.substring(0, 10)); debounceTime(7000),
// this.isSaving.set(true); filter(() => this.dirty() && !this.isSaving())
// this.error.set(null); );
// }),
// switchMap(({ scene, hash }) => this.saveSub = triggers$.pipe(
// this.files.put(this.path, scene).pipe( switchMap(() => {
// tap(async () => { const scene = this.scene();
// console.log('✅ Autosave successful', { newHash: hash.substring(0, 10) }); if (!scene) return of(null);
// this.lastSavedHash = hash; const hash = this.hashScene(scene);
// this.dirty.set(false); if (this.lastSavedHash === hash) return of(null);
// this.isSaving.set(false); this.isSaving.set(true);
// this.hasConflict.set(false); this.error.set(null);
// try { await this.savePreviewPNG(); } catch (e) { console.warn('PNG preview update failed', e); } const isExMd = /\.excalidraw\.md$/i.test(this.path || '');
// }),
// catchError((e) => { const save$ = isExMd
// console.error('❌ Save error:', e); ? this.files.getText(this.path).pipe(
// const status = e?.status ?? e?.statusCode; catchError(() => of('')),
// if (status === 409) { switchMap((existing) => {
// this.error.set('Conflit: le fichier a été modifié sur le disque.'); let fm = this.excalIo.extractFrontMatter(existing) || null;
// this.hasConflict.set(true); if (fm) {
// } else { if (!/excalidraw-plugin\s*:/i.test(fm)) {
// this.error.set('Erreur de sauvegarde. Veuillez réessayer.'); fm = fm.replace(/^---\s*\n/, `---\nexcalidraw-plugin: parsed\n`);
// } } else {
// this.isSaving.set(false); fm = fm.replace(/excalidraw-plugin\s*:\s*.*?(\r?\n)/i, `excalidraw-plugin: parsed$1`);
// return of({ rev: '' } as any); }
// }) const stamp = new Date().toISOString();
// ) if (/^updated\s*:/mi.test(fm)) {
// ) fm = fm.replace(/updated\s*:\s*.*?(\r?\n)/i, `updated: "${stamp}"$1`);
// ).subscribe(); } else {
fm = fm.replace(/^---\s*\n/, `---\nupdated: "${stamp}"\n`);
}
} else {
fm = `---\nexcalidraw-plugin: parsed\nupdated: "${new Date().toISOString()}"\n---`;
}
const md = this.excalIo.toObsidianMd(scene as any, fm);
return this.files.putText(this.path, md);
})
)
: this.files.put(this.path, scene);
return save$.pipe(
tap(async () => {
this.lastSavedHash = hash;
this.dirty.set(false);
this.isSaving.set(false);
this.hasConflict.set(false);
try { await this.savePreviewPNG(); } catch (e) { console.warn('PNG preview update failed', e); }
}),
catchError((e) => {
console.error('❌ Autosave error:', e);
const status = (e as any)?.status ?? (e as any)?.statusCode;
if (status === 409) {
this.error.set('Conflit: le fichier a été modifié sur le disque.');
this.hasConflict.set(true);
} else {
this.error.set('Erreur de sauvegarde automatique.');
}
this.isSaving.set(false);
return of(null);
})
);
})
).subscribe();
console.log('✓ Dirty check and Autosave subscriptions active'); console.log('✓ Dirty check and Autosave subscriptions active');
@ -725,6 +760,11 @@ export class DrawingsEditorComponent implements OnInit, AfterViewInit, OnDestroy
const target = `${base}.png`; const target = `${base}.png`;
await firstValueFrom(this.files.putBinary(target, blob, 'image/png')); await firstValueFrom(this.files.putBinary(target, blob, 'image/png'));
this.error.set(null); this.error.set(null);
try {
const norm = target.replace(/\\/g, '/');
const dir = norm.includes('/') ? norm.substring(0, norm.lastIndexOf('/')) : '/';
this.showToast(`PNG exporté avec succès dans ${dir}`, 'success');
} catch {}
} catch (err) { } catch (err) {
console.error('Error exporting PNG:', err); console.error('Error exporting PNG:', err);
this.error.set('Erreur lors de l\'export en PNG'); this.error.set('Erreur lors de l\'export en PNG');
@ -752,6 +792,11 @@ export class DrawingsEditorComponent implements OnInit, AfterViewInit, OnDestroy
const target = `${base}.svg`; const target = `${base}.svg`;
await firstValueFrom(this.files.putBinary(target, blob, 'image/svg+xml')); await firstValueFrom(this.files.putBinary(target, blob, 'image/svg+xml'));
this.error.set(null); this.error.set(null);
try {
const norm = target.replace(/\\/g, '/');
const dir = norm.includes('/') ? norm.substring(0, norm.lastIndexOf('/')) : '/';
this.showToast(`SVG exporté avec succès dans ${dir}`, 'success');
} catch {}
} catch (err) { } catch (err) {
console.error('Error exporting SVG:', err); console.error('Error exporting SVG:', err);
this.error.set('Erreur lors de l\'export en SVG'); this.error.set('Erreur lors de l\'export en SVG');

View File

@ -45,6 +45,16 @@ import { TrashExplorerComponent } from '../../layout/sidebar/trash/trash-explore
class="w-full text-left block text-sm px-3 py-2 rounded-lg hover:bg-surface1 dark:hover:bg-card active:bg-surface2 dark:active:bg-gray-700 text-main dark:text-main hover:text-main dark:hover:text-gray-100 transition-all active:scale-[0.98] transform"> class="w-full text-left block text-sm px-3 py-2 rounded-lg hover:bg-surface1 dark:hover:bg-card active:bg-surface2 dark:active:bg-gray-700 text-main dark:text-main hover:text-main dark:hover:text-gray-100 transition-all active:scale-[0.98] transform">
🧪 Markdown Playground 🧪 Markdown Playground
</button> </button>
<button
(click)="onApiTestsPanelClick()"
class="mt-1 w-full text-left block text-sm px-3 py-2 rounded-lg hover:bg-surface1 dark:hover:bg-card active:bg-surface2 dark:active:bg-gray-700 text-main dark:text-main hover:text-main dark:hover:text-gray-100 transition-all active:scale-[0.98] transform">
🔬 API Tests Panel
</button>
<button
(click)="onTestsExcalidrawClick()"
class="mt-1 w-full text-left block text-sm px-3 py-2 rounded-lg hover:bg-surface1 dark:hover:bg-card active:bg-surface2 dark:active:bg-gray-700 text-main dark:text-main hover:text-main dark:hover:text-gray-100 transition-all active:scale-[0.98] transform">
🎨 Test Excalidraw
</button>
</div> </div>
</section> </section>
@ -158,6 +168,8 @@ export class AppSidebarDrawerComponent {
@Output() tagSelected = new EventEmitter<string>(); @Output() tagSelected = new EventEmitter<string>();
@Output() quickLinkSelected = new EventEmitter<string>(); @Output() quickLinkSelected = new EventEmitter<string>();
@Output() markdownPlaygroundSelected = new EventEmitter<void>(); @Output() markdownPlaygroundSelected = new EventEmitter<void>();
@Output() testsPanelSelected = new EventEmitter<void>();
@Output() testsExcalidrawSelected = new EventEmitter<void>();
@Output() helpPageSelected = new EventEmitter<void>(); @Output() helpPageSelected = new EventEmitter<void>();
@Output() aboutSelected = new EventEmitter<void>(); @Output() aboutSelected = new EventEmitter<void>();
@ -189,6 +201,16 @@ export class AppSidebarDrawerComponent {
this.mobileNav.sidebarOpen.set(false); this.mobileNav.sidebarOpen.set(false);
} }
onApiTestsPanelClick(): void {
this.testsPanelSelected.emit();
this.mobileNav.sidebarOpen.set(false);
}
onTestsExcalidrawClick(): void {
this.testsExcalidrawSelected.emit();
this.mobileNav.sidebarOpen.set(false);
}
onHelpPageClick(): void { onHelpPageClick(): void {
this.helpPageSelected.emit(); this.helpPageSelected.emit();
this.mobileNav.sidebarOpen.set(false); this.mobileNav.sidebarOpen.set(false);

View File

@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, OnDestroy, OnInit, ViewChild, inject, signal } from '@angular/core'; import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild, inject, signal } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { DrawingsEditorComponent } from '../drawings/drawings-editor.component'; import { DrawingsEditorComponent } from '../drawings/drawings-editor.component';
import { DrawingsFileService } from '../drawings/drawings-file.service'; import { DrawingsFileService } from '../drawings/drawings-file.service';
@ -13,58 +13,75 @@ import { firstValueFrom } from 'rxjs';
imports: [CommonModule, DrawingsEditorComponent], imports: [CommonModule, DrawingsEditorComponent],
template: ` template: `
<div class="h-full flex flex-col gap-3"> <div class="h-full flex flex-col gap-3">
<div class="rounded-xl border border-border bg-card shadow-subtle"> <div class="sticky top-0 z-40">
<div class="flex items-center justify-between px-3 py-2"> <div class="rounded-2xl border border-border/60 bg-card/80 backdrop-blur-[2px] shadow-subtle max-w-full overflow-visible">
<div class="flex items-center gap-2"> <div class="flex flex-wrap items-center gap-2 px-3 pt-2">
<h2 class="text-sm font-semibold">Test Excalidraw</h2> <div class="flex items-center gap-2 min-w-0 flex-1 overflow-hidden">
<span class="text-[10px] font-semibold uppercase tracking-widest rounded-full bg-surface1 px-2 py-0.5 text-muted">Legacy Integration</span> <!-- <h2 class="text-sm font-semibold">Test Excalidraw</h2> -->
<ng-container *ngIf="currentPath(); else nofile"> <ng-container *ngIf="currentPath(); else nofile">
<span class="ml-2 text-xs text-muted select-all truncate max-w-[32rem]" [title]="currentPath()">{{ currentPath() }}</span> <span class="ml-2 text-xs text-muted truncate select-text" [title]="currentPath()">{{ currentPath() }}</span>
</ng-container> </ng-container>
<ng-template #nofile> <ng-template #nofile>
<span class="ml-2 text-xs text-muted">No file</span> <span class="ml-2 text-xs text-muted">Aucun fichier</span>
</ng-template> </ng-template>
<span class="ml-2 text-xs" [class.text-success]="!dirty()" [class.text-warning]="dirty()"> {{ dirty() ? 'Dirty' : 'Saved' }}</span> <div class="ml-auto flex items-center">
</div> <span
<div class="flex items-center gap-1"> class="inline-flex h-7 w-7 items-center justify-center rounded-full border border-border/60 bg-card/70 transition-colors"
<button type="button" class="btn btn-ghost btn-sm" (click)="createNew()" title="Nouveau"> [class.text-emerald-400]="!dirty()"
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg> [class.text-red-500]="dirty()"
<span class="ml-1 hidden sm:inline">Nouveau</span> [title]="dirty() ? 'Dessin non sauvegardé' : 'Dessin sauvegardé'"
</button> aria-label="Statut de sauvegarde"
<div class="relative"> >
<button type="button" class="btn btn-ghost btn-sm" (click)="toggleOpenPicker()" title="Ouvrir"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h5l2 2h9a2 2 0 0 1 2 2z"/></svg> <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
<span class="ml-1 hidden sm:inline">Ouvrir</span> <polyline points="17 21 17 13 7 13 7 21" />
</button> <polyline points="7 3 7 8 15 8" />
<div *ngIf="openPicker()" class="absolute right-0 mt-1 w-80 max-h-80 overflow-y-auto rounded-lg border border-border bg-card shadow-xl z-10"> </svg>
<div class="p-2 text-xs text-muted">Sélectionner un fichier *.excalidraw.md</div> <span class="sr-only">{{ dirty() ? 'Non sauvegardé' : 'Sauvegardé' }}</span>
<ul> </span>
<li *ngFor="let f of excalidrawFiles()">
<button type="button" class="w-full text-left px-3 py-1.5 text-sm hover:bg-surface1" (click)="openFile(f.filePath)">{{ f.filePath }}</button>
</li>
</ul>
</div> </div>
</div> </div>
<button type="button" class="btn btn-ghost btn-sm" (click)="closeFile()" [disabled]="!currentPath()" title="Fermer le fichier"> </div>
<svg width="18" height="18" 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> <!-- Row 1: Primary actions -->
<span class="ml-1 hidden sm:inline">Fermer</span> <div class="flex items-center justify-center gap-1 md:gap-2 px-3">
</button> <div class="flex items-center gap-1">
<div class="mx-1 h-5 w-px bg-border"></div> <button type="button" class="btn btn-ghost btn-sm" (click)="createNew()" title="Nouveau" aria-label="Nouveau">
<button type="button" class="btn btn-ghost btn-sm" (click)="saveDebounced()" [disabled]="!currentPath()" title="Enregistrer (Ctrl/Cmd+S)"> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h11l5 5v9a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg> <span class="ml-1 hidden md:inline">Nouveau</span>
<span class="ml-1 hidden sm:inline">Enregistrer</span>
</button>
<button type="button" class="btn btn-ghost btn-sm" (click)="saveAs()" title="Enregistrer sous">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M19 12H5"/></svg>
<span class="ml-1 hidden sm:inline">Enregistrer sous</span>
</button>
<div class="mx-1 h-5 w-px bg-border"></div>
<div class="relative">
<button type="button" class="btn btn-ghost btn-sm" (click)="toggleExport()" title="Exporter">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12"/><path d="M7 11l5 5 5-5"/><path d="M5 19h14"/></svg>
<span class="ml-1 hidden sm:inline">Exporter</span>
</button> </button>
<div *ngIf="openExport()" class="absolute right-0 mt-1 w-52 rounded-lg border border-border bg-card shadow-xl z-10"> <div class="relative">
<button type="button" class="btn btn-ghost btn-sm" (click)="toggleOpenPicker()" title="Ouvrir" aria-haspopup="menu" aria-expanded="{{openPicker()}}">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h5l2 2h9a2 2 0 0 1 2 2z"/></svg>
<span class="ml-1 hidden md:inline">Ouvrir</span>
</button>
<div *ngIf="openPicker()" class="absolute right-0 mt-1 w-72 max-w-[80vw] max-h-80 overflow-y-auto rounded-xl border border-border bg-card shadow-2xl z-50">
<div class="px-3 py-2 text-xs text-muted">Sélectionner un fichier *.excalidraw.md</div>
<ul class="divide-y divide-border/40">
<li *ngFor="let f of excalidrawFiles()">
<button type="button" class="w-full text-left px-3 py-2 text-sm hover:bg-surface1 truncate" (click)="openFile(f.filePath)" [title]="f.filePath">{{ f.filePath }}</button>
</li>
</ul>
</div>
</div>
<button type="button" class="btn btn-ghost btn-sm" (click)="closeFile()" [disabled]="!currentPath()" title="Fermer le fichier" aria-label="Fermer le fichier">
<svg width="18" height="18" 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>
<span class="ml-1 hidden md:inline">Fermer</span>
</button>
<button type="button" class="btn btn-ghost btn-sm" (click)="saveDebounced()" [disabled]="!currentPath()" title="Enregistrer (Ctrl/Cmd+S)" aria-label="Enregistrer">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h11l5 5v9a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
<span class="ml-1 hidden md:inline">Enregistrer</span>
</button>
</div>
</div>
<div class="h-px bg-border/70 mx-2 my-1"></div>
<!-- Row 2: Export only -->
<div class="flex items-center justify-center gap-1 md:gap-2 px-3 pb-2">
<div class="relative">
<button type="button" class="btn btn-ghost btn-sm" (click)="toggleExport()" title="Exporter" aria-haspopup="menu" aria-expanded="{{openExport()}}">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12"/><path d="M7 11l5 5 5-5"/><path d="M5 19h14"/></svg>
<span class="ml-1">Exporter</span>
</button>
<div *ngIf="openExport()" class="absolute right-0 mt-1 w-64 max-w-[75vw] rounded-xl border border-border bg-card shadow-2xl z-50">
<button class="w-full text-left px-3 py-2 text-sm hover:bg-surface1" (click)="exportPNG(true)">PNG (avec fond)</button> <button class="w-full text-left px-3 py-2 text-sm hover:bg-surface1" (click)="exportPNG(true)">PNG (avec fond)</button>
<button class="w-full text-left px-3 py-2 text-sm hover:bg-surface1" (click)="exportPNG(false)">PNG (sans fond)</button> <button class="w-full text-left px-3 py-2 text-sm hover:bg-surface1" (click)="exportPNG(false)">PNG (sans fond)</button>
<div class="h-px bg-border mx-2"></div> <div class="h-px bg-border mx-2"></div>
@ -74,20 +91,13 @@ import { firstValueFrom } from 'rxjs';
<button class="w-full text-left px-3 py-2 text-sm hover:bg-surface1" (click)="exportJSON()">JSON (télécharger)</button> <button class="w-full text-left px-3 py-2 text-sm hover:bg-surface1" (click)="exportJSON()">JSON (télécharger)</button>
</div> </div>
</div> </div>
<div class="mx-1 h-5 w-px bg-border"></div>
<label class="inline-flex items-center gap-2 text-xs text-muted select-none">
<input type="checkbox" [checked]="autosave()" (change)="setAutosave($any($event.target).checked)" /> Autosave 10s
</label>
<button type="button" class="btn btn-ghost btn-sm" (click)="toggleFullscreen()" title="Plein écran">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M16 3h3a2 2 0 0 1 2 2v3"/><path d="M8 21H5a2 2 0 0 1-2-2v-3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/></svg>
</button>
</div> </div>
</div> </div>
</div> </div>
<div class="flex-1 min-h-0"> <div class="flex-1 min-h-0">
<ng-container *ngIf="currentPath(); else noSelection"> <ng-container *ngIf="currentPath(); else noSelection">
<app-drawings-editor #editor [path]="currentPath()!"></app-drawings-editor> <app-drawings-editor #editor [path]="currentPath()!" [showInlineActions]="false"></app-drawings-editor>
</ng-container> </ng-container>
<ng-template #noSelection> <ng-template #noSelection>
<div class="h-full flex items-center justify-center rounded-xl border border-border bg-card text-muted"> <div class="h-full flex items-center justify-center rounded-xl border border-border bg-card text-muted">
@ -104,7 +114,7 @@ import { firstValueFrom } from 'rxjs';
</div> </div>
`, `,
}) })
export class TestExcalidrawPageComponent implements OnInit, OnDestroy { export class TestExcalidrawPageComponent implements OnInit, OnDestroy, AfterViewInit {
private readonly drawings = inject(DrawingsFileService); private readonly drawings = inject(DrawingsFileService);
private readonly excalIo = inject(ExcalidrawIoService); private readonly excalIo = inject(ExcalidrawIoService);
private readonly vault = inject(VaultService); private readonly vault = inject(VaultService);
@ -120,6 +130,7 @@ export class TestExcalidrawPageComponent implements OnInit, OnDestroy {
private autosaveTimer: any = null; private autosaveTimer: any = null;
private saveTimer: any = null; private saveTimer: any = null;
private dirtySyncInterval: any = null;
excalidrawFiles = signal<Array<{ filePath: string }>>([]); excalidrawFiles = signal<Array<{ filePath: string }>>([]);
@ -141,17 +152,20 @@ export class TestExcalidrawPageComponent implements OnInit, OnDestroy {
// build files list for picker // build files list for picker
this.refreshFileList(); this.refreshFileList();
// poll editor dirty state via save hash comparison using existing editor polling this.startDirtySync();
// here we just mirror the editor's internal dirty state by attempting a heuristic: }
const syncDirty = () => {
// not accessible directly; rely on presence of '*' in title not available. Skip. ngAfterViewInit(): void {
}; this.startDirtySync();
void syncDirty;
} }
ngOnDestroy(): void { ngOnDestroy(): void {
if (this.autosaveTimer) clearInterval(this.autosaveTimer); if (this.autosaveTimer) clearInterval(this.autosaveTimer);
if (this.saveTimer) clearTimeout(this.saveTimer); if (this.saveTimer) clearTimeout(this.saveTimer);
if (this.dirtySyncInterval) {
clearInterval(this.dirtySyncInterval);
this.dirtySyncInterval = null;
}
} }
refreshFileList(): void { refreshFileList(): void {
@ -191,6 +205,7 @@ export class TestExcalidrawPageComponent implements OnInit, OnDestroy {
this.currentPath.set(path); this.currentPath.set(path);
this.updateUrlFileParam(path); this.updateUrlFileParam(path);
this.openPicker.set(false); this.openPicker.set(false);
this.dirty.set(false);
// focus editor next tick // focus editor next tick
setTimeout(() => this.editor?.onExcalidrawReady?.(), 0); setTimeout(() => this.editor?.onExcalidrawReady?.(), 0);
} }
@ -199,6 +214,7 @@ export class TestExcalidrawPageComponent implements OnInit, OnDestroy {
this.currentPath.set(path); this.currentPath.set(path);
this.updateUrlFileParam(path); this.updateUrlFileParam(path);
this.openPicker.set(false); this.openPicker.set(false);
this.dirty.set(false);
} }
async saveDebounced(): Promise<void> { async saveDebounced(): Promise<void> {
@ -208,6 +224,17 @@ export class TestExcalidrawPageComponent implements OnInit, OnDestroy {
async save(): Promise<void> { async save(): Promise<void> {
await this.editor?.saveNow(); await this.editor?.saveNow();
this.dirty.set(this.editor?.dirty?.() ?? false);
}
async exportPNG(withBg: boolean): Promise<void> {
await this.editor?.exportPNG(withBg);
this.openExport.set(false);
}
async exportSVG(withBg: boolean): Promise<void> {
await this.editor?.exportSVG(withBg);
this.openExport.set(false);
} }
async saveAs(): Promise<void> { async saveAs(): Promise<void> {
@ -228,16 +255,6 @@ export class TestExcalidrawPageComponent implements OnInit, OnDestroy {
this.updateUrlFileParam(path); this.updateUrlFileParam(path);
} }
async exportPNG(withBg: boolean): Promise<void> {
await this.editor?.exportPNG(withBg);
this.openExport.set(false);
}
async exportSVG(withBg: boolean): Promise<void> {
await this.editor?.exportSVG(withBg);
this.openExport.set(false);
}
private updateUrlFileParam(filePath: string | null): void { private updateUrlFileParam(filePath: string | null): void {
try { try {
this.router.navigate([], { queryParams: { file: filePath || null }, queryParamsHandling: 'merge', preserveFragment: true }); this.router.navigate([], { queryParams: { file: filePath || null }, queryParamsHandling: 'merge', preserveFragment: true });
@ -247,8 +264,8 @@ export class TestExcalidrawPageComponent implements OnInit, OnDestroy {
closeFile(): void { closeFile(): void {
this.currentPath.set(null); this.currentPath.set(null);
this.openPicker.set(false); this.openPicker.set(false);
this.openExport.set(false);
this.updateUrlFileParam(null); this.updateUrlFileParam(null);
this.dirty.set(false);
} }
async exportJSON(): Promise<void> { async exportJSON(): Promise<void> {
@ -275,6 +292,7 @@ export class TestExcalidrawPageComponent implements OnInit, OnDestroy {
this.autosaveTimer = setInterval(() => { this.autosaveTimer = setInterval(() => {
if (this.currentPath()) { if (this.currentPath()) {
this.editor?.saveNow(); this.editor?.saveNow();
this.dirty.set(this.editor?.dirty?.() ?? false);
} }
}, 10000); }, 10000);
} }
@ -284,4 +302,14 @@ export class TestExcalidrawPageComponent implements OnInit, OnDestroy {
this.editor?.toggleFullscreen(); this.editor?.toggleFullscreen();
} }
private startDirtySync(): void {
if (this.dirtySyncInterval) return;
this.dirtySyncInterval = setInterval(() => {
const childDirty = this.editor?.dirty?.();
if (typeof childDirty === 'boolean' && childDirty !== this.dirty()) {
this.dirty.set(childDirty);
}
}, 800);
}
} }

View File

@ -279,6 +279,7 @@ import { InPageSearchOverlayComponent } from '../../shared/search/in-page-search
(quickLinkSelected)="onQuickLink($event)" (quickLinkSelected)="onQuickLink($event)"
(markdownPlaygroundSelected)="onMarkdownPlaygroundSelected()" (markdownPlaygroundSelected)="onMarkdownPlaygroundSelected()"
(testsPanelSelected)="onTestsPanelSelected()" (testsPanelSelected)="onTestsPanelSelected()"
(testsExcalidrawSelected)="onTestsExcalidrawSelected()"
(helpPageSelected)="onHelpPageSelected()" (helpPageSelected)="onHelpPageSelected()"
(aboutSelected)="onAboutSelected()" (aboutSelected)="onAboutSelected()"
></app-sidebar-drawer> ></app-sidebar-drawer>
@ -860,14 +861,23 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
} }
onMarkdownPlaygroundSelected(): void { onMarkdownPlaygroundSelected(): void {
if (this.responsive.isMobile()) {
this.mobileNav.setActiveTab('page');
}
this.markdownPlaygroundSelected.emit(); this.markdownPlaygroundSelected.emit();
} }
onTestsPanelSelected(): void { onTestsPanelSelected(): void {
if (this.responsive.isMobile()) {
this.mobileNav.setActiveTab('page');
}
this.testsPanelRequested.emit(); this.testsPanelRequested.emit();
} }
onTestsExcalidrawSelected(): void { onTestsExcalidrawSelected(): void {
if (this.responsive.isMobile()) {
this.mobileNav.setActiveTab('page');
}
this.testsExcalidrawRequested.emit(); this.testsExcalidrawRequested.emit();
} }

View File

@ -4,17 +4,19 @@
"type": "split", "type": "split",
"children": [ "children": [
{ {
"id": "b123bf2cfbaf681c", "id": "511c66a5c96502b2",
"type": "tabs", "type": "tabs",
"children": [ "children": [
{ {
"id": "a71c9cf3bbb86552", "id": "8309e5042a8fb85c",
"type": "leaf", "type": "leaf",
"state": { "state": {
"type": "empty", "type": "excalidraw",
"state": {}, "state": {
"icon": "lucide-file", "file": "Drawing-20251028-1452.excalidraw.md"
"title": "New tab" },
"icon": "excalidraw-icon",
"title": "Drawing-20251028-1452.excalidraw"
} }
} }
] ]
@ -174,8 +176,17 @@
"obsidian-excalidraw-plugin:New drawing": false "obsidian-excalidraw-plugin:New drawing": false
} }
}, },
"active": "a71c9cf3bbb86552", "active": "8309e5042a8fb85c",
"lastOpenFiles": [ "lastOpenFiles": [
"Drawing-20251028-1452.png",
"Drawing-20251028-1452.excalidraw.md.bak",
"Drawing-20251028-1452.excalidraw.md",
"dessin.svg",
"dessin.excalidraw.md",
"dessin.png",
"dessin.excalidraw.md.bak",
"dessin_05.svg",
"dessin_05.png",
"dessin_05.excalidraw.md", "dessin_05.excalidraw.md",
"dessin_03.excalidraw.md", "dessin_03.excalidraw.md",
"dessin_05.excalidraw.md.bak", "dessin_05.excalidraw.md.bak",
@ -189,9 +200,7 @@
"dessin-002.excalidraw.md.bak", "dessin-002.excalidraw.md.bak",
"Dessin_001.excalidraw.md", "Dessin_001.excalidraw.md",
"Dessin_001.excalidraw.md.tmp", "Dessin_001.excalidraw.md.tmp",
"Dessin_001.excalidraw.md.bak",
"Drawing 2025-10-28 11.11.59.excalidraw.md", "Drawing 2025-10-28 11.11.59.excalidraw.md",
"Drawing 2025-10-28 11.11.59.excalidraw.md.tmp",
"dessin-06.excalidraw.md", "dessin-06.excalidraw.md",
"Dessin-5.excalidraw.md", "Dessin-5.excalidraw.md",
"Drawing-20251027-2203.excalidraw.md", "Drawing-20251027-2203.excalidraw.md",
@ -209,8 +218,6 @@
"Drawing-20251027-1914.excalidraw.md", "Drawing-20251027-1914.excalidraw.md",
"Drawing-20251027-1705.excalidraw.md", "Drawing-20251027-1705.excalidraw.md",
"Drawing 2025-10-27 16.43.48.excalidraw.md", "Drawing 2025-10-27 16.43.48.excalidraw.md",
"test-drawing.excalidraw.md", "Untitled.canvas"
"Untitled.canvas",
"test.md"
] ]
} }

View File

@ -1,49 +0,0 @@
---
excalidraw-plugin: parsed
updated: "2025-10-28T15:45:35.205Z"
---
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==
You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'
# Excalidraw Data
## Text Elements
%%
## Drawing
```compressed-json
N4IgLgngDgpiBcIYA8DGBDANgSwCYCd0B3EAGhADcZ8BnbAewDsEAmcm+gV31TkXoBGdXNnSMAtC
gw4CxcVEycA5tmbkYmGAFsYjMDQQBtUHgQhGAeQAyABXQARAJL4A0gDEAbAH0AGvgCKAKIAnADCA
LIAFgBqZODQfEiYOFA0cOTICACMLAAc5BCsAAy5AHQArOREeGCRCADMLAAs5JEw2EqRYNkA7FkV5
GJKmghF7GD49ADWMKH0mPT4ZgDEWTBra3EC6KhTSpOcjLhzC0uIE2I0UOj4ut3kAGbYyQDKkCOIH
DJxNBPTMAB1Gp1eBsEC/SYzN4QD7g+amcgHTqMGA0AzwLLkejXVDYSDZIpjED7LhQRy4dGGAC6j0
IOnJCEYnGSiK4RxRaIQoEgsFYAF92DAYLhslkiuUek0muVyvVyFRaAxmPBci1KNQ6EwAHJMXjZYL
lDweIrS8qYkDYGj2DQwMDChAPLBpcgCNm4QKaHR6SkmEWIACCoVQWsmRSsPWiPgA4lGAI49RxuZD
BXBNOI8xI3SYkPk0kCcKC4dB2v1ZHoeLJGjz1Gv1VXkHCMKaM5mYBv0Xb2+COzBpAW+syB4Oh8OR
mPxxPJ1PphJmLP0EgZbIsYIFVjBFgDEDVXC1BosDytdqdbqgrdDD5EiH/E6LFYbdbpEDbXYkw7He
Z386ERhXG53OInled5Ei+BFwT+GYgV3EEwWvKEQLMMCRVZZRIg5dFzWxHY8UKDFCXIN8yQpIw8we
OkYAZeAmRZEADnZVF0W5WdQQFcEhVLOtgiaYJyx6OV1UVJgGlXQTNUYHVGD1eBgnqctVVyepRMta
1NBLB0nRgF03Q9bQ7nRGi23zQtiy7MsKyrWs6zVRtm2o1t207P0e2dEAoHoVR9CMQwxiKGlDEaQ9
xE3coqTzTB0F+OYtC0PESxsDy9BbWjfhuMAACFVBERglGSozdFwTKjlUXL4FAG0vTAKjzGsOwnFc
TxfACEIIhiQCO04dEilKIp6iaXIihXEoayNXJyjBJR0CgBpSiyXJjSUlgWCKatyhKLI2NS/AwH9f
BszadA/UM9Qjl2/aYEOuc9oXEBc0GKAoDeUyuXBSIFwBDRUHoHQXlQW5dAQCZOC08A2h0Mwi3wZt
yC+5J0FdQgwEWJi2NQbhbj0UJIjSgAVFjnxuOI0b2u5HDtLR0p2PZ6I/U4zAuX9rgx+4QGJ5mye0
QJTuuogDr9EB50XVn0dJ8m3GeTBoVhZCiZFvQOa0NwmDANx0FizA8PNNnRe0JW9BebAAC8+GWmG5
aq8mLBxXCCSJbX5fJgAlLhkUY7IzZJh3tCe7azoXPm8o99nybeSFZk/M4QFWR9NiDnWtGd98MLMG
nZc9i3vcgmApdA+EUOF9OFdD/5oL3UE469rQcZQHacCUZUQE0B4WeJjh8HSzgwCRhuCziHYwGwKh
Ai0ARhSLAQPmOkBZCUFQct0yrA6QEQB/nz0APs2jhTxEqowOYil+31elCsVQLvwBeN6n/vB5gHH6
HmF6MyQm1UAH4Szd+b68d5TejIWJyGlewgwir8f0b9b730fn/NisBGDhHoLgPgLkQZwOtHaN+XYU
HqGuvgcIjF0BKD4FPFA7ltqU1fKneAQMQakMWGAX6WATbqGQGQsAw9R64F+roZBmkWFsKBLUewNw
pgIKQUA1yQEYAAAkxC4Eng5Yk+A8AG2NkvS0RVspKC5vDTQfoaHkCQY6ZkDC8AwG2PgewHYZi4Bs
LcB41BdDSWwRaGgVh6CHRKhIkGlpHaoiNl47sfDXHOzAMWQJLjQFgASp5agViiCMEESCEAWguBpD
iFoEx2BL5JT/uQRgatEgAFU9B4j0cFQa5RxBimCrkapWRpRE2VjXfBTIl7Yl0K0zg7S4EJSgL3PJ
IAOmMANkgixPTdD2FEAsUqU9ri/BgFMrA9BSqgBoG9BJ3jDHFnQHlWBtwKAME6i8V+JYclVRImVE
AQ4Qz0DDBGaMcYExJhTGmah+BgZsVuHQQ2JVzlLxoH9eYeiLCd2ELw4B7AgXJB8KMKFkxkgAE04X
sU0Jg90695aXOAJtU5wo96knJCjQUaKzmYv0LtGA6V2g5XsIQWeWDglpFJUqf5gz1lcEwMcHYbRH
D10WDAAAWg/LQWzXoLiel5IJkKIJpXSjpclALlBEIWYVLKJVKRkQon4o41BAnlQKRPLsBjWYpEBh
8kGBTwbvOBliTujY+A0LYuRQp99pEdEiHXLoh8V4lTcBRQ+5KaBuo9V608U8kaRXDYow5MAiAUOp
m6W8kdlgPDTemuIxt4GIJgDoo1zlgmGxFS9CgWBgbZDYrGogYjc2Gr0WKuBWjHBaEIbm8lVEp4cq
INIhI+BbJ9IGS45lMB0WnxRDcNlnaClQDHaiUiWJlFzxeNOiwaa0jRtooIAAViO/Qy6po1rzfW6V
rl6BrttFYGAzcEA9BYAJM9Dx1332mhiGUVRgQICNJUEAbQPWnnKE0DwzrnhzrKnyPkQA
```
%%

View File

@ -0,0 +1,15 @@
---
excalidraw-plugin: parsed
updated: "2025-10-29T00:30:06.609Z"
---
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==
# Excalidraw Data
## Text Elements
%%
## Drawing
```compressed-json
N4IgLgngDgpiBcIYA8DGBDANgSwCYCd0B3EAGhADcZ8BnbAewDsEAmcm+gV31TkXoBGdXNnSMAtCgw4CxcVEycA5tmbkYmGAFsYjMDQQBtUHgQgA6hABeA3MgD6LdBQAKLAEoB5KwCFxACx9kJTJwaD4QEXQtJlxQ5AQAZgAWAAZyCFZU9JAiPDB/BAB2AA4AOmSATmqa2pryfxhsJX8wBEq2EDElTQQcmjB8egBrGABhekx6fDMAYgBGGEXF0IF0VGGlIc5GXAmpmcRBsRoodHxdNvIAM2xMTABlSF7EDhlQgaHR83zC+E7PiMYE8IC8QG9TORti1GDAaAZ4PNyPQzqhsJAEPNsuQtlwoABJXAIwwAXRuhB0hIQjE49yhXF2sPhCFAkFgrAAvuwYDA4ojkiUOliAKzCyrkKi0BjMRGpABsEuodCYADkmLxMcKSqlkqU5UiQNgaAARDQwMC8hDXLA0GDkAQM3AAUU0Oj0CJpdJAnCguHQFr58yK+rllSKRUqod15BwjGG1NpmBj9A2lvg1swtvIqlwKDM6FSIA5ZK6UCgT39fFANH89CI5g0qHoOgeqAuuitNrt4EaOjMfvw8fIqG4Fz0Y385zAABVwmY1jNh6PLviLVofOtNttdvtpmZjoxTudLqER/gx2BV9onbsAILnuuNdB8roPkhL88rtcAMTuj2eEQQnEH4XleWjfkwYDftEdyZIiIFftoEF6A82BWHwLA5GeoFrp4qLonBWJYcuehge4XAwnCCIGthiFaBW+BgPeQxEE+fKekmIC0aRa5PF84yTHuiALEsomniRl5ruROy4EyCIgNuwFcRJYF8UCIJgkB4mfjx2hqd8vysAhulaNOKBMTgSgyiAmjXFcym0NMPicGAYBMGYPqhOsYDYFQTpaAIvJ+gILwceQshKCojBKC62gnvAYVICIPnRbFbptAlibqMlqhKAA4tsBLsVlSXorlAAyqgwOcaXxYl3m+TA070JMLJhOyrxmqgPnuUuAzNrOHWJVMqZ8hmWYgNcQz0V1PUyuN3aYOgAy3t1jXNa1mX3FyICwIwACy9C5p2mbdntpoWt1aYLeoD74PtVHoEofCJSgUDTGAG4bLiMkIIMnDdm9H2tlgGHqMg72Mf5gW4K2uh8DdSAQx9PwFMa5zDIdx3pl2Nx3DAAASYi4KFJVbHgqHoVk2Y0D4Oa5Te6AhWm/3drm1q0mAqG5guxopqMuAuBc1zULoGo46dNPlfQz65SdE1Gu4cJoXLEsKzQ5FgP6quI0tAwuPQqgWvgfNEIwqN/CAMScLaoRaJz2C1XoCZeow0QRAAqno6KaLg4iYSwwriFi/slMHwrzMkp6QeZD00i7nEorocecAnyJ7QbUCeVtid7dzMALmnIBJ4wxqiFMIQ5+QZwDDAZdYPQlfVrWZvy2z/roAnO1QBcFAMDbDyzbyTuXkSLI7RcdBWAzrp1SVNBtpMvueC5wgI7j4KL/cAAaCDB3KqRlHKgfsFvmAAJp73KRRlMKyRYvMwrH8K3KaFdzqz6RY/wKAlg2HYjhnBuC8L4AIQRK6sx2raN+AYCp4kJAiYAUCh4fziu6e8MAfBNGisaQgkVrob2gTANaTAR5FxrFwTAex1iNHxFZaYMAABaLUtBt3YC3Cs+g2Hgi1oxHwjoyFV3BMoZ6tdcB012LlYkJYpruyVrsagqtQC6CZr7P6+AAbDhwFAdRmiQBuz7PAVmyIXKxj4MYq2GNqAqndro7sWgrH4EZszPkkDyTu2agTZo/hLKtCLryMq0VvwUheiVM06UaCeO8b4jKiU3LLViSVPuMAiBfS3I6XchwQCzGuCUa4lRrShHQgdI6MBnFqLVt2KwLC2oUCwADTkEpsApKxmUt2LjuF7RENFfEDjnojypEIihRACbhHwLGYYmds6IyIe/SqsIaqf0SV6GgbsoDzLhEYEs0xmiqAeGszw1xri2mWYnAQAArYh+h9noCgK08pBDJbFyOSc8qMA7LFBYIkZELzzTNR0f8RICpciGXgHKOUL8QCNG8RlO+wLuKSW0MxOsg0IiKW0jhJCITyKky9OTWGFoAWQvxfc9pFTEb4syW1fhVCzD4CUGsAAFJhb5AACFg8wkTsvmCUUgrLD7CgAJShCVkoWk5w6UMvQMynlfKWAsGSHKlg4p+W32FTtBF+kYB4Tmh6RMO1biaEQRyDkQA=
```
%%

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -1,21 +0,0 @@
---
excalidraw-plugin: parsed
updated: "2025-10-28T15:52:34.935Z"
---
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==
# Text Elements
# Drawing
```json
{"elements":[{"id":"FH8lzL5Mi3fE2hkUwBpjg","type":"ellipse","x":186.5,"y":194.5,"width":184,"height":143.5,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":2},"seed":980334059,"version":87,"versionNonce":1002008523,"isDeleted":false,"boundElements":[{"id":"RwNj5xmpJCXxtCVzpGsCc","type":"arrow"},{"id":"tVxfMX9-gzA1gIMHh04AP","type":"arrow"}],"updated":1761666749806,"link":null,"locked":false},{"id":"RwNj5xmpJCXxtCVzpGsCc","type":"arrow","x":186.5,"y":271.5,"width":183,"height":5,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":2},"seed":1981797643,"version":68,"versionNonce":624371685,"isDeleted":false,"boundElements":null,"updated":1761666744662,"link":null,"locked":false,"points":[[0,0],[183,-5]],"lastCommittedPoint":null,"startBinding":{"elementId":"FH8lzL5Mi3fE2hkUwBpjg","focus":0.03811374375552938,"gap":1},"endBinding":null,"startArrowhead":null,"endArrowhead":"arrow"},{"id":"tVxfMX9-gzA1gIMHh04AP","type":"arrow","x":277,"y":192,"width":1,"height":145,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":2},"seed":132945413,"version":89,"versionNonce":1141263659,"isDeleted":false,"boundElements":null,"updated":1761666749806,"link":null,"locked":false,"points":[[0,0],[1,145]],"lastCommittedPoint":null,"startBinding":{"elementId":"FH8lzL5Mi3fE2hkUwBpjg","focus":0.01073822548811009,"gap":2.5093386662695423},"endBinding":null,"startArrowhead":null,"endArrowhead":"arrow"}],"appState":{"showWelcomeScreen":true,"theme":"dark","collaborators":{},"currentChartType":"bar","currentItemBackgroundColor":"transparent","currentItemEndArrowhead":"arrow","currentItemFillStyle":"solid","currentItemFontFamily":1,"currentItemFontSize":20,"currentItemOpacity":100,"currentItemRoughness":1,"currentItemStartArrowhead":null,"currentItemStrokeColor":"#1e1e1e","currentItemRoundness":"round","currentItemStrokeStyle":"solid","currentItemStrokeWidth":2,"currentItemTextAlign":"left","cursorButton":"up","activeEmbeddable":null,"draggingElement":null,"editingElement":null,"editingGroupId":null,"editingLinearElement":null,"activeTool":{"type":"selection","customType":null,"locked":false,"lastActiveTool":null},"penMode":false,"penDetected":false,"errorMessage":null,"exportBackground":true,"exportScale":2,"exportEmbedScene":false,"exportWithDarkMode":false,"fileHandle":null,"gridSize":null,"isBindingEnabled":true,"defaultSidebarDockedPreference":false,"isLoading":false,"isResizing":false,"isRotating":false,"lastPointerDownWith":"mouse","multiElement":null,"name":"Untitled-2025-10-28-1152","contextMenu":null,"openMenu":null,"openPopup":null,"openSidebar":null,"openDialog":null,"pasteDialog":{"shown":false,"data":null},"previousSelectedElementIds":{"tVxfMX9-gzA1gIMHh04AP":true},"resizingElement":null,"scrolledOutside":false,"scrollX":0,"scrollY":0,"selectedElementIds":{},"selectedGroupIds":{},"selectedElementsAreBeingDragged":false,"selectionElement":null,"shouldCacheIgnoreZoom":false,"showStats":false,"startBoundElement":null,"suggestedBindings":[],"frameRendering":{"enabled":true,"clip":true,"name":true,"outline":true},"frameToHighlight":null,"editingFrame":null,"elementsToHighlight":null,"toast":null,"viewBackgroundColor":"#f8f9fa","zenModeEnabled":false,"zoom":{"value":1},"viewModeEnabled":false,"pendingImageElementId":null,"showHyperlinkPopup":false,"selectedLinearElement":null,"snapLines":[],"originSnapOffset":null,"objectsSnapModeEnabled":false,"offsetLeft":723,"offsetTop":155,"width":665,"height":546},"files":{}}
```
# Embedded Files
%%
```compressed-json
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
```
%%

View File

@ -0,0 +1,15 @@
---
excalidraw-plugin: parsed
updated: "2025-10-28T18:48:43.751Z"
---
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==
# Excalidraw Data
## Text Elements
%%
## Drawing
```compressed-json
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
```
%%

BIN
vault/dessin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

21
vault/dessin.svg Normal file
View File

@ -0,0 +1,21 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 270.3366543638838 194.5" width="540.6733087277676" height="389">
<!-- svg-source:excalidraw -->
<defs>
<style class="style-fonts">
@font-face {
font-family: "Virgil";
src: url("https://unpkg.com/@excalidraw/excalidraw@undefined/dist/excalidraw-assets/Virgil.woff2");
}
@font-face {
font-family: "Cascadia";
src: url("https://unpkg.com/@excalidraw/excalidraw@undefined/dist/excalidraw-assets/Cascadia.woff2");
}
@font-face {
font-family: "Assistant";
src: url("https://unpkg.com/@excalidraw/excalidraw@undefined/dist/excalidraw-assets/Assistant-Regular.woff2");
}
</style>
</defs>
<g stroke-linecap="round" transform="translate(10 10) rotate(0 122.75 87.25)"><path d="M123.66 0.36 C137.66 -0.96, 155.36 2.01, 169.1 5.73 C182.84 9.46, 195.35 15.56, 206.13 22.69 C216.9 29.82, 227.4 38.97, 233.76 48.53 C240.11 58.09, 243.03 69.45, 244.23 80.05 C245.44 90.65, 245.15 101.93, 241 112.12 C236.86 122.32, 228.55 132.89, 219.37 141.21 C210.19 149.52, 198.38 156.71, 185.91 162.01 C173.44 167.32, 158.96 171.21, 144.53 173.05 C130.11 174.88, 114.04 175.18, 99.35 173.01 C84.66 170.85, 68.91 165.59, 56.38 160.06 C43.84 154.52, 32.97 148.12, 24.15 139.8 C15.34 131.48, 7.46 120.29, 3.48 110.14 C-0.49 99.98, -1.42 89.19, 0.28 78.86 C1.98 68.54, 6.82 57.63, 13.69 48.19 C20.56 38.75, 30.3 29.35, 41.49 22.2 C52.67 15.04, 64.83 8.92, 80.79 5.28 C96.75 1.65, 125.77 0.58, 137.24 0.4 C148.71 0.22, 149.76 2.79, 149.59 4.2 M140.94 2.38 C155.18 2.39, 169.62 5.6, 182.49 10.33 C195.36 15.07, 208.87 22.62, 218.16 30.79 C227.44 38.96, 233.62 49.12, 238.19 59.34 C242.77 69.55, 246.29 81.58, 245.59 92.08 C244.89 102.58, 240.11 113.03, 234.01 122.35 C227.91 131.67, 219.45 140.36, 208.99 147.99 C198.52 155.62, 184.68 163.57, 171.22 168.14 C157.76 172.71, 142.91 175.03, 128.22 175.41 C113.53 175.78, 97.33 174.03, 83.11 170.38 C68.89 166.73, 54.05 160.21, 42.9 153.51 C31.76 146.81, 23.28 139.23, 16.25 130.17 C9.22 121.11, 2.62 109.78, 0.73 99.13 C-1.16 88.48, 1.51 76.33, 4.91 66.26 C8.3 56.19, 12.92 47.33, 21.1 38.7 C29.29 30.07, 41.85 20.82, 54 14.49 C66.15 8.16, 79.61 3.08, 94.01 0.71 C108.41 -1.66, 132.56 -0.23, 140.41 0.29 C148.27 0.82, 141.65 2.29, 141.12 3.85" stroke="#1e1e1e" stroke-width="2" fill="none"/></g><g stroke-linecap="round"><g transform="translate(67.5 25) rotate(0 2.75 73.75)"><path d="M-1.04 -0.25 C-0.35 24.08, 3.39 121.84, 4.66 146.55 M0.62 -1.42 C1.63 22.98, 5.98 123.03, 6.93 147.58" stroke="#1e1e1e" stroke-width="2" fill="none"/></g></g><mask/><g stroke-linecap="round"><g transform="translate(71.57179654033308 97.21709399425663) rotate(0 93.5 -1.75)"><path d="M-1.18 0.93 C29.85 0.14, 154.96 -2.98, 186.38 -3.66 M0.41 0.37 C31.8 0.28, 157.71 -1.26, 188.76 -2.21" stroke="#1e1e1e" stroke-width="2" fill="none"/></g></g><mask/></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -1,21 +0,0 @@
---
excalidraw-plugin: parsed
updated: "2025-10-28T16:00:51.235Z"
---
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==
# Text Elements
# Drawing
```json
{"elements":[{"id":"k815FAbmfYF5PnwUH8C-Y","type":"diamond","x":145,"y":168,"width":184,"height":162,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":2},"seed":1003701346,"version":78,"versionNonce":177304866,"isDeleted":false,"boundElements":null,"updated":1761667243295,"link":null,"locked":false},{"id":"-5PKdSwNmlYANRFbrw08a","type":"arrow","x":236.5,"y":170.5,"width":1,"height":154,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":2},"seed":1957331170,"version":45,"versionNonce":596418814,"isDeleted":false,"boundElements":null,"updated":1761667248128,"link":null,"locked":false,"points":[[0,0],[1,154]],"lastCommittedPoint":null,"startBinding":null,"endBinding":null,"startArrowhead":null,"endArrowhead":"arrow"}],"appState":{"showWelcomeScreen":true,"theme":"dark","collaborators":{},"currentChartType":"bar","currentItemBackgroundColor":"transparent","currentItemEndArrowhead":"arrow","currentItemFillStyle":"solid","currentItemFontFamily":1,"currentItemFontSize":20,"currentItemOpacity":100,"currentItemRoughness":1,"currentItemStartArrowhead":null,"currentItemStrokeColor":"#1e1e1e","currentItemRoundness":"round","currentItemStrokeStyle":"solid","currentItemStrokeWidth":2,"currentItemTextAlign":"left","cursorButton":"up","activeEmbeddable":null,"draggingElement":null,"editingElement":null,"editingGroupId":null,"editingLinearElement":null,"activeTool":{"type":"selection","customType":null,"locked":false,"lastActiveTool":null},"penMode":false,"penDetected":false,"errorMessage":null,"exportBackground":true,"exportScale":2,"exportEmbedScene":false,"exportWithDarkMode":false,"fileHandle":null,"gridSize":null,"isBindingEnabled":true,"defaultSidebarDockedPreference":false,"isLoading":false,"isResizing":false,"isRotating":false,"lastPointerDownWith":"mouse","multiElement":null,"name":"Untitled-2025-10-28-1200","contextMenu":null,"openMenu":null,"openPopup":null,"openSidebar":null,"openDialog":null,"pasteDialog":{"shown":false,"data":null},"previousSelectedElementIds":{"-5PKdSwNmlYANRFbrw08a":true},"resizingElement":null,"scrolledOutside":false,"scrollX":0,"scrollY":0,"selectedElementIds":{},"selectedGroupIds":{},"selectedElementsAreBeingDragged":false,"selectionElement":null,"shouldCacheIgnoreZoom":false,"showStats":false,"startBoundElement":null,"suggestedBindings":[],"frameRendering":{"enabled":true,"clip":true,"name":true,"outline":true},"frameToHighlight":null,"editingFrame":null,"elementsToHighlight":null,"toast":null,"viewBackgroundColor":"#f8f9fa","zenModeEnabled":false,"zoom":{"value":1},"viewModeEnabled":false,"pendingImageElementId":null,"showHyperlinkPopup":false,"selectedLinearElement":null,"snapLines":[],"originSnapOffset":null,"objectsSnapModeEnabled":false,"offsetLeft":723,"offsetTop":155,"width":665,"height":546},"files":{}}
```
# Embedded Files
%%
```compressed-json
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=
```
%%

View File

@ -1,21 +0,0 @@
---
excalidraw-plugin: parsed
updated: "2025-10-28T16:26:01.524Z"
---
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==
# Text Elements
# Drawing
```json
{"elements":[{"id":"GeEImoYg9respBPK3wmrF","type":"diamond","x":284.5,"y":175,"width":151,"height":168,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":2},"seed":1926584722,"version":64,"versionNonce":1088033746,"isDeleted":false,"boundElements":null,"updated":1761668759766,"link":null,"locked":false}],"appState":{"showWelcomeScreen":true,"theme":"dark","collaborators":{},"currentChartType":"bar","currentItemBackgroundColor":"transparent","currentItemEndArrowhead":"arrow","currentItemFillStyle":"solid","currentItemFontFamily":1,"currentItemFontSize":20,"currentItemOpacity":100,"currentItemRoughness":1,"currentItemStartArrowhead":null,"currentItemStrokeColor":"#1e1e1e","currentItemRoundness":"round","currentItemStrokeStyle":"solid","currentItemStrokeWidth":2,"currentItemTextAlign":"left","cursorButton":"up","activeEmbeddable":null,"draggingElement":null,"editingElement":null,"editingGroupId":null,"editingLinearElement":null,"activeTool":{"type":"selection","customType":null,"locked":false,"lastActiveTool":null},"penMode":false,"penDetected":false,"errorMessage":null,"exportBackground":true,"exportScale":2,"exportEmbedScene":false,"exportWithDarkMode":false,"fileHandle":null,"gridSize":null,"isBindingEnabled":true,"defaultSidebarDockedPreference":false,"isLoading":false,"isResizing":false,"isRotating":false,"lastPointerDownWith":"mouse","multiElement":null,"name":"Untitled-2025-10-28-1225","contextMenu":null,"openMenu":null,"openPopup":null,"openSidebar":null,"openDialog":null,"pasteDialog":{"shown":false,"data":null},"previousSelectedElementIds":{"GeEImoYg9respBPK3wmrF":true},"resizingElement":null,"scrolledOutside":false,"scrollX":0,"scrollY":0,"selectedElementIds":{},"selectedGroupIds":{},"selectedElementsAreBeingDragged":false,"selectionElement":null,"shouldCacheIgnoreZoom":false,"showStats":false,"startBoundElement":null,"suggestedBindings":[],"frameRendering":{"enabled":true,"clip":true,"name":true,"outline":true},"frameToHighlight":null,"editingFrame":null,"elementsToHighlight":null,"toast":null,"viewBackgroundColor":"#f8f9fa","zenModeEnabled":false,"zoom":{"value":1},"viewModeEnabled":false,"pendingImageElementId":null,"showHyperlinkPopup":false,"selectedLinearElement":null,"snapLines":[],"originSnapOffset":null,"objectsSnapModeEnabled":false,"offsetLeft":723,"offsetTop":155,"width":665,"height":546},"files":{}}
```
# Embedded Files
%%
```compressed-json
N4IgpgNmC2YHYBcDOIBcBtUBLAJmkA4mAKICS0A9gJoDmAnAE5hIAOAQgAoDSAzAO7QGAMRAAaEAgCeLMPhxYAhpTh5xADzQAmABwAWAHQBWcZLQBGAOzGQfXAgAW5w2fH2wWGvYTmAbNvEKcDRQaAAM4kgIDBQA1mAAwhQQFAz4AMRmYJmZYiAARgoAxjE00QCuKonJqagSDIGsCkyIuQBmWBAQAMpSIbVISbi5kdFxAOp2jqiaEVGxYD2SfSADEEPi5Z5wzCioLiAULEVYUuah4SClFGUspDi76AC64q31sHdocGWdG9cq20hdqApDItABfCJgMB4PZ0TQ+Qx6CyaGYgABuYAYSCwFDgaB8unEGKxOLgADlcYVZHtQtptKEeDwLLofOIsEgACKQMAIaFoVoKCBIMDiPJ/HDEKCwRC7L4/EA3HAKXkwyw+Mw+PxWOgWTXiNZwGKfb4QfUUYp81ACoVgMHPEAKFgsHrK6mgJD2Ch8MaQQoUWBdQpMeBoKJlEUSNywORNI3iP2dBRi+oIFJAiEgQplBjNBDxexNBAAFWk1PyTVyWZz8AQpF50DYRRK5UqSRS+CiDSOucr2dzdZgxBUAEEc163AoYQ6x3xe9XEAPoEIOt1emXVutM32a4uhLiEEIlB1THt49uF/W94gulgAF7UzQXKv9+sAeSOhROJ7M5zP89r9YAErXFsOzmH+L4wC6DAIKO0R8BOMJyqaW7/ouPSjAkbY1CAGRZPhc6QdAwEVDgAK7CALaqKhREYfMizLBu1HPju9Z0eMkxaBBrEwEWYBqLBaw0HitRQK03h/gMDBsGUCCpiJCosLkRQIFgGLENAeTQkqeR9Mh4g4PUNA0FgQSSjANbGvK0InKZNDmdK3ioPp4DyKpQQEOUtxISa4g2e5NAADKmWATQOZZzm+Q6hSqRiRYUEkaDAqW+DClAMWkr2kT+iWoKRfKyQWjC1rCvqCiRMOGVxQlEBWRAGYyHAACyFA4NSJURo1XK8jFlodX5Y4ME1OwKDQ1IufxLApAgjbFFcpGhgw4Z+WoU0wYGgoPita0IBpWk4IG8DtYKpXgKt00TA4HKxi1bX8idEbtFAAASgQ4HpUWlLgN73nVbJIGwpnyGZcBJlAMJhhGbUCt8CA3m1BQMBy5pxDgHBMK0mLwFS902v9gUUJOdm46d7KAcwd7E1aD3/cBCDKlT/UgBA5UIBwFCmbySNenAl1TCAlBlMKuTQLDWDhS0+UoaD0a1AAqogJzgwAtI+miGMrP6q9omsooYlb7vxCDDV8f0HI1JtlGbhzwOzLA3NbjXw2AiOO/AHKKMkNBm0ckRgB7goUN7qDup6fAiUzSr03VDVMGiOJC103K9RKUo7vcSWECQ5DUPQTCsJwvACMIi3hhm+eU2ZaeSy5SBBkk4OvrJ2J3dTeMrPXnQABphBEncQFQvcrMnKoS7WGchxmaVgCnnnXN56aQulo/V8go5gGw7hBByRljcVNPD8vpJj2bHrXBAODxEUbikMJKRgAAWgl0AkxGZ98C6yCv7MhZsOKJ9SwiGUYyzAVSAxUHZB49pXhKDAOTFQmIqagHgGDS0kN4xrCUqgdBIAZbUhwdcBABp8FLVtC8N4YB4rPQ8PYISXgzb+TskIChDDV5ICoTQuhTkXKplZmbeOYA+CzWbOKKo7ZahpFaNoVodABS5HvM1VqJBQa6T6gfW8z9M5okFOGcwGYBF8Fuso1B+926NWBjQcgo0SCrw+IAlYYdnqlgYAaGIdsHZt1OtPFOwVthhVXqfUGLBfHMDQE8cQKQPCmS6EE18rRWjCm4VFCgeQABWM9kAxMdEYocJjv4HHiYkwKYBxJoGRDwCJhSeTxSwWYQw1hbA4AcPiBErh3CeCcoYFkGYnqhMnmCIAA===
```
%%

View File

@ -1,15 +0,0 @@
---
excalidraw-plugin: parsed
updated: "2025-10-28T16:46:06.902Z"
---
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==
# Excalidraw Data
## Text Elements
%%
## Drawing
```compressed-json
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
```
%%

View File

@ -12,11 +12,6 @@ task: true
archive: true archive: true
draft: true draft: true
private: true private: true
color: "#EF4444"
tags:
- configuration
- tag4
- test
--- ---
# TEST # TEST