Files
blogflare/src/routes/admin/pages/new/+page.svelte
T
2026-08-08 09:07:23 +08:00

344 lines
7.0 KiB
Svelte

<script lang="ts">
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import type { ActionData } from './$types';
import MarkdownRenderer from '$lib/components/MarkdownRenderer.svelte';
import { createSlugAutoHandler } from '$lib/utils/slugify';
let { form }: { form: ActionData } = $props();
let title = $state('');
let slug = $state('');
let content = $state('');
let mode = $state<'edit' | 'preview'>('edit');
let isSlugUserModified = $state(false);
let isGenerating = $state(false);
const slugHandler = createSlugAutoHandler({
getSlug: () => slug,
setSlug: (val) => (slug = val),
getIsUserModified: () => isSlugUserModified,
setIsUserModified: (val) => (isSlugUserModified = val),
setIsGenerating: (val) => (isGenerating = val)
});
function handleTitleInput() {
slugHandler.onNameInput(title);
}
function handleSlugInput() {
slugHandler.onSlugInput(slug);
}
</script>
<div class="admin-page-header">
<h2>新建独立{$page.data.aliases?.page || '页面'}</h2>
<a href={resolve('/admin/pages')} class="btn btn-secondary">&larr; 返回</a>
</div>
{#if form?.error}
<div class="error-alert">
{form.error}
</div>
{/if}
<form method="POST" class="post-form">
<div class="form-grid">
<div class="form-main">
<div class="form-group">
<label for="title">标题 *</label>
<input
type="text"
id="title"
name="title"
bind:value={title}
oninput={handleTitleInput}
placeholder="输入标题..."
required
/>
</div>
<div class="form-group">
<div class="editor-header">
<label for="content">正文 *</label>
<div class="editor-tabs">
<button
type="button"
class="tab-btn {mode === 'edit' ? 'active' : ''}"
onclick={() => (mode = 'edit')}>编辑</button
>
<button
type="button"
class="tab-btn {mode === 'preview' ? 'active' : ''}"
onclick={() => (mode = 'preview')}>预览</button
>
</div>
</div>
<div class="editor-body">
{#if mode === 'edit'}
<textarea
id="content"
name="content"
bind:value={content}
rows="16"
style="min-height: 400px;"
placeholder="支持 Markdown 格式..."
required></textarea>
{:else}
<div class="preview-area">
<MarkdownRenderer {content} />
</div>
{/if}
</div>
</div>
</div>
<div class="form-sidebar">
<div class="sidebar-box">
<h3>发布</h3>
<button type="submit" class="btn btn-primary submit-btn">发布</button>
</div>
<div class="sidebar-box">
<h3 style="display: flex; justify-content: space-between; align-items: center;">
<span>别名 *</span>
<button
type="button"
style="background: none; border: none; padding: 0; color: #666; font-size: 0.8rem; cursor: pointer; outline: none;"
onclick={() => slugHandler.forceGenerate(title)}
disabled={isGenerating}
>
{isGenerating ? '正在生成...' : '重新生成'}
</button>
</h3>
<input
type="text"
id="slug"
name="slug"
class="sidebar-input"
bind:value={slug}
oninput={handleSlugInput}
placeholder="about"
required
/>
</div>
</div>
</div>
</form>
<style>
.admin-page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
border-bottom: 2px solid var(--text-color);
padding-bottom: 12px;
}
.admin-page-header h2 {
margin: 0;
font-size: 2rem;
color: var(--text-color);
}
.btn {
display: inline-block;
padding: 10px 20px;
text-decoration: none;
font-size: 0.95rem;
font-weight: 600;
transition: all 0.3s ease;
cursor: pointer;
border: 1px solid var(--text-color);
background-color: transparent;
color: var(--text-color);
font-family: inherit;
text-transform: uppercase;
letter-spacing: 1px;
}
.btn-primary {
background-color: var(--text-color);
color: var(--bg-color, #ffffff);
width: 100%;
text-align: center;
}
.btn-primary:hover {
background-color: var(--accent-color, #419b38);
border-color: var(--accent-color, #419b38);
color: white;
}
.btn-secondary:hover {
border-color: var(--accent-color, #419b38);
color: var(--accent-color, #419b38);
}
.error-alert {
padding: 12px 16px;
background-color: transparent;
border-left: 4px solid #d9534f;
color: #d9534f;
margin-bottom: 30px;
font-weight: 600;
}
.editor-header {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 6px;
}
.editor-tabs {
display: flex;
gap: 8px;
}
.tab-btn {
background: none;
border: none;
padding: 4px 12px;
font-size: 0.85rem;
font-weight: 600;
color: rgba(0, 0, 0, 0.4);
cursor: pointer;
border-radius: 4px;
transition: all 0.2s ease;
}
.tab-btn:hover {
background: rgba(0, 0, 0, 0.05);
}
.tab-btn.active {
color: var(--accent-color, #419b38);
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 {
display: grid;
grid-template-columns: 1fr 280px;
gap: 40px;
align-items: start;
}
.form-main {
width: 100%;
}
.form-sidebar {
position: sticky;
top: 80px;
width: 100%;
display: flex;
flex-direction: column;
gap: 30px;
z-index: 10;
}
@media (max-width: 900px) {
.form-grid {
grid-template-columns: 1fr;
}
.form-sidebar {
position: static;
flex-direction: row;
flex-wrap: wrap;
gap: 20px;
margin-top: 20px;
}
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
font-weight: 700;
margin-bottom: 6px;
color: var(--text-color);
font-size: 0.9rem;
letter-spacing: 0.5px;
}
.form-group input[type='text'],
.form-group textarea {
width: 100%;
padding: 12px 14px;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 0;
background: rgba(0, 0, 0, 0.015);
font-family: inherit;
font-size: 1.05rem;
color: var(--text-color);
box-sizing: border-box;
transition:
border-color 0.3s ease,
box-shadow 0.3s ease;
}
.form-group textarea {
resize: vertical;
line-height: 1.8;
min-height: 120px;
}
.form-group input[type='text']:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--accent-color, #419b38);
box-shadow: 0 0 0 3px rgba(65, 155, 56, 0.08);
}
.sidebar-input:focus {
outline: none;
border-bottom-color: var(--accent-color, #419b38);
}
.sidebar-input {
width: 100%;
padding: 10px 0;
border: none;
border-bottom: 2px solid rgba(0, 0, 0, 0.1);
background: transparent;
font-family: inherit;
font-size: 1rem;
color: var(--text-color);
box-sizing: border-box;
transition: border-bottom-color 0.3s ease;
}
.sidebar-box {
background: transparent;
border: none;
border-radius: 0;
padding: 0;
}
.sidebar-box h3 {
margin: 0 0 20px 0;
font-size: 0.9rem;
font-weight: 700;
letter-spacing: 1px;
text-transform: uppercase;
color: var(--text-color);
border-bottom: none;
}
</style>