release: v0.16.0
See CHANGELOG.md for the full v0.16.0 entry. --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: ralksta <ralksta@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Ralf <rohlenbo79@gmail.com> Co-authored-by: Paul Thies <110185070+ImScheinox@users.noreply.github.com> Co-authored-by: lancetm714 <108419425+lancetm714@users.noreply.github.com>
This commit is contained in:
@@ -26,6 +26,7 @@ import {
|
||||
import AssetPicker from './AssetPicker';
|
||||
import { BlockBadge } from './BlockBadge';
|
||||
import { useUnsavedGuard } from './useUnsavedGuard';
|
||||
import { reportIfSessionExpired } from './sessionExpiry';
|
||||
import { EssayView } from '@/app/[...path]/EssayView';
|
||||
import type { PhotoItem } from '@/app/[...path]/PhotoGrid';
|
||||
import './journal-studio.css';
|
||||
@@ -435,23 +436,33 @@ function JournalEditor({ slug, onBack }: JournalEditorProps) {
|
||||
title: string;
|
||||
} | null>(null);
|
||||
|
||||
/** Set when the entry could not be fetched; blocks saving over it. */
|
||||
const [loadError, setLoadError] = useState<string | null>(null);
|
||||
|
||||
// Load entry
|
||||
useEffect(() => {
|
||||
async function load() {
|
||||
setLoading(true);
|
||||
setLoadError(null);
|
||||
try {
|
||||
const res = await fetch(`/api/admin/journal/${slug}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
const md = data.entry.rawMarkdown;
|
||||
setRawMarkdown(md);
|
||||
setParsed(parseJournalMarkdown(md));
|
||||
setDirty(false);
|
||||
} else {
|
||||
alert('Failed to load journal entry');
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
res.status === 401
|
||||
? 'Your session has expired. Sign in again to continue.'
|
||||
: `The server answered ${res.status}.`,
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
alert('Error loading journal entry');
|
||||
const data = await res.json();
|
||||
const md = data.entry.rawMarkdown;
|
||||
setRawMarkdown(md);
|
||||
setParsed(parseJournalMarkdown(md));
|
||||
setDirty(false);
|
||||
} catch (err) {
|
||||
console.error('Failed to load journal entry:', err);
|
||||
// An empty editor saved over the entry replaces it with nothing, so
|
||||
// this blocks the save rather than only reporting it.
|
||||
setLoadError(err instanceof Error ? err.message : 'The entry could not be loaded.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -504,6 +515,9 @@ function JournalEditor({ slug, onBack }: JournalEditorProps) {
|
||||
|
||||
// Save
|
||||
const handleSave = async () => {
|
||||
// The editor is not rendered in this state, but Cmd+S still reaches here.
|
||||
if (loadError) return;
|
||||
|
||||
setSaving(true);
|
||||
try {
|
||||
const res = await fetch(`/api/admin/journal/${slug}`, {
|
||||
@@ -516,7 +530,7 @@ function JournalEditor({ slug, onBack }: JournalEditorProps) {
|
||||
|
||||
if (res.ok) {
|
||||
setDirty(false);
|
||||
} else {
|
||||
} else if (!reportIfSessionExpired(res)) {
|
||||
const data = await res.json();
|
||||
alert(data.error || 'Failed to save');
|
||||
}
|
||||
@@ -636,6 +650,25 @@ function JournalEditor({ slug, onBack }: JournalEditorProps) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the editor rather than sitting above it: an empty editor saved
|
||||
* over the entry would replace the text with nothing.
|
||||
*/
|
||||
if (loadError) {
|
||||
return (
|
||||
<div className="admin-error" role="alert">
|
||||
<strong>This entry could not be loaded.</strong> {loadError}
|
||||
<p>
|
||||
Nothing has been changed. Saving stays disabled until it loads, so an empty editor cannot
|
||||
overwrite the entry.
|
||||
</p>
|
||||
<button className="admin-btn admin-btn-secondary" onClick={onBack}>
|
||||
Back to entries
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="journal-editor-container">
|
||||
{/* Top Bar */}
|
||||
|
||||
Reference in New Issue
Block a user