diff --git a/src/app/features/drawings/drawings-editor.component.html b/src/app/features/drawings/drawings-editor.component.html
index f7f9c26..a63b574 100644
--- a/src/app/features/drawings/drawings-editor.component.html
+++ b/src/app/features/drawings/drawings-editor.component.html
@@ -3,25 +3,27 @@
-
-
-
-
-
diff --git a/src/app/features/drawings/drawings-editor.component.ts b/src/app/features/drawings/drawings-editor.component.ts
index 999d845..64e4e4d 100644
--- a/src/app/features/drawings/drawings-editor.component.ts
+++ b/src/app/features/drawings/drawings-editor.component.ts
@@ -16,7 +16,7 @@ import {
signal
} from '@angular/core';
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 { ExcalidrawIoService } from './excalidraw-io.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 {
@Input() path: string = '';
+ @Input() showInlineActions: boolean = true;
@ViewChild('editorEl', { static: false }) editorEl?: ElementRef
void;
@@ -628,42 +629,76 @@ export class DrawingsEditorComponent implements OnInit, AfterViewInit, OnDestroy
}
});
- // Subscription 2: Handle auto-saving (disabled temporarily)
- // this.saveSub = sceneChange$.pipe(
- // distinctUntilChanged((prev, curr) => prev.hash === curr.hash),
- // debounceTime(2000),
- // filter(({ hash }) => this.lastSavedHash !== hash),
- // filter(() => !this.isSaving()),
- // tap(({ hash }) => {
- // console.log('💾 Autosaving... hash:', hash.substring(0, 10));
- // this.isSaving.set(true);
- // this.error.set(null);
- // }),
- // switchMap(({ scene, hash }) =>
- // this.files.put(this.path, scene).pipe(
- // tap(async () => {
- // console.log('✅ Autosave successful', { newHash: hash.substring(0, 10) });
- // 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('❌ Save error:', e);
- // const status = e?.status ?? e?.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. Veuillez réessayer.');
- // }
- // this.isSaving.set(false);
- // return of({ rev: '' } as any);
- // })
- // )
- // )
- // ).subscribe();
+ // Subscription 2: Natural autosave after inactivity (idle-based)
+ const triggers$ = fromEvent(host, 'pointermove').pipe(
+ mergeWith(
+ fromEvent(document, 'keydown'),
+ fromEvent(host, 'wheel'),
+ sceneChange$.pipe(map(() => null))
+ ),
+ debounceTime(7000),
+ filter(() => this.dirty() && !this.isSaving())
+ );
+
+ this.saveSub = triggers$.pipe(
+ switchMap(() => {
+ const scene = this.scene();
+ if (!scene) return of(null);
+ const hash = this.hashScene(scene);
+ if (this.lastSavedHash === hash) return of(null);
+ this.isSaving.set(true);
+ this.error.set(null);
+ const isExMd = /\.excalidraw\.md$/i.test(this.path || '');
+
+ const save$ = isExMd
+ ? this.files.getText(this.path).pipe(
+ catchError(() => of('')),
+ switchMap((existing) => {
+ let fm = this.excalIo.extractFrontMatter(existing) || null;
+ if (fm) {
+ if (!/excalidraw-plugin\s*:/i.test(fm)) {
+ fm = fm.replace(/^---\s*\n/, `---\nexcalidraw-plugin: parsed\n`);
+ } else {
+ fm = fm.replace(/excalidraw-plugin\s*:\s*.*?(\r?\n)/i, `excalidraw-plugin: parsed$1`);
+ }
+ const stamp = new Date().toISOString();
+ if (/^updated\s*:/mi.test(fm)) {
+ fm = fm.replace(/updated\s*:\s*.*?(\r?\n)/i, `updated: "${stamp}"$1`);
+ } 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');
@@ -725,6 +760,11 @@ export class DrawingsEditorComponent implements OnInit, AfterViewInit, OnDestroy
const target = `${base}.png`;
await firstValueFrom(this.files.putBinary(target, blob, 'image/png'));
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) {
console.error('Error exporting PNG:', err);
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`;
await firstValueFrom(this.files.putBinary(target, blob, 'image/svg+xml'));
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) {
console.error('Error exporting SVG:', err);
this.error.set('Erreur lors de l\'export en SVG');
diff --git a/src/app/features/sidebar/app-sidebar-drawer.component.ts b/src/app/features/sidebar/app-sidebar-drawer.component.ts
index be85412..b2ee1e8 100644
--- a/src/app/features/sidebar/app-sidebar-drawer.component.ts
+++ b/src/app/features/sidebar/app-sidebar-drawer.component.ts
@@ -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">
🧪 Markdown Playground
+
+
@@ -158,6 +168,8 @@ export class AppSidebarDrawerComponent {
@Output() tagSelected = new EventEmitter