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:
Ralksta
2026-09-22 14:12:13 +02:00
committed by GitHub
parent c3f92ca4ec
commit a3a6319c29
120 changed files with 6818 additions and 975 deletions

View File

@@ -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 */}