diff --git a/app/package-lock.json b/app/package-lock.json index 61b6f08..9e73bbc 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -9,8 +9,10 @@ "version": "0.0.0", "dependencies": { "@exampledev/new.css": "^1.1.3", + "@lexical/plain-text": "^0.3.11", "esbuild": "^0.15.5", "esbuild-register": "^3.3.3", + "lexical": "^0.3.11", "solid-js": "^1.4.8" }, "devDependencies": { @@ -27,6 +29,90 @@ "resolved": "https://registry.npmjs.org/@exampledev/new.css/-/new.css-1.1.3.tgz", "integrity": "sha512-qhbGfqBRwUlM6MCSaJdUfjq86opNCMvM+6kVvs6S0kYhy0V8dKbe4rDMIklEJGuMc5QH5OuPjdCReu9I0tim2w==" }, + "node_modules/@lexical/clipboard": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.3.11.tgz", + "integrity": "sha512-ly+9R2Rccz80eV9Gu0hOi701fXIyU50t+S7OR0Ensos09oAmQPae6poFkvS3k36tH+leLgk0hRZ47pB/+ejENA==", + "peer": true, + "dependencies": { + "@lexical/html": "0.3.11", + "@lexical/list": "0.3.11", + "@lexical/selection": "0.3.11", + "@lexical/utils": "0.3.11" + }, + "peerDependencies": { + "lexical": "0.3.11" + } + }, + "node_modules/@lexical/html": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@lexical/html/-/html-0.3.11.tgz", + "integrity": "sha512-cw6IjI+OQr6Dwm1Mvkws0HsMOZ0lIky/DIhSt7ZAcUya+N1sis3kNIhg5Gijvpao+/n17QPgOwWS2sE7PFESmg==", + "peer": true, + "dependencies": { + "@lexical/selection": "0.3.11" + }, + "peerDependencies": { + "lexical": "0.3.11" + } + }, + "node_modules/@lexical/list": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.3.11.tgz", + "integrity": "sha512-l79kqwFRTuUx+fNxRmYaqP27tB7va/MAKoRL1Jzv1EYBZjz0fEHxpaRBG7Y5JHSMMFRQrJEhL4hKMs2EiCecvQ==", + "peer": true, + "dependencies": { + "@lexical/utils": "0.3.11" + }, + "peerDependencies": { + "lexical": "0.3.11" + } + }, + "node_modules/@lexical/plain-text": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@lexical/plain-text/-/plain-text-0.3.11.tgz", + "integrity": "sha512-AfCHJPtY0g/gknYDCU2uZ5NHASSp1Gg7Ho7OuAi3sVOuVqs+SHI+ZkeC3y7MxrGhOP3TnZ+EsltAbToEQclSDA==", + "peerDependencies": { + "@lexical/clipboard": "0.3.11", + "@lexical/selection": "0.3.11", + "@lexical/utils": "0.3.11", + "lexical": "0.3.11" + } + }, + "node_modules/@lexical/selection": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@lexical/selection/-/selection-0.3.11.tgz", + "integrity": "sha512-6SlXUmLP6K2E1OQFS2QcOiGe1fZV4o1vFLynq/st7BVzpQ1/bDMp2lnsnNWyA0H6v36n+wVUvipKsyFcrDtH/w==", + "peer": true, + "peerDependencies": { + "lexical": "0.3.11" + } + }, + "node_modules/@lexical/table": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@lexical/table/-/table-0.3.11.tgz", + "integrity": "sha512-/6nDug81vfs6eI3u23JTRcCny1fsJ/gjM/ljERd6+f9rP+yXbStMKIyAsWebz6ZSskoyl5LIj8Po3PParhCdGg==", + "peer": true, + "dependencies": { + "@lexical/utils": "0.3.11" + }, + "peerDependencies": { + "lexical": "0.3.11" + } + }, + "node_modules/@lexical/utils": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@lexical/utils/-/utils-0.3.11.tgz", + "integrity": "sha512-J3HQjPSYg33Yd+g/SUx9Selqn27qCmXN253+0lIEH8R8pn2A+Pe4vrk+/DSr/URuN1GUq+jjLWci3SDsA4h3xQ==", + "peer": true, + "dependencies": { + "@lexical/list": "0.3.11", + "@lexical/table": "0.3.11" + }, + "peerDependencies": { + "lexical": "0.3.11" + } + }, "node_modules/@tsconfig/node18-strictest-esm": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@tsconfig/node18-strictest-esm/-/node18-strictest-esm-1.0.0.tgz", @@ -97,6 +183,11 @@ "esbuild": ">=0.12 <1" } }, + "node_modules/lexical": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/lexical/-/lexical-0.3.11.tgz", + "integrity": "sha512-HZvQ2T3g0jWBX6MC/A0HY1N7NMvR+FrmTfR4vn6WnoCg56UPlkgIX3GKa6rCyGOAnOtykXRPJ831JWEKJUHalQ==" + }, "node_modules/solid-js": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.4.8.tgz", @@ -122,6 +213,68 @@ "resolved": "https://registry.npmjs.org/@exampledev/new.css/-/new.css-1.1.3.tgz", "integrity": "sha512-qhbGfqBRwUlM6MCSaJdUfjq86opNCMvM+6kVvs6S0kYhy0V8dKbe4rDMIklEJGuMc5QH5OuPjdCReu9I0tim2w==" }, + "@lexical/clipboard": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.3.11.tgz", + "integrity": "sha512-ly+9R2Rccz80eV9Gu0hOi701fXIyU50t+S7OR0Ensos09oAmQPae6poFkvS3k36tH+leLgk0hRZ47pB/+ejENA==", + "peer": true, + "requires": { + "@lexical/html": "0.3.11", + "@lexical/list": "0.3.11", + "@lexical/selection": "0.3.11", + "@lexical/utils": "0.3.11" + } + }, + "@lexical/html": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@lexical/html/-/html-0.3.11.tgz", + "integrity": "sha512-cw6IjI+OQr6Dwm1Mvkws0HsMOZ0lIky/DIhSt7ZAcUya+N1sis3kNIhg5Gijvpao+/n17QPgOwWS2sE7PFESmg==", + "peer": true, + "requires": { + "@lexical/selection": "0.3.11" + } + }, + "@lexical/list": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.3.11.tgz", + "integrity": "sha512-l79kqwFRTuUx+fNxRmYaqP27tB7va/MAKoRL1Jzv1EYBZjz0fEHxpaRBG7Y5JHSMMFRQrJEhL4hKMs2EiCecvQ==", + "peer": true, + "requires": { + "@lexical/utils": "0.3.11" + } + }, + "@lexical/plain-text": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@lexical/plain-text/-/plain-text-0.3.11.tgz", + "integrity": "sha512-AfCHJPtY0g/gknYDCU2uZ5NHASSp1Gg7Ho7OuAi3sVOuVqs+SHI+ZkeC3y7MxrGhOP3TnZ+EsltAbToEQclSDA==", + "requires": {} + }, + "@lexical/selection": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@lexical/selection/-/selection-0.3.11.tgz", + "integrity": "sha512-6SlXUmLP6K2E1OQFS2QcOiGe1fZV4o1vFLynq/st7BVzpQ1/bDMp2lnsnNWyA0H6v36n+wVUvipKsyFcrDtH/w==", + "peer": true, + "requires": {} + }, + "@lexical/table": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@lexical/table/-/table-0.3.11.tgz", + "integrity": "sha512-/6nDug81vfs6eI3u23JTRcCny1fsJ/gjM/ljERd6+f9rP+yXbStMKIyAsWebz6ZSskoyl5LIj8Po3PParhCdGg==", + "peer": true, + "requires": { + "@lexical/utils": "0.3.11" + } + }, + "@lexical/utils": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@lexical/utils/-/utils-0.3.11.tgz", + "integrity": "sha512-J3HQjPSYg33Yd+g/SUx9Selqn27qCmXN253+0lIEH8R8pn2A+Pe4vrk+/DSr/URuN1GUq+jjLWci3SDsA4h3xQ==", + "peer": true, + "requires": { + "@lexical/list": "0.3.11", + "@lexical/table": "0.3.11" + } + }, "@tsconfig/node18-strictest-esm": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@tsconfig/node18-strictest-esm/-/node18-strictest-esm-1.0.0.tgz", @@ -174,6 +327,11 @@ "integrity": "sha512-eFHOkutgIMJY5gc8LUp/7c+LLlDqzNi9T6AwCZ2WKKl3HmT+5ef3ZRyPPxDOynInML0fgaC50yszPKfPnjC0NQ==", "requires": {} }, + "lexical": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/lexical/-/lexical-0.3.11.tgz", + "integrity": "sha512-HZvQ2T3g0jWBX6MC/A0HY1N7NMvR+FrmTfR4vn6WnoCg56UPlkgIX3GKa6rCyGOAnOtykXRPJ831JWEKJUHalQ==" + }, "solid-js": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.4.8.tgz", diff --git a/app/package.json b/app/package.json index 523811d..d94f019 100644 --- a/app/package.json +++ b/app/package.json @@ -12,8 +12,10 @@ "packageManager": "npm@8.15.0", "dependencies": { "@exampledev/new.css": "^1.1.3", + "@lexical/plain-text": "^0.3.11", "esbuild": "^0.15.5", "esbuild-register": "^3.3.3", + "lexical": "^0.3.11", "solid-js": "^1.4.8" }, "devDependencies": { diff --git a/app/src/components/editor.css b/app/src/components/editor.css index 66c9889..de63d91 100644 --- a/app/src/components/editor.css +++ b/app/src/components/editor.css @@ -1,3 +1,10 @@ .editor { - inline-size: 100%; + padding: 1rem; + box-sizing: border-box; + background-color: var(--nc-bg-2); + color: var(--nc-tx-2); + border-width: 0.75rem; + border-top-style: solid; + border-top-color: var(--nc-bg-3); + box-shadow: 0 1px var(--nc-bg-3); } diff --git a/app/src/components/editor.tsx b/app/src/components/editor.tsx index 1415199..57d515f 100644 --- a/app/src/components/editor.tsx +++ b/app/src/components/editor.tsx @@ -1,19 +1,43 @@ +import { + $createParagraphNode, + $createTextNode, + $getRoot, + createEditor, +} from "lexical"; +import { registerPlainText } from "@lexical/plain-text"; +import { onCleanup, onMount } from "solid-js"; import type Pages from "../protocol/pages"; import "./editor.css"; +const editor = createEditor(); + +function ref(el: HTMLElement) { + editor.setRootElement(el); +} + export default (props: { id: number; + title: string; + text: string; onUpdatePage: (content: Pages.RequestContentPage) => void; -}) => ( - -); +}) => { + const initialEditorState = () => { + const root = $getRoot(); + if (root.getFirstChild()) return; + const paragraphNode = $createParagraphNode(); + const textNode = $createTextNode(props.text); + paragraphNode.append(textNode); + root.append(paragraphNode); + }; + onCleanup(registerPlainText(editor, initialEditorState)); + onMount(() => { + onCleanup( + editor.registerTextContentListener((text) => { + const [title] = text.split("\n"); + props.onUpdatePage({ id: props.id, title: title ?? "", text }); + }) + ); + editor.focus(); + }); + return
; +}; diff --git a/app/src/pages/page.tsx b/app/src/pages/page.tsx index 89e5c10..31a7738 100644 --- a/app/src/pages/page.tsx +++ b/app/src/pages/page.tsx @@ -1,7 +1,6 @@ import { createResource } from "solid-js"; import type Pages from "../protocol/pages"; import Editor from "../components/editor"; -import Card from "../components/card"; import beforeunload from "../helpers/beforeunload"; import throttle from "../helpers/throttle"; @@ -23,6 +22,14 @@ async function updatePage( return res.ok; } +async function deletePage(id: number): Promise { + const res = await fetch( + new URL(`/pages?id=eq.${id}`, import.meta.env.QUOT_API_URL), + { method: "DELETE" } + ); + return res.ok; +} + async function fetchPage(id: number): Promise { const res = await fetch( new URL(`/pages?id=eq.${id}`, import.meta.env.QUOT_API_URL) @@ -32,13 +39,12 @@ async function fetchPage(id: number): Promise { } export default (props: { id: number }) => { - const [page, { refetch }] = createResource(props.id, fetchPage); + const [page] = createResource(props.id, fetchPage); const throttledUpdate = throttle( async (id: number, content: Pages.RequestContentPage) => { - if (await updatePage(id, content)) { + if (await (content.text ? updatePage(id, content) : deletePage(id))) { unblock(); - refetch(); - window.history.replaceState({}, "", `/${id}`); + window.history.replaceState({}, "", `/${content.text ? id : "new"}`); } }, intervalMs @@ -51,8 +57,17 @@ export default (props: { id: number }) => { return (
- - {() => } + {() => + !page.loading && ( + + ) + }
); };