From 9af143f6ce8819ccd815fde8dc4439e34197526c Mon Sep 17 00:00:00 2001 From: Kohei Watanabe Date: Wed, 7 Sep 2022 17:03:54 +0900 Subject: [PATCH] rich text support (experimental) --- app/controllers/routes/views.ts | 2 +- app/package-lock.json | 14 ++++----- app/package.json | 2 +- app/views/components/editor.css | 9 ++++++ app/views/components/editor.tsx | 51 +++++++++++++++++++++------------ 5 files changed, 50 insertions(+), 28 deletions(-) diff --git a/app/controllers/routes/views.ts b/app/controllers/routes/views.ts index 2d64627..8838bb0 100644 --- a/app/controllers/routes/views.ts +++ b/app/controllers/routes/views.ts @@ -12,7 +12,7 @@ async function views(fastify: FastifyInstance) { }, { bundle: true, - minify: true, + minify: process.env.NODE_ENV !== "development", entryPoints: [entryPoint], define: { "import.meta.env.QUOT_API_ENDPOINT": JSON.stringify( diff --git a/app/package-lock.json b/app/package-lock.json index b8a4728..8fec273 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -11,7 +11,7 @@ "@exampledev/new.css": "^1.1.3", "@fastify/http-proxy": "^8.2.2", "@lexical/history": "^0.4.1", - "@lexical/plain-text": "^0.4.1", + "@lexical/rich-text": "^0.4.1", "@tsconfig/node18-strictest-esm": "^1.0.1", "esbuild": "^0.15.7", "esbuild-register": "^3.3.3", @@ -149,10 +149,10 @@ "lexical": "0.4.1" } }, - "node_modules/@lexical/plain-text": { + "node_modules/@lexical/rich-text": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@lexical/plain-text/-/plain-text-0.4.1.tgz", - "integrity": "sha512-cNFLXhOfR0coUFGA6aPGcHr6a+Y9ZrkETMM78+XV9J8iVsKij4/katFhsqACQDna4vSfXuqjTitCRtiFaDevDg==", + "resolved": "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.4.1.tgz", + "integrity": "sha512-EI4ul3y1hqMp0VS/4D8aOyR41ysz1KaYgkm6PyrRXEMyK8uKmVubJP83RkOU2fWkTVtdrMjM6aeT1qX849LetA==", "peerDependencies": { "@lexical/clipboard": "0.4.1", "@lexical/selection": "0.4.1", @@ -1207,10 +1207,10 @@ "@lexical/utils": "0.4.1" } }, - "@lexical/plain-text": { + "@lexical/rich-text": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@lexical/plain-text/-/plain-text-0.4.1.tgz", - "integrity": "sha512-cNFLXhOfR0coUFGA6aPGcHr6a+Y9ZrkETMM78+XV9J8iVsKij4/katFhsqACQDna4vSfXuqjTitCRtiFaDevDg==", + "resolved": "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.4.1.tgz", + "integrity": "sha512-EI4ul3y1hqMp0VS/4D8aOyR41ysz1KaYgkm6PyrRXEMyK8uKmVubJP83RkOU2fWkTVtdrMjM6aeT1qX849LetA==", "requires": {} }, "@lexical/selection": { diff --git a/app/package.json b/app/package.json index 8043e11..702e862 100644 --- a/app/package.json +++ b/app/package.json @@ -14,7 +14,7 @@ "@exampledev/new.css": "^1.1.3", "@fastify/http-proxy": "^8.2.2", "@lexical/history": "^0.4.1", - "@lexical/plain-text": "^0.4.1", + "@lexical/rich-text": "^0.4.1", "@tsconfig/node18-strictest-esm": "^1.0.1", "esbuild": "^0.15.7", "esbuild-register": "^3.3.3", diff --git a/app/views/components/editor.css b/app/views/components/editor.css index de63d91..86f0257 100644 --- a/app/views/components/editor.css +++ b/app/views/components/editor.css @@ -8,3 +8,12 @@ border-top-color: var(--nc-bg-3); box-shadow: 0 1px var(--nc-bg-3); } + +.editor :is(h1, h2, h3, h4, h5, h6) { + padding: unset; + font-size: unset; +} + +.editor p { + margin: unset; +} diff --git a/app/views/components/editor.tsx b/app/views/components/editor.tsx index de26253..42a0654 100644 --- a/app/views/components/editor.tsx +++ b/app/views/components/editor.tsx @@ -1,17 +1,21 @@ import { - $createLineBreakNode, $createParagraphNode, $createTextNode, $getRoot, + $isElementNode, createEditor, } from "lexical"; import { registerHistory, createEmptyHistoryState } from "@lexical/history"; -import { registerPlainText } from "@lexical/plain-text"; +import { + $createHeadingNode, + HeadingNode, + registerRichText, +} from "@lexical/rich-text"; import { onCleanup, onMount } from "solid-js"; import type Pages from "../../protocol/pages"; import "./editor.css"; -const editor = createEditor(); +const editor = createEditor({ nodes: [HeadingNode] }); function ref(el: HTMLElement) { editor.setRootElement(el); @@ -25,25 +29,34 @@ export default (props: { }) => { const initialEditorState = () => { const root = $getRoot(); - if (root.getFirstChild()) return; - const paragraphNode = $createParagraphNode(); - const text = props.text - .split("\n") - .flatMap((line) => [$createTextNode(line), $createLineBreakNode()]) - .slice(0, -1); - paragraphNode.append(...text); - root.append(paragraphNode); + for (const [i, text] of props.text.split("\n").entries()) { + const line = i === 0 ? $createHeadingNode("h2") : $createParagraphNode(); + const indent = text.match(/^\s*/)?.[0]?.length ?? 0; + line.setIndent(indent); + line.append($createTextNode(text.slice(indent))); + root.append(line); + } }; - onCleanup(registerPlainText(editor, initialEditorState)); + onCleanup(registerRichText(editor, initialEditorState)); onCleanup(registerHistory(editor, createEmptyHistoryState(), 333)); - onMount(() => { - onCleanup( - editor.registerTextContentListener((text) => { - const [title] = text.split("\n"); + onCleanup( + editor.registerUpdateListener(() => + editor.update(() => { + const root = $getRoot(); + const lines = root + .getChildren() + .map( + (line) => + `${" ".repeat( + $isElementNode(line) ? line.getIndent() : 0 + )}${line.getTextContent()}` + ); + const title = lines[0]; + const text = lines.join("\n"); props.onUpdatePage({ id: props.id, title: title ?? "", text }); }) - ); - editor.focus(); - }); + ) + ); + onMount(() => editor.focus()); return
; };