refactor: 在文章与页面后台表单中集成新的大纲与内容编辑器组件
continuous-integration/drone/push Build is passing

This commit is contained in:
seahi committed 2026-09-24 08:48:16 +08:00
1 parent 19d934065c
commit 609c6e18e2
4 files changed
+44 -124

No files matched your search

+11 -31
View File
@@ -2,12 +2,12 @@
import { resolve } from '$app/paths'; import { resolve } from '$app/paths';
import { page } from '$app/stores'; import { page } from '$app/stores';
import type { PageData, ActionData } from './$types'; import type { PageData, ActionData } from './$types';
import MarkdownRenderer from '$lib/components/MarkdownRenderer.svelte'; import ContentEditor from '$lib/components/admin/ContentEditor.svelte';
import MarkdownEditor from '$lib/components/admin/MarkdownEditor.svelte'; import { EditorOutline } from '$lib/editor/outline.svelte';
import TextInput from '$lib/components/admin/TextInput.svelte'; import TextInput from '$lib/components/admin/TextInput.svelte';
import Button from '$lib/components/admin/Button.svelte'; import Button from '$lib/components/admin/Button.svelte';
import Alert from '$lib/components/admin/Alert.svelte'; import Alert from '$lib/components/admin/Alert.svelte';
import Tabs from '$lib/components/admin/Tabs.svelte'; import EditorSidebarTabs from '$lib/components/admin/EditorSidebarTabs.svelte';
import ArticleAttachments from '$lib/components/admin/ArticleAttachments.svelte'; import ArticleAttachments from '$lib/components/admin/ArticleAttachments.svelte';
import { untrack } from 'svelte'; import { untrack } from 'svelte';
import { ArrowLeft } from 'lucide-svelte'; import { ArrowLeft } from 'lucide-svelte';
@@ -20,6 +20,7 @@
let content = $state(initialData.page.content); let content = $state(initialData.page.content);
let mode = $state<'edit' | 'preview'>('edit'); let mode = $state<'edit' | 'preview'>('edit');
const outline = new EditorOutline();
let sidebarTab = $state('settings'); let sidebarTab = $state('settings');
let sessionUploads = $state<{ url: string; key: string; filename: string }[]>([]); let sessionUploads = $state<{ url: string; key: string; filename: string }[]>([]);
</script> </script>
@@ -76,30 +77,18 @@
</div> </div>
<div class="editor-body"> <div class="editor-body">
{#if mode === 'edit'} <ContentEditor
<MarkdownEditor
bind:value={content} bind:value={content}
{mode}
{outline}
onUpload={(img) => (sessionUploads = [...sessionUploads, img])} onUpload={(img) => (sessionUploads = [...sessionUploads, img])}
/> />
{:else}
<div class="preview-area">
<MarkdownRenderer {content} />
</div>
{/if}
</div> </div>
</div> </div>
</div> </div>
<div class="form-sidebar"> <div class="form-sidebar">
<Tabs <EditorSidebarTabs {outline} bind:activeTab={sidebarTab} />
tabs={[
{ id: 'settings', label: '设置' },
{ id: 'media', label: '媒体附件' }
]}
bind:activeTab={sidebarTab}
fullWidth={true}
small={true}
/>
<!-- 设置面板始终挂载(仅切换显隐),确保别名字段在提交时始终存在 --> <!-- 设置面板始终挂载(仅切换显隐),确保别名字段在提交时始终存在 -->
<div class="sidebar-panel" class:is-hidden={sidebarTab !== 'settings'}> <div class="sidebar-panel" class:is-hidden={sidebarTab !== 'settings'}>
@@ -183,23 +172,15 @@
background: rgba(65, 155, 56, 0.1); background: rgba(65, 155, 56, 0.1);
} }
.preview-area {
padding: 20px;
border: 1px solid rgba(0, 0, 0, 0.12);
background: rgba(0, 0, 0, 0.015);
min-height: 400px;
max-height: 600px;
overflow-y: auto;
}
.form-grid { .form-grid {
display: grid; display: grid;
grid-template-columns: 1fr 280px; grid-template-columns: minmax(0, 1fr) 280px;
gap: 40px; gap: 40px;
align-items: start; align-items: start;
} }
.form-main { .form-main {
min-width: 0;
width: 100%; width: 100%;
} }
@@ -230,8 +211,7 @@
.form-sidebar { .form-sidebar {
position: static; position: static;
flex-direction: row; flex-direction: column;
flex-wrap: wrap;
gap: 20px; gap: 20px;
margin-top: 20px; margin-top: 20px;
} }
+11 -31
View File
@@ -2,12 +2,12 @@
import { resolve } from '$app/paths'; import { resolve } from '$app/paths';
import { page } from '$app/stores'; import { page } from '$app/stores';
import type { ActionData } from './$types'; import type { ActionData } from './$types';
import MarkdownRenderer from '$lib/components/MarkdownRenderer.svelte'; import ContentEditor from '$lib/components/admin/ContentEditor.svelte';
import MarkdownEditor from '$lib/components/admin/MarkdownEditor.svelte'; import { EditorOutline } from '$lib/editor/outline.svelte';
import TextInput from '$lib/components/admin/TextInput.svelte'; import TextInput from '$lib/components/admin/TextInput.svelte';
import Button from '$lib/components/admin/Button.svelte'; import Button from '$lib/components/admin/Button.svelte';
import Alert from '$lib/components/admin/Alert.svelte'; import Alert from '$lib/components/admin/Alert.svelte';
import Tabs from '$lib/components/admin/Tabs.svelte'; import EditorSidebarTabs from '$lib/components/admin/EditorSidebarTabs.svelte';
import ArticleAttachments from '$lib/components/admin/ArticleAttachments.svelte'; import ArticleAttachments from '$lib/components/admin/ArticleAttachments.svelte';
import { createSlugAutoHandler } from '$lib/utils/slugify'; import { createSlugAutoHandler } from '$lib/utils/slugify';
@@ -22,6 +22,7 @@
let isSlugUserModified = $state(false); let isSlugUserModified = $state(false);
let isGenerating = $state(false); let isGenerating = $state(false);
const outline = new EditorOutline();
let sidebarTab = $state('settings'); let sidebarTab = $state('settings');
let sessionUploads = $state<{ url: string; key: string; filename: string }[]>([]); let sessionUploads = $state<{ url: string; key: string; filename: string }[]>([]);
@@ -92,30 +93,18 @@
</div> </div>
<div class="editor-body"> <div class="editor-body">
{#if mode === 'edit'} <ContentEditor
<MarkdownEditor
bind:value={content} bind:value={content}
{mode}
{outline}
onUpload={(img) => (sessionUploads = [...sessionUploads, img])} onUpload={(img) => (sessionUploads = [...sessionUploads, img])}
/> />
{:else}
<div class="preview-area">
<MarkdownRenderer {content} />
</div>
{/if}
</div> </div>
</div> </div>
</div> </div>
<div class="form-sidebar"> <div class="form-sidebar">
<Tabs <EditorSidebarTabs {outline} bind:activeTab={sidebarTab} />
tabs={[
{ id: 'settings', label: '设置' },
{ id: 'media', label: '媒体附件' }
]}
bind:activeTab={sidebarTab}
fullWidth={true}
small={true}
/>
<!-- 设置面板始终挂载(仅切换显隐),确保别名字段在提交时始终存在 --> <!-- 设置面板始终挂载(仅切换显隐),确保别名字段在提交时始终存在 -->
<div class="sidebar-panel" class:is-hidden={sidebarTab !== 'settings'}> <div class="sidebar-panel" class:is-hidden={sidebarTab !== 'settings'}>
@@ -210,23 +199,15 @@
background: rgba(65, 155, 56, 0.1); background: rgba(65, 155, 56, 0.1);
} }
.preview-area {
padding: 20px;
border: 1px solid rgba(0, 0, 0, 0.12);
background: rgba(0, 0, 0, 0.015);
min-height: 400px;
max-height: 600px;
overflow-y: auto;
}
.form-grid { .form-grid {
display: grid; display: grid;
grid-template-columns: 1fr 280px; grid-template-columns: minmax(0, 1fr) 280px;
gap: 40px; gap: 40px;
align-items: start; align-items: start;
} }
.form-main { .form-main {
min-width: 0;
width: 100%; width: 100%;
} }
@@ -257,8 +238,7 @@
.form-sidebar { .form-sidebar {
position: static; position: static;
flex-direction: row; flex-direction: column;
flex-wrap: wrap;
gap: 20px; gap: 20px;
margin-top: 20px; margin-top: 20px;
} }
+11 -31
View File
@@ -5,14 +5,14 @@
import type { PageData, ActionData } from './$types'; import type { PageData, ActionData } from './$types';
import { deserialize } from '$app/forms'; import { deserialize } from '$app/forms';
import { Save, X } from 'lucide-svelte'; import { Save, X } from 'lucide-svelte';
import MarkdownRenderer from '$lib/components/MarkdownRenderer.svelte'; import ContentEditor from '$lib/components/admin/ContentEditor.svelte';
import MarkdownEditor from '$lib/components/admin/MarkdownEditor.svelte'; import { EditorOutline } from '$lib/editor/outline.svelte';
import FeaturedImagePicker from '$lib/components/admin/FeaturedImagePicker.svelte'; import FeaturedImagePicker from '$lib/components/admin/FeaturedImagePicker.svelte';
import SelectDropdown from '$lib/components/admin/SelectDropdown.svelte'; import SelectDropdown from '$lib/components/admin/SelectDropdown.svelte';
import TextInput from '$lib/components/admin/TextInput.svelte'; import TextInput from '$lib/components/admin/TextInput.svelte';
import Button from '$lib/components/admin/Button.svelte'; import Button from '$lib/components/admin/Button.svelte';
import Alert from '$lib/components/admin/Alert.svelte'; import Alert from '$lib/components/admin/Alert.svelte';
import Tabs from '$lib/components/admin/Tabs.svelte'; import EditorSidebarTabs from '$lib/components/admin/EditorSidebarTabs.svelte';
import ArticleAttachments from '$lib/components/admin/ArticleAttachments.svelte'; import ArticleAttachments from '$lib/components/admin/ArticleAttachments.svelte';
import { untrack } from 'svelte'; import { untrack } from 'svelte';
@@ -33,6 +33,7 @@
let tagsList = $state(initialData.tags || []); let tagsList = $state(initialData.tags || []);
let seriesList = $state(initialData.series || []); let seriesList = $state(initialData.series || []);
const outline = new EditorOutline();
let sidebarTab = $state('settings'); let sidebarTab = $state('settings');
let sessionUploads = $state<{ url: string; key: string; filename: string }[]>([]); let sessionUploads = $state<{ url: string; key: string; filename: string }[]>([]);
@@ -166,30 +167,18 @@
</div> </div>
<div class="editor-body"> <div class="editor-body">
{#if mode === 'edit'} <ContentEditor
<MarkdownEditor
bind:value={content} bind:value={content}
{mode}
{outline}
onUpload={(img) => (sessionUploads = [...sessionUploads, img])} onUpload={(img) => (sessionUploads = [...sessionUploads, img])}
/> />
{:else}
<div class="preview-area">
<MarkdownRenderer {content} />
</div>
{/if}
</div> </div>
</div> </div>
</div> </div>
<div class="form-sidebar"> <div class="form-sidebar">
<Tabs <EditorSidebarTabs {outline} bind:activeTab={sidebarTab} />
tabs={[
{ id: 'settings', label: '设置' },
{ id: 'media', label: '媒体附件' }
]}
bind:activeTab={sidebarTab}
fullWidth={true}
small={true}
/>
<!-- 设置面板始终挂载(仅切换显隐),确保别名/分类/系列/标签等字段在提交时始终存在 --> <!-- 设置面板始终挂载(仅切换显隐),确保别名/分类/系列/标签等字段在提交时始终存在 -->
<div class="sidebar-panel" class:is-hidden={sidebarTab !== 'settings'}> <div class="sidebar-panel" class:is-hidden={sidebarTab !== 'settings'}>
@@ -378,23 +367,15 @@
background: rgba(65, 155, 56, 0.1); background: rgba(65, 155, 56, 0.1);
} }
.preview-area {
padding: 20px;
border: 1px solid rgba(0, 0, 0, 0.12);
background: rgba(0, 0, 0, 0.015);
min-height: 400px;
max-height: 600px;
overflow-y: auto;
}
.form-grid { .form-grid {
display: grid; display: grid;
grid-template-columns: 1fr 280px; grid-template-columns: minmax(0, 1fr) 280px;
gap: 40px; gap: 40px;
align-items: start; align-items: start;
} }
.form-main { .form-main {
min-width: 0;
width: 100%; width: 100%;
} }
@@ -425,8 +406,7 @@
.form-sidebar { .form-sidebar {
position: static; position: static;
flex-direction: row; flex-direction: column;
flex-wrap: wrap;
gap: 16px; gap: 16px;
margin-top: 20px; margin-top: 20px;
} }
+11 -31
View File
@@ -6,14 +6,14 @@
import type { PageData, ActionData } from './$types'; import type { PageData, ActionData } from './$types';
import { deserialize } from '$app/forms'; import { deserialize } from '$app/forms';
import { ArrowLeft, X } from 'lucide-svelte'; import { ArrowLeft, X } from 'lucide-svelte';
import MarkdownRenderer from '$lib/components/MarkdownRenderer.svelte'; import ContentEditor from '$lib/components/admin/ContentEditor.svelte';
import MarkdownEditor from '$lib/components/admin/MarkdownEditor.svelte'; import { EditorOutline } from '$lib/editor/outline.svelte';
import FeaturedImagePicker from '$lib/components/admin/FeaturedImagePicker.svelte'; import FeaturedImagePicker from '$lib/components/admin/FeaturedImagePicker.svelte';
import SelectDropdown from '$lib/components/admin/SelectDropdown.svelte'; import SelectDropdown from '$lib/components/admin/SelectDropdown.svelte';
import TextInput from '$lib/components/admin/TextInput.svelte'; import TextInput from '$lib/components/admin/TextInput.svelte';
import Button from '$lib/components/admin/Button.svelte'; import Button from '$lib/components/admin/Button.svelte';
import Alert from '$lib/components/admin/Alert.svelte'; import Alert from '$lib/components/admin/Alert.svelte';
import Tabs from '$lib/components/admin/Tabs.svelte'; import EditorSidebarTabs from '$lib/components/admin/EditorSidebarTabs.svelte';
import ArticleAttachments from '$lib/components/admin/ArticleAttachments.svelte'; import ArticleAttachments from '$lib/components/admin/ArticleAttachments.svelte';
import { untrack } from 'svelte'; import { untrack } from 'svelte';
@@ -47,6 +47,7 @@
setIsGenerating: (val) => (isGenerating = val) setIsGenerating: (val) => (isGenerating = val)
}); });
const outline = new EditorOutline();
let sidebarTab = $state('settings'); let sidebarTab = $state('settings');
let sessionUploads = $state<{ url: string; key: string; filename: string }[]>([]); let sessionUploads = $state<{ url: string; key: string; filename: string }[]>([]);
@@ -185,30 +186,18 @@
</div> </div>
<div class="editor-body"> <div class="editor-body">
{#if mode === 'edit'} <ContentEditor
<MarkdownEditor
bind:value={content} bind:value={content}
{mode}
{outline}
onUpload={(img) => (sessionUploads = [...sessionUploads, img])} onUpload={(img) => (sessionUploads = [...sessionUploads, img])}
/> />
{:else}
<div class="preview-area">
<MarkdownRenderer {content} />
</div>
{/if}
</div> </div>
</div> </div>
</div> </div>
<div class="form-sidebar"> <div class="form-sidebar">
<Tabs <EditorSidebarTabs {outline} bind:activeTab={sidebarTab} />
tabs={[
{ id: 'settings', label: '设置' },
{ id: 'media', label: '媒体附件' }
]}
bind:activeTab={sidebarTab}
fullWidth={true}
small={true}
/>
<!-- 设置面板始终挂载(仅切换显隐),确保别名/分类/系列/标签等字段在提交时始终存在 --> <!-- 设置面板始终挂载(仅切换显隐),确保别名/分类/系列/标签等字段在提交时始终存在 -->
<div class="sidebar-panel" class:is-hidden={sidebarTab !== 'settings'}> <div class="sidebar-panel" class:is-hidden={sidebarTab !== 'settings'}>
@@ -397,23 +386,15 @@
background: rgba(65, 155, 56, 0.1); background: rgba(65, 155, 56, 0.1);
} }
.preview-area {
padding: 20px;
border: 1px solid rgba(0, 0, 0, 0.12);
background: rgba(0, 0, 0, 0.015);
min-height: 400px;
max-height: 600px;
overflow-y: auto;
}
.form-grid { .form-grid {
display: grid; display: grid;
grid-template-columns: 1fr 280px; grid-template-columns: minmax(0, 1fr) 280px;
gap: 40px; gap: 40px;
align-items: start; align-items: start;
} }
.form-main { .form-main {
min-width: 0;
width: 100%; width: 100%;
} }
@@ -444,8 +425,7 @@
.form-sidebar { .form-sidebar {
position: static; position: static;
flex-direction: row; flex-direction: column;
flex-wrap: wrap;
gap: 16px; gap: 16px;
margin-top: 20px; margin-top: 20px;
} }