Files
rose-ash/shared/editor/src/Editor.jsx
giles f42042ccb7
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m5s
Monorepo: consolidate 7 repos into one
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)
2026-02-24 19:44:17 +00:00

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>
);
}