feat(konten): muat versi server & buang draft (poin 1)

Simpan source draft/file, tambah discardAndReload(), badge Draft aktif, dan tombol muat dari server.
This commit is contained in:
a2nr 2026-10-27 16:08:32 +07:00
parent 72d30351e5
commit 6971d0a3c3
2 changed files with 63 additions and 1 deletions

View File

@ -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<string | null>(null);
body = $state('');
draftId = $state<string | null>(null);
/** Asal konten yang sedang dimuat: draft server atau file published. */
source = $state<'draft' | 'file'>('file');
baseMtime = $state<number | null>(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;
}

View File

@ -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 @@
<!-- Save / Publish actions (shared across all header modes) -->
{#snippet saveActions()}
{#if mgr.activePath && !isAssetFile}
{#if mgr.source === 'draft'}
<span class="draft-badge" title="File ini sedang memakai draft tersimpan di server">Draft aktif</span>
<button
class="panel-btn discard-btn"
use:preserveFocus
onclick={() => mgr.discardAndReload()}
disabled={mgr.loading || mgr.saving || mgr.publishing}
title="Muat dari server (buang draft)"
>
{mgr.loading ? '⏳' : '↩️'}
</button>
{/if}
<button
class="panel-btn edit-btn"
use:preserveFocus
@ -1469,6 +1484,17 @@
.panel-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.panel-btn.save-btn { color: var(--color-text-muted); }
.panel-btn.publish-btn { color: var(--color-primary, #339af0); font-weight: 600; }
.draft-badge {
align-self: center;
font-size: 0.68rem;
font-weight: 600;
padding: 0.12rem 0.4rem;
border-radius: 999px;
background: rgba(245, 159, 0, 0.14);
border: 1px solid rgba(245, 159, 0, 0.45);
color: #b35c00;
white-space: nowrap;
}
.ie-progress {
font-size: 0.75rem;
color: var(--color-text-muted);