This commit is contained in:
1 parent
c91ef5aac9
commit
2b6578af92
5 files changed
+3
-389
No files matched your search
+1
-3
@@ -54,8 +54,6 @@
|
|||||||
"marked": "^18.0.9",
|
"marked": "^18.0.9",
|
||||||
"marked-highlight": "^2.2.4",
|
"marked-highlight": "^2.2.4",
|
||||||
"sanitize-html": "2.17.6",
|
"sanitize-html": "2.17.6",
|
||||||
"smol-toml": "^1.7.1",
|
"tocbot": "^4.36.8"
|
||||||
"tocbot": "^4.36.8",
|
|
||||||
"yaml": "^2.9.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Generated
+2
-13
@@ -45,15 +45,9 @@ importers:
|
|||||||
sanitize-html:
|
sanitize-html:
|
||||||
specifier: 2.17.6
|
specifier: 2.17.6
|
||||||
version: 2.17.6
|
version: 2.17.6
|
||||||
smol-toml:
|
|
||||||
specifier: ^1.7.1
|
|
||||||
version: 1.7.1
|
|
||||||
tocbot:
|
tocbot:
|
||||||
specifier: ^4.36.8
|
specifier: ^4.36.8
|
||||||
version: 4.36.8
|
version: 4.36.8
|
||||||
yaml:
|
|
||||||
specifier: ^2.9.0
|
|
||||||
version: 2.9.0
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@eslint/js':
|
'@eslint/js':
|
||||||
specifier: ^10.0.1
|
specifier: ^10.0.1
|
||||||
@@ -1631,10 +1625,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==}
|
resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
smol-toml@1.7.1:
|
|
||||||
resolution: {integrity: sha512-PPlsspAZ4jbMBu5DMFhfUGDQLu/vrL4SyBROVS37x8ynnVmFIs1VPBz1Co8Xks3TvpIaZXmU85y4DrQ+UyVFoQ==}
|
|
||||||
engines: {node: '>= 18'}
|
|
||||||
|
|
||||||
source-map-js@1.2.1:
|
source-map-js@1.2.1:
|
||||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -3456,8 +3446,6 @@ snapshots:
|
|||||||
mrmime: 2.0.1
|
mrmime: 2.0.1
|
||||||
totalist: 3.0.1
|
totalist: 3.0.1
|
||||||
|
|
||||||
smol-toml@1.7.1: {}
|
|
||||||
|
|
||||||
source-map-js@1.2.1: {}
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
stackback@0.0.2: {}
|
stackback@0.0.2: {}
|
||||||
@@ -3679,7 +3667,8 @@ snapshots:
|
|||||||
|
|
||||||
yaml@1.10.3: {}
|
yaml@1.10.3: {}
|
||||||
|
|
||||||
yaml@2.9.0: {}
|
yaml@2.9.0:
|
||||||
|
optional: true
|
||||||
|
|
||||||
yocto-queue@0.1.0: {}
|
yocto-queue@0.1.0: {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,144 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
||||||
import { parse as parseToml } from 'smol-toml';
|
|
||||||
import { parse as parseYaml } from 'yaml';
|
|
||||||
|
|
||||||
export interface HugoPostData {
|
|
||||||
title: string;
|
|
||||||
slug: string;
|
|
||||||
content: string;
|
|
||||||
cover: string;
|
|
||||||
published_at: string;
|
|
||||||
updated_at: string;
|
|
||||||
categories: string[];
|
|
||||||
tags: string[];
|
|
||||||
series: string[];
|
|
||||||
series_weight: number | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析并转换时间,一旦解析失败或空缺,严格使用当前系统时间
|
|
||||||
*/
|
|
||||||
function parseOrCurrentDate(val: unknown): string {
|
|
||||||
if (!val) return new Date().toISOString();
|
|
||||||
try {
|
|
||||||
const dateObj = new Date(val as string | number | Date);
|
|
||||||
if (isNaN(dateObj.getTime())) {
|
|
||||||
return new Date().toISOString();
|
|
||||||
}
|
|
||||||
return dateObj.toISOString();
|
|
||||||
} catch (err: any) {
|
|
||||||
return new Date().toISOString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清洗分类和标签列表(同时支持数组与单字符格式)
|
|
||||||
*/
|
|
||||||
function parseArrayField(val: unknown): string[] {
|
|
||||||
if (Array.isArray(val)) {
|
|
||||||
return val.map((item) => String(item).trim()).filter(Boolean);
|
|
||||||
}
|
|
||||||
if (typeof val === 'string' && val.trim() !== '') {
|
|
||||||
return [val.trim()];
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseIntegerField(val: unknown): number | null {
|
|
||||||
if (val === null || val === undefined || val === '') return null;
|
|
||||||
const parsed = typeof val === 'number' ? val : Number(String(val).trim());
|
|
||||||
return Number.isInteger(parsed) ? parsed : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 去掉文件名结尾的格式拓展名,提供纯文件名基础
|
|
||||||
*/
|
|
||||||
function stripExtension(filename: string): string {
|
|
||||||
return filename.replace(/\.(md|markdown|txt)$/i, '').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hugo Markdown 头部元数据及正文解析器
|
|
||||||
* 同时原生兼容 TOML (+++) 与 YAML (---) 语法,自动过滤并丢弃本项目无端对应参数
|
|
||||||
*/
|
|
||||||
export function parseHugoMarkdown(rawContent: string, fileName = ''): HugoPostData {
|
|
||||||
// 剔除可能存在的 UTF-8 BOM
|
|
||||||
const text = rawContent.replace(/^\uFEFF/, '').trim();
|
|
||||||
const defaultName = fileName ? stripExtension(fileName) : '新文章';
|
|
||||||
|
|
||||||
let meta: Record<string, unknown> = {};
|
|
||||||
let bodyContent = text;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. 优先检识 TOML (以 +++ 开头包络)
|
|
||||||
if (text.startsWith('+++')) {
|
|
||||||
const endIndex = text.indexOf('\n+++', 3);
|
|
||||||
if (endIndex !== -1) {
|
|
||||||
const frontmatter = text.substring(3, endIndex).trim();
|
|
||||||
bodyContent = text.substring(endIndex + 4).trim();
|
|
||||||
meta = parseToml(frontmatter) as Record<string, any>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 2. 检识常规 YAML (以 --- 开头包络)
|
|
||||||
else if (text.startsWith('---')) {
|
|
||||||
const endIndex = text.indexOf('\n---', 3);
|
|
||||||
if (endIndex !== -1) {
|
|
||||||
const frontmatter = text.substring(3, endIndex).trim();
|
|
||||||
bodyContent = text.substring(endIndex + 4).trim();
|
|
||||||
meta = parseYaml(frontmatter) as Record<string, any>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
|
||||||
console.error(
|
|
||||||
'解析 Markdown 头部参数时出现异常,直接回货底稿正文并对时差退回初始系统时刻:',
|
|
||||||
err
|
|
||||||
);
|
|
||||||
// 当解析受卡出错,空字典依然不阻挠对下方日期与稿费正文字面的顺利捕拾
|
|
||||||
}
|
|
||||||
|
|
||||||
// 取值并完成映射:除以下对应参数处,诸如 draft/twemoji/lightgallery 等全在空载转储里随水蒸丢!
|
|
||||||
const title =
|
|
||||||
typeof meta.title === 'string' && meta.title.trim() !== '' ? meta.title.trim() : defaultName;
|
|
||||||
|
|
||||||
let slug = typeof meta.slug === 'string' ? meta.slug.trim() : '';
|
|
||||||
if (!slug) {
|
|
||||||
// 没有声明明确的 slug 时,由名目精简转化出基础链接字符串
|
|
||||||
slug = defaultName
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/[^\w\u4e00-\u9fa5\s-]/g, '')
|
|
||||||
.replace(/\s+/g, '-');
|
|
||||||
if (!slug) {
|
|
||||||
slug = 'post-' + Date.now();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 特色图片字段识别(首挑 featuredImage,也兼备处理 cover/image)
|
|
||||||
const cover =
|
|
||||||
typeof meta.featuredImage === 'string'
|
|
||||||
? meta.featuredImage.trim()
|
|
||||||
: typeof meta.cover === 'string'
|
|
||||||
? meta.cover.trim()
|
|
||||||
: typeof meta.image === 'string'
|
|
||||||
? meta.image.trim()
|
|
||||||
: '';
|
|
||||||
|
|
||||||
// 时间字段转化:严格按需求约定“一旦转换错漏或是全无,采用目前时刻填满”
|
|
||||||
const published_at = parseOrCurrentDate(meta.date || meta.published_at);
|
|
||||||
const updated_at = parseOrCurrentDate(
|
|
||||||
meta.lastmod || meta.updated_at || meta.date || meta.published_at
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: title || '无名小贴',
|
|
||||||
slug,
|
|
||||||
content: bodyContent,
|
|
||||||
cover,
|
|
||||||
published_at,
|
|
||||||
updated_at,
|
|
||||||
categories: parseArrayField(meta.categories || meta.category),
|
|
||||||
tags: parseArrayField(meta.tags || meta.tag),
|
|
||||||
series: parseArrayField(meta.series),
|
|
||||||
series_weight: parseIntegerField(meta.series_weight)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
||||||
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';
|
||||||
@@ -9,7 +8,6 @@
|
|||||||
import MarkdownEditor from '$lib/components/admin/MarkdownEditor.svelte';
|
import MarkdownEditor from '$lib/components/admin/MarkdownEditor.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 { parseHugoMarkdown } from '$lib/hugoParser';
|
|
||||||
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';
|
||||||
@@ -28,7 +26,6 @@
|
|||||||
let selectedCategoryId = $state<number | string>(initialData.post.category_id ?? '');
|
let selectedCategoryId = $state<number | string>(initialData.post.category_id ?? '');
|
||||||
let selectedSeriesId = $state<number | string>(initialData.post.series_id ?? '');
|
let selectedSeriesId = $state<number | string>(initialData.post.series_id ?? '');
|
||||||
let seriesWeight = $state<number | string>(initialData.post.series_weight ?? '');
|
let seriesWeight = $state<number | string>(initialData.post.series_weight ?? '');
|
||||||
let fileInput: HTMLInputElement;
|
|
||||||
let selectedTags = $state<number[]>(initialData.selectedTagIds || []);
|
let selectedTags = $state<number[]>(initialData.selectedTagIds || []);
|
||||||
let tagsList = $state(initialData.tags || []);
|
let tagsList = $state(initialData.tags || []);
|
||||||
let seriesList = $state(initialData.series || []);
|
let seriesList = $state(initialData.series || []);
|
||||||
@@ -105,126 +102,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedSeriesId = '';
|
|
||||||
seriesWeight = imported.series_weight ?? '';
|
|
||||||
if (imported.series.length > 0) {
|
|
||||||
if (imported.series.length > 1) {
|
|
||||||
alert(
|
|
||||||
`检测到多个系列:${imported.series.join('、')}。BlogFlare 每篇文章只能属于一个系列,本次将选择第一个系列。`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const seriesName = imported.series[0].trim();
|
|
||||||
let matchedSeries = seriesList.find(
|
|
||||||
(item) => item.name.trim().toLowerCase() === seriesName.toLowerCase()
|
|
||||||
);
|
|
||||||
if (!matchedSeries && seriesName) {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('name', seriesName);
|
|
||||||
const response = await fetch('/admin/series?/create', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
headers: { 'x-sveltekit-action': 'true' }
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionResult = deserialize(await response.text()) as any;
|
|
||||||
if (actionResult.type === 'success' && actionResult.data?.series) {
|
|
||||||
const createdSeries = actionResult.data.series as {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
};
|
|
||||||
matchedSeries = createdSeries;
|
|
||||||
seriesList = [...seriesList, createdSeries];
|
|
||||||
} else {
|
|
||||||
alert(
|
|
||||||
actionResult.data?.error ||
|
|
||||||
`无法创建系列“${seriesName}”,请在系列管理中检查名称和 URL 别名。`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (matchedSeries) selectedSeriesId = matchedSeries.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 标签处理:同步点选或即刻新增入参
|
|
||||||
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 (err: any) {
|
|
||||||
console.error('自动拓展建构新标签错付:', err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
target.value = '';
|
|
||||||
} catch (err: any) {
|
|
||||||
console.error('导入报错:');
|
|
||||||
alert('文档内部解析受断,核验是否带有完整及恰当规范的内容格式');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="admin-page-header">
|
<div class="admin-page-header">
|
||||||
<h2>编辑</h2>
|
<h2>编辑</h2>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<Button type="button" variant="secondary" onclick={() => fileInput?.click()}>
|
|
||||||
导入 Markdown
|
|
||||||
</Button>
|
|
||||||
<Button href={resolve('/admin/posts')} variant="secondary">← 返回</Button>
|
<Button href={resolve('/admin/posts')} variant="secondary">← 返回</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
bind:this={fileInput}
|
|
||||||
accept=".md,.markdown,.txt"
|
|
||||||
style="display:none"
|
|
||||||
onchange={handleImportMarkdown}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{#if form?.error}
|
{#if form?.error}
|
||||||
<Alert message={form.error} />
|
<Alert message={form.error} />
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
import MarkdownEditor from '$lib/components/admin/MarkdownEditor.svelte';
|
import MarkdownEditor from '$lib/components/admin/MarkdownEditor.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 { parseHugoMarkdown } from '$lib/hugoParser';
|
|
||||||
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';
|
||||||
@@ -30,7 +29,6 @@
|
|||||||
let selectedCategoryId = $state<string | number>('');
|
let selectedCategoryId = $state<string | number>('');
|
||||||
let selectedSeriesId = $state<string | number>('');
|
let selectedSeriesId = $state<string | number>('');
|
||||||
let seriesWeight = $state<string | number>('');
|
let seriesWeight = $state<string | number>('');
|
||||||
let fileInput: HTMLInputElement;
|
|
||||||
|
|
||||||
let tagsList = $state(initialData.tags || []);
|
let tagsList = $state(initialData.tags || []);
|
||||||
let seriesList = $state(initialData.series || []);
|
let seriesList = $state(initialData.series || []);
|
||||||
@@ -122,126 +120,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedSeriesId = '';
|
|
||||||
seriesWeight = imported.series_weight ?? '';
|
|
||||||
if (imported.series.length > 0) {
|
|
||||||
if (imported.series.length > 1) {
|
|
||||||
alert(
|
|
||||||
`检测到多个系列:${imported.series.join('、')}。BlogFlare 每篇文章只能属于一个系列,本次将选择第一个系列。`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const seriesName = imported.series[0].trim();
|
|
||||||
let matchedSeries = seriesList.find(
|
|
||||||
(item) => item.name.trim().toLowerCase() === seriesName.toLowerCase()
|
|
||||||
);
|
|
||||||
if (!matchedSeries && seriesName) {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('name', seriesName);
|
|
||||||
const response = await fetch('/admin/series?/create', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
headers: { 'x-sveltekit-action': 'true' }
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionResult = deserialize(await response.text()) as any;
|
|
||||||
if (actionResult.type === 'success' && actionResult.data?.series) {
|
|
||||||
const createdSeries = actionResult.data.series as {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
};
|
|
||||||
matchedSeries = createdSeries;
|
|
||||||
seriesList = [...seriesList, createdSeries];
|
|
||||||
} else {
|
|
||||||
alert(
|
|
||||||
actionResult.data?.error ||
|
|
||||||
`无法创建系列“${seriesName}”,请在系列管理中检查名称和 URL 别名。`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (matchedSeries) selectedSeriesId = matchedSeries.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 标签匹配:库有即点取,不知则自主申请向库内补充建好
|
|
||||||
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 (err: any) {
|
|
||||||
console.error('新建导入对应新标签时出错:', err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
target.value = '';
|
|
||||||
} catch (err: any) {
|
|
||||||
console.error('导入转换出现错误:');
|
|
||||||
alert('解析文档内容受困,请稍后检查所填文书');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="admin-page-header">
|
<div class="admin-page-header">
|
||||||
<h2>新{$page.data.aliases?.post || '文章'}</h2>
|
<h2>新{$page.data.aliases?.post || '文章'}</h2>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<Button type="button" variant="secondary" onclick={() => fileInput?.click()}>
|
|
||||||
导入 Markdown
|
|
||||||
</Button>
|
|
||||||
<Button href={resolve('/admin/posts')} variant="secondary">← 返回</Button>
|
<Button href={resolve('/admin/posts')} variant="secondary">← 返回</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
bind:this={fileInput}
|
|
||||||
accept=".md,.markdown,.txt"
|
|
||||||
style="display:none"
|
|
||||||
onchange={handleImportMarkdown}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{#if form?.error}
|
{#if form?.error}
|
||||||
<Alert message={form.error} />
|
<Alert message={form.error} />
|
||||||
|
|||||||
Reference in new issue
Block a user