From ffceb519bebdeb0619f490158a091e2b98ab22f4 Mon Sep 17 00:00:00 2001 From: anggoro Date: Sun, 23 Aug 2026 08:18:02 +0700 Subject: [PATCH] feat(content-editor): konfirmasi ganda file navigasi kritis & hapus narrow sidebar - Tambah PROTECTED_BASENAMES (home.md, sub-home.md) dengan konfirmasi kedua saat rename/delete agar tidak merusak navigasi siswa (tidak bisa di-undo). Diteruskan ke renameEntry/deleteEntry di contentEditor.ts. - Hapus fitur featureSidebarNarrow + CSS terkait (tidak dipakai). - Footer & backend: penyesuaian rute/test content editor. --- frontend/src/lib/components/Footer.svelte | 10 +- frontend/src/lib/services/contentEditor.ts | 13 +- .../src/routes/teacher/content/+page.svelte | 135 +++----------- routes/content_editor.py | 37 +++- services/repositories.py | 15 +- services/teacher_auth.py | 16 +- services/tests/conftest.py | 19 ++ services/tests/test_content_editor_routes.py | 170 +++++++++++++++++- 8 files changed, 279 insertions(+), 136 deletions(-) diff --git a/frontend/src/lib/components/Footer.svelte b/frontend/src/lib/components/Footer.svelte index 331af3c..90fa689 100644 --- a/frontend/src/lib/components/Footer.svelte +++ b/frontend/src/lib/components/Footer.svelte @@ -19,14 +19,8 @@ color: var(--color-text-muted); margin-top: auto; } - .teacher-link { - color: var(--color-primary); - text-decoration: none; - font-weight: 500; - } - .teacher-link:hover { - text-decoration: underline; - } + /* Progress link moved to navbar — style tidak lagi dipakai */ + .version-badge { display: inline-block; font-size: 0.65rem; diff --git a/frontend/src/lib/services/contentEditor.ts b/frontend/src/lib/services/contentEditor.ts index 74d4d97..ce2de00 100644 --- a/frontend/src/lib/services/contentEditor.ts +++ b/frontend/src/lib/services/contentEditor.ts @@ -94,19 +94,24 @@ export async function createFile(path: string, template?: string) { return res.json(); } -export async function renameEntry(root: 'content' | 'assets', oldPath: string, newPath: string) { +export async function renameEntry( + root: 'content' | 'assets', + oldPath: string, + newPath: string, + confirmCritical = false, +) { const res = await fetch('/api/content/tree/rename', { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, credentials: 'include', - body: JSON.stringify({ root, old_path: oldPath, new_path: newPath }), + body: JSON.stringify({ root, old_path: oldPath, new_path: newPath, confirm_critical: confirmCritical }), }); return res.json(); } -export async function deleteEntry(root: 'content' | 'assets', path: string, force = false) { +export async function deleteEntry(root: 'content' | 'assets', path: string, confirmCritical = false, force = false) { const res = await fetch( - `/api/content/tree/entry?root=${root}&path=${encodeURIComponent(path)}&force=${force}`, + `/api/content/tree/entry?root=${root}&path=${encodeURIComponent(path)}&force=${force}&confirm_critical=${confirmCritical}`, { method: 'DELETE', credentials: 'include' }, ); return res.json(); diff --git a/frontend/src/routes/teacher/content/+page.svelte b/frontend/src/routes/teacher/content/+page.svelte index 3fde700..6c3040f 100644 --- a/frontend/src/routes/teacher/content/+page.svelte +++ b/frontend/src/routes/teacher/content/+page.svelte @@ -181,7 +181,6 @@ type FeatureTemplate = { id: string; label: string; group: 'konten' | 'penilaian'; snippet: string }; let featureSidebarOpen = $state(true); - let featureSidebarNarrow = $state(false); // Desktop: panel fitur terbuka by default. Mobile: default tersembunyi (drawer). $effect(() => { @@ -478,13 +477,25 @@ } } + // File navigasi kritis yang memerlukan konfirmasi ganda + const PROTECTED_BASENAMES = new Set(['home.md', 'sub-home.md']); + async function handleRename(oldPath: string) { const oldName = oldPath.split('/').pop() || oldPath; const newName = window.prompt('Nama baru:', oldName); if (!newName || newName === oldName) return; const parentPath = oldPath.substring(0, oldPath.lastIndexOf('/')); const newPath = parentPath ? `${parentPath}/${newName}` : newName; - const res = await renameEntry(mgr.treeRoot, oldPath, newPath); + + // P0-1: konfirmasi kedua untuk file navigasi kritis + if (PROTECTED_BASENAMES.has(oldName)) { + if (!window.confirm( + `"${oldName}" adalah file navigasi utama. Mengganti nama ini bisa ` + + `merusak halaman untuk semua siswa dan TIDAK BISA di-undo. Lanjutkan?` + )) return; + } + + const res = await renameEntry(mgr.treeRoot, oldPath, newPath, PROTECTED_BASENAMES.has(oldName)); if (res.success) { await loadTrees(); if (mgr.activePath === oldPath) { @@ -497,8 +508,14 @@ async function handleDelete(path: string) { const name = path.split('/').pop() || path; - if (!window.confirm(`Hapus "${name}"?`)) return; - const res = await deleteEntry(mgr.treeRoot, path); + // P0-1: konfirmasi kedua untuk file navigasi kritis + if (PROTECTED_BASENAMES.has(name)) { + if (!window.confirm( + `"${name}" adalah file navigasi utama. Menghapus ini bisa ` + + `merusak halaman untuk semua siswa dan TIDAK BISA di-undo. Lanjutkan?` + )) return; + } else if (!window.confirm(`Hapus "${name}"?`)) return; + const res = await deleteEntry(mgr.treeRoot, path, PROTECTED_BASENAMES.has(name)); if (res.success) { await loadTrees(); if (mgr.activePath === path) { @@ -857,9 +874,7 @@ {:else if dynTab.id === 'velxio'}
- @@ -893,7 +908,7 @@ {#if featureSidebarOpen && mgr.activePath && !isAssetFile} -