diff --git a/frontend/src/lib/services/contentEditor.svelte.ts b/frontend/src/lib/services/contentEditor.svelte.ts index 7e187a8..049233e 100644 --- a/frontend/src/lib/services/contentEditor.svelte.ts +++ b/frontend/src/lib/services/contentEditor.svelte.ts @@ -1,11 +1,13 @@ import { tick } from 'svelte'; -import { getDraft, saveDraft, publishDraft, type DraftResponse } from './contentEditor'; +import { getDraft, saveDraft, publishDraft, discardDraft, type DraftResponse } from './contentEditor'; import { renderMarkdownPreview } from './markdown'; export class ContentEditorManager { activePath = $state(null); body = $state(''); draftId = $state(null); + /** Asal konten yang sedang dimuat: draft server atau file published. */ + source = $state<'draft' | 'file'>('file'); baseMtime = $state(null); dirty = $state(false); loading = $state(false); @@ -60,6 +62,7 @@ export class ContentEditorManager { if (res.success) { this.body = res.body ?? ''; this.draftId = res.draft_id; + this.source = res.source === 'draft' ? 'draft' : 'file'; this.baseMtime = res.base_mtime ?? null; this.dirty = false; // Trigger preview @@ -118,6 +121,37 @@ export class ContentEditorManager { } } + /** + * Buang draft server lalu muat ulang file published terbaru. + * Hanya relevan saat `source === 'draft'`. + */ + async discardAndReload() { + if (!this.activePath || this.loading) return; + if (this.dirty && !window.confirm('Perubahan yang belum disimpan akan hilang. Muat versi server terbaru?')) { + return; + } + const draftId = this.draftId; + if (!draftId) { + await this.loadDraft(this.activePath); + return; + } + this.loading = true; + try { + const res = await discardDraft(draftId); + if (!res.success) { + this.showMessage('error', res.message || 'Gagal membuang draft'); + return; + } + await this.loadDraft(this.activePath); + this.showMessage('success', 'Draft dibuang — menampilkan versi server'); + } catch (err) { + console.error('[contentEditor] discardAndReload error:', err); + this.showMessage('error', 'Gagal terhubung ke server'); + } finally { + this.loading = false; + } + } + async handleSave() { if (!this.activePath || this.saving) return; this.saving = true; @@ -125,6 +159,7 @@ export class ContentEditorManager { const res = await saveDraft(this.activePath, this.body, this.baseMtime); if (res.success) { this.draftId = res.draft_id; + this.source = 'draft'; this.dirty = false; this.showMessage('success', 'Draft tersimpan'); } else { @@ -149,6 +184,7 @@ export class ContentEditorManager { return; } this.draftId = saveRes.draft_id; + this.source = 'draft'; this.dirty = false; } diff --git a/frontend/src/routes/teacher/content/+page.svelte b/frontend/src/routes/teacher/content/+page.svelte index e97a3de..2612147 100644 --- a/frontend/src/routes/teacher/content/+page.svelte +++ b/frontend/src/routes/teacher/content/+page.svelte @@ -223,6 +223,7 @@ mgr.activePath = path; mgr.body = ''; mgr.draftId = null; + mgr.source = 'file'; mgr.dirty = false; // Clear preview since assets don't have markdown mgr.previewHtml = ''; @@ -768,6 +769,7 @@ mgr.activePath = null; mgr.body = ''; mgr.draftId = null; + mgr.source = 'file'; mgr.previewSlides = []; mgr.previewActiveTabs = []; } @@ -780,6 +782,7 @@ mgr.activePath = null; mgr.body = ''; mgr.draftId = null; + mgr.source = 'file'; } } else { alert(forceRes.message || 'Gagal menghapus'); @@ -1038,6 +1041,18 @@ {#snippet saveActions()} {#if mgr.activePath && !isAssetFile} + {#if mgr.source === 'draft'} + Draft aktif + + {/if}