split global.css
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ApiError, deletePost, getPost, savePost } from '../../../../lib/api';
|
||||
import { confirmDialog, notify } from '../../../../lib/confirm';
|
||||
import { clientSlugify } from './codemirror';
|
||||
|
||||
interface Opts {
|
||||
editSlug?: string;
|
||||
getContent: () => string;
|
||||
setContent: (s: string) => void;
|
||||
}
|
||||
|
||||
/** Post metadata form + slug derivation + load/save/delete. */
|
||||
export function usePostMeta({ editSlug, getContent, setContent }: Opts) {
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const [title, setTitle] = useState('');
|
||||
const [slug, setSlug] = useState(editSlug || '');
|
||||
const [slugTouched, setSlugTouched] = useState(!!editSlug);
|
||||
const [date, setDate] = useState(today);
|
||||
const [summary, setSummary] = useState('');
|
||||
const [tagsInput, setTagsInput] = useState('');
|
||||
const [draft, setDraft] = useState(false);
|
||||
const [originalSlug, setOriginalSlug] = useState(editSlug || '');
|
||||
|
||||
// Load existing post for editing.
|
||||
useEffect(() => {
|
||||
if (!editSlug) return;
|
||||
getPost(editSlug)
|
||||
.then(post => {
|
||||
if (post.title) setTitle(post.title);
|
||||
if (post.summary) setSummary(post.summary);
|
||||
if (post.date) setDate(post.date);
|
||||
if (post.tags?.length) setTagsInput(post.tags.join(', '));
|
||||
setDraft(!!post.draft);
|
||||
if (post.content) setContent(post.content);
|
||||
})
|
||||
.catch(() => notify('Failed to load post.', 'error'));
|
||||
}, [editSlug, setContent]);
|
||||
|
||||
// Auto-derive slug from title until the user edits the slug field.
|
||||
useEffect(() => {
|
||||
if (slugTouched) return;
|
||||
setSlug(clientSlugify(title));
|
||||
}, [title, slugTouched]);
|
||||
|
||||
async function handleSave() {
|
||||
const content = getContent();
|
||||
if (!title.trim() || !slug || !content) {
|
||||
notify('Title, slug, and body are required.', 'error');
|
||||
return;
|
||||
}
|
||||
if (!/!\[[^\]]*\]\([^)]+\)/.test(content)) {
|
||||
notify(
|
||||
'Add at least one image before saving — drag, paste, or use the Add image button.',
|
||||
'error',
|
||||
);
|
||||
return;
|
||||
}
|
||||
const tags = tagsInput
|
||||
.split(',')
|
||||
.map(t => t.trim())
|
||||
.filter(Boolean);
|
||||
try {
|
||||
const saved = await savePost({
|
||||
slug,
|
||||
old_slug: originalSlug || null,
|
||||
title: title.trim(),
|
||||
date,
|
||||
summary: summary || null,
|
||||
tags,
|
||||
draft,
|
||||
content,
|
||||
});
|
||||
notify('Post saved!', 'success');
|
||||
if (saved?.slug && saved.slug !== slug) {
|
||||
setSlug(saved.slug);
|
||||
setSlugTouched(true);
|
||||
}
|
||||
setOriginalSlug(saved?.slug ?? slug);
|
||||
} catch (e) {
|
||||
notify(
|
||||
e instanceof ApiError ? `Error: ${e.message}` : 'Failed to connect to server.',
|
||||
'error',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete() {
|
||||
const target = originalSlug || slug;
|
||||
const ok = await confirmDialog({
|
||||
title: 'Remove from catalogue?',
|
||||
message: `“${target}” will be permanently removed. This cannot be undone.`,
|
||||
confirmLabel: 'Remove',
|
||||
});
|
||||
if (!ok) return;
|
||||
try {
|
||||
await deletePost(target);
|
||||
window.location.href = '/admin';
|
||||
} catch {
|
||||
notify('Error deleting post.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title,
|
||||
setTitle,
|
||||
slug,
|
||||
setSlug,
|
||||
setSlugTouched,
|
||||
date,
|
||||
setDate,
|
||||
summary,
|
||||
setSummary,
|
||||
tagsInput,
|
||||
setTagsInput,
|
||||
draft,
|
||||
setDraft,
|
||||
originalSlug,
|
||||
handleSave,
|
||||
handleDelete,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user