diff --git a/src/routes/admin/posts/[id]/+page.server.ts b/src/routes/admin/posts/[id]/+page.server.ts index 19a0061..1c0170b 100644 --- a/src/routes/admin/posts/[id]/+page.server.ts +++ b/src/routes/admin/posts/[id]/+page.server.ts @@ -11,8 +11,8 @@ export const load: PageServerLoad = async ({ params, platform }) => { try { const post = await db.prepare( - 'SELECT id, title, slug, content, cover, category_id FROM posts WHERE id = ?' - ).bind(postId).first<{ id: number; title: string; slug: string; content: string; cover: string | null; category_id: number | null }>(); + 'SELECT id, title, slug, content, cover, category_id, published_at, updated_at FROM posts WHERE id = ?' + ).bind(postId).first<{ id: number; title: string; slug: string; content: string; cover: string | null; category_id: number | null; published_at?: string; updated_at?: string }>(); if (!post) { throw error(404, '文章不存在'); @@ -51,6 +51,8 @@ export const actions: Actions = { const cover = formData.get('cover')?.toString().trim() || null; const categoryId = formData.get('category_id') ? Number(formData.get('category_id')) : null; const selectedTags = formData.getAll('tags').map(t => Number(t)); + const publishedAt = formData.get('published_at')?.toString().trim(); + const updatedAt = formData.get('updated_at')?.toString().trim() || new Date().toISOString(); if (!title || !slug || !content) { return fail(400, { error: '标题、URL 别名和文章正文为必填项' }); @@ -58,11 +60,19 @@ export const actions: Actions = { try { // 更新文章表 - await db.prepare( - `UPDATE posts - SET title = ?, slug = ?, content = ?, cover = ?, category_id = ?, updated_at = CURRENT_TIMESTAMP - WHERE id = ?` - ).bind(title, slug, content, cover, categoryId, postId).run(); + if (publishedAt) { + await db.prepare( + `UPDATE posts + SET title = ?, slug = ?, content = ?, cover = ?, category_id = ?, published_at = ?, updated_at = ? + WHERE id = ?` + ).bind(title, slug, content, cover, categoryId, publishedAt, updatedAt, postId).run(); + } else { + await db.prepare( + `UPDATE posts + SET title = ?, slug = ?, content = ?, cover = ?, category_id = ?, updated_at = ? + WHERE id = ?` + ).bind(title, slug, content, cover, categoryId, updatedAt, postId).run(); + } // 清除原有标签并重新绑定 await db.prepare('DELETE FROM post_tags WHERE post_id = ?').bind(postId).run(); diff --git a/src/routes/admin/posts/[id]/+page.svelte b/src/routes/admin/posts/[id]/+page.svelte index 1562940..5365bc6 100644 --- a/src/routes/admin/posts/[id]/+page.svelte +++ b/src/routes/admin/posts/[id]/+page.svelte @@ -4,6 +4,7 @@ import { deserialize } from '$app/forms'; import MarkdownRenderer from '$lib/components/MarkdownRenderer.svelte'; import FeaturedImagePicker from '$lib/components/admin/FeaturedImagePicker.svelte'; + import { parseHugoMarkdown } from '$lib/hugoParser'; let { data, form }: { data: PageData; form: ActionData } = $props(); @@ -12,6 +13,10 @@ let content = $state(data.post.content || ''); let cover = $state(data.post.cover || ''); let mode = $state<'edit' | 'preview'>('edit'); + let publishedAt = $state(data.post.published_at || ''); + let updatedAt = $state(data.post.updated_at || ''); + let selectedCategoryId = $state(data.post.category_id ?? ''); + let fileInput: HTMLInputElement; let selectedTags = $state(data.selectedTagIds || []); let tagsList = $state(data.tags || []); @@ -139,12 +144,76 @@ showAdmonitionMenu = false; } + async function handleImportMarkdown(e: Event) { + const target = e.target as HTMLInputElement; + const file = target.files?.[0]; + if (!file) return; + + try { + const rawText = await file.text(); + const imported = parseHugoMarkdown(rawText, file.name); + + title = imported.title; + slug = imported.slug; + content = imported.content; + if (imported.cover) cover = imported.cover; + publishedAt = imported.published_at; + updatedAt = imported.updated_at; + + // 1. 分类处理:遭遇未知新分类名称时跳过,保留由用户抉择或处理 + if (imported.categories.length > 0) { + const targetName = imported.categories[0].trim().toLowerCase(); + const matchedCat = data.categories.find(c => c.name.trim().toLowerCase() === targetName); + if (matchedCat) { + selectedCategoryId = matchedCat.id; + } + } + + // 2. 标签处理:同步点选或即刻新增入参 + for (const tagName of imported.tags) { + const val = tagName.trim(); + if (!val) continue; + const existingTag = tagsList.find(t => t.name.toLowerCase() === val.toLowerCase()); + if (existingTag) { + if (!selectedTags.includes(existingTag.id)) { + selectedTags = [...selectedTags, existingTag.id]; + } + } else { + try { + const formData = new FormData(); + formData.append('name', val); + const res = await fetch('/admin/tags?/create', { + method: 'POST', + body: formData, + headers: { 'x-sveltekit-action': 'true' } + }); + const actionRes = deserialize(await res.text()) as any; + if (actionRes.type === 'success' && actionRes.data?.tag) { + tagsList = [...tagsList, actionRes.data.tag]; + selectedTags = [...selectedTags, actionRes.data.tag.id]; + } + } catch(e) { + console.error('自动拓展建构新标签错付:', e); + } + } + } + target.value = ''; + } catch (err) { + console.error('导入报错:', err); + alert('文档内部解析受断,核验是否带有完整及恰当规范的内容格式'); + } + } +

编辑

- ← 返回 +
+ + ← 返回 +
+ {#if form?.error}
@@ -153,6 +222,8 @@ {/if}
+ +
@@ -255,10 +326,10 @@ diff --git a/src/routes/admin/posts/new/+page.server.ts b/src/routes/admin/posts/new/+page.server.ts index 19a42f5..aa4986a 100644 --- a/src/routes/admin/posts/new/+page.server.ts +++ b/src/routes/admin/posts/new/+page.server.ts @@ -32,6 +32,8 @@ export const actions: Actions = { const cover = formData.get('cover')?.toString().trim() || null; const categoryId = formData.get('category_id') ? Number(formData.get('category_id')) : null; const selectedTags = formData.getAll('tags').map(t => Number(t)); + const publishedAt = formData.get('published_at')?.toString().trim() || new Date().toISOString(); + const updatedAt = formData.get('updated_at')?.toString().trim() || publishedAt; if (!title || !slug || !content) { return fail(400, { error: '标题、URL 别名和文章正文为必填项' }); @@ -40,9 +42,9 @@ export const actions: Actions = { try { const res = await db.prepare( `INSERT INTO posts (title, slug, content, cover, author_id, category_id, published_at, updated_at) - VALUES (?, ?, ?, ?, 1, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) + VALUES (?, ?, ?, ?, 1, ?, ?, ?) RETURNING id` - ).bind(title, slug, content, cover, categoryId).first<{ id: number }>(); + ).bind(title, slug, content, cover, categoryId, publishedAt, updatedAt).first<{ id: number }>(); const postId = res?.id; diff --git a/src/routes/admin/posts/new/+page.svelte b/src/routes/admin/posts/new/+page.svelte index ef305b2..5177038 100644 --- a/src/routes/admin/posts/new/+page.svelte +++ b/src/routes/admin/posts/new/+page.svelte @@ -4,6 +4,7 @@ import { deserialize } from '$app/forms'; import MarkdownRenderer from '$lib/components/MarkdownRenderer.svelte'; import FeaturedImagePicker from '$lib/components/admin/FeaturedImagePicker.svelte'; + import { parseHugoMarkdown } from '$lib/hugoParser'; let { data, form }: { data: PageData; form: ActionData } = $props(); @@ -12,6 +13,10 @@ let content = $state(''); let cover = $state(''); let mode = $state<'edit' | 'preview'>('edit'); + let publishedAt = $state(''); + let updatedAt = $state(''); + let selectedCategoryId = $state(''); + let fileInput: HTMLInputElement; let tagsList = $state(data.tags || []); @@ -145,12 +150,76 @@ showAdmonitionMenu = false; } + async function handleImportMarkdown(e: Event) { + const target = e.target as HTMLInputElement; + const file = target.files?.[0]; + if (!file) return; + + try { + const rawText = await file.text(); + const imported = parseHugoMarkdown(rawText, file.name); + + title = imported.title; + slug = imported.slug; + content = imported.content; + if (imported.cover) cover = imported.cover; + publishedAt = imported.published_at; + updatedAt = imported.updated_at; + + // 1. 分类匹配:严格遵守“遇到全新未知的分类名跳过,由用户决定”的规范 + if (imported.categories.length > 0) { + const targetName = imported.categories[0].trim().toLowerCase(); + const matchedCat = data.categories.find(c => c.name.trim().toLowerCase() === targetName); + if (matchedCat) { + selectedCategoryId = matchedCat.id; + } + } + + // 2. 标签匹配:库有即点取,不知则自主申请向库内补充建好 + for (const tagName of imported.tags) { + const val = tagName.trim(); + if (!val) continue; + const existingTag = tagsList.find(t => t.name.toLowerCase() === val.toLowerCase()); + if (existingTag) { + if (!selectedTags.includes(existingTag.id)) { + selectedTags = [...selectedTags, existingTag.id]; + } + } else { + try { + const formData = new FormData(); + formData.append('name', val); + const res = await fetch('/admin/tags?/create', { + method: 'POST', + body: formData, + headers: { 'x-sveltekit-action': 'true' } + }); + const actionRes = deserialize(await res.text()) as any; + if (actionRes.type === 'success' && actionRes.data?.tag) { + tagsList = [...tagsList, actionRes.data.tag]; + selectedTags = [...selectedTags, actionRes.data.tag.id]; + } + } catch(e) { + console.error('新建导入对应新标签时出错:', e); + } + } + } + target.value = ''; + } catch (err) { + console.error('导入转换出现错误:', err); + alert('解析文档内容受困,请稍后检查所填文书'); + } + } +

新{$page.data.aliases?.post || '文章'}

- ← 返回 +
+ + ← 返回 +
+ {#if form?.error}
@@ -159,6 +228,8 @@ {/if} + +
@@ -261,7 +332,7 @@