All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m5s
Combines shared, blog, market, cart, events, federation, and account into a single repository. Eliminates submodule sync, sibling model copying at build time, and per-app CI orchestration. Changes: - Remove per-app .git, .gitmodules, .gitea, submodule shared/ dirs - Remove stale sibling model copies from each app - Update all 6 Dockerfiles for monorepo build context (root = .) - Add build directives to docker-compose.yml - Add single .gitea/workflows/ci.yml with change detection - Add .dockerignore for monorepo build context - Create __init__.py for federation and account (cross-app imports)
82 lines
2.6 KiB
JavaScript
82 lines
2.6 KiB
JavaScript
import { useMemo, useState, useEffect, useCallback } from "react";
|
|
import { KoenigComposer, KoenigEditor, CardMenuPlugin } from "@tryghost/koenig-lexical";
|
|
import "koenig-styles";
|
|
import makeFileUploader from "./useFileUpload";
|
|
|
|
export default function Editor({ initialState, onChange, csrfToken, uploadUrls, oembedUrl, unsplashApiKey, snippetsUrl }) {
|
|
const fileUploader = useMemo(() => makeFileUploader(csrfToken, uploadUrls), [csrfToken, uploadUrls]);
|
|
|
|
const [snippets, setSnippets] = useState([]);
|
|
|
|
useEffect(() => {
|
|
if (!snippetsUrl) return;
|
|
fetch(snippetsUrl, { headers: { "X-CSRFToken": csrfToken || "" } })
|
|
.then((r) => r.ok ? r.json() : [])
|
|
.then(setSnippets)
|
|
.catch(() => {});
|
|
}, [snippetsUrl, csrfToken]);
|
|
|
|
const createSnippet = useCallback(async ({ name, value }) => {
|
|
if (!snippetsUrl) return;
|
|
const resp = await fetch(snippetsUrl, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-CSRFToken": csrfToken || "",
|
|
},
|
|
body: JSON.stringify({ name, value: JSON.stringify(value) }),
|
|
});
|
|
if (!resp.ok) return;
|
|
const created = await resp.json();
|
|
setSnippets((prev) => {
|
|
const idx = prev.findIndex((s) => s.name === created.name);
|
|
if (idx >= 0) {
|
|
const next = [...prev];
|
|
next[idx] = created;
|
|
return next;
|
|
}
|
|
return [...prev, created].sort((a, b) => a.name.localeCompare(b.name));
|
|
});
|
|
}, [snippetsUrl, csrfToken]);
|
|
|
|
const cardConfig = useMemo(() => ({
|
|
fetchEmbed: async (url, { type } = {}) => {
|
|
const params = new URLSearchParams({ url });
|
|
if (type) params.set("type", type);
|
|
const resp = await fetch(`${oembedUrl}?${params}`, {
|
|
headers: { "X-CSRFToken": csrfToken || "" },
|
|
});
|
|
if (!resp.ok) return {};
|
|
return resp.json();
|
|
},
|
|
unsplash: unsplashApiKey
|
|
? { defaultHeaders: { Authorization: `Client-ID ${unsplashApiKey}` } }
|
|
: false,
|
|
membersEnabled: true,
|
|
snippets: snippets.map((s) => ({
|
|
id: s.id,
|
|
name: s.name,
|
|
value: typeof s.value === "string" ? JSON.parse(s.value) : s.value,
|
|
})),
|
|
createSnippet,
|
|
}), [oembedUrl, csrfToken, unsplashApiKey, snippets, createSnippet]);
|
|
|
|
return (
|
|
<KoenigComposer
|
|
initialEditorState={initialState || undefined}
|
|
fileUploader={fileUploader}
|
|
cardConfig={cardConfig}
|
|
>
|
|
<KoenigEditor
|
|
onChange={(serializedState) => {
|
|
if (onChange) {
|
|
onChange(JSON.stringify(serializedState));
|
|
}
|
|
}}
|
|
>
|
|
<CardMenuPlugin />
|
|
</KoenigEditor>
|
|
</KoenigComposer>
|
|
);
|
|
}
|