mirror of
https://github.com/kou029w/quot.git
synced 2025-01-31 14:28:06 +00:00
Compare commits
5 commits
23e6bf327b
...
b725b250d3
Author | SHA1 | Date | |
---|---|---|---|
b725b250d3 | |||
b569dac9ce | |||
6e1ef081b8 | |||
a6ae92d66c | |||
9af143f6ce |
7 changed files with 109 additions and 33 deletions
|
@ -12,7 +12,7 @@ async function views(fastify: FastifyInstance) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
bundle: true,
|
bundle: true,
|
||||||
minify: true,
|
minify: process.env.NODE_ENV !== "development",
|
||||||
entryPoints: [entryPoint],
|
entryPoints: [entryPoint],
|
||||||
define: {
|
define: {
|
||||||
"import.meta.env.QUOT_API_ENDPOINT": JSON.stringify(
|
"import.meta.env.QUOT_API_ENDPOINT": JSON.stringify(
|
||||||
|
|
34
app/package-lock.json
generated
34
app/package-lock.json
generated
|
@ -11,7 +11,8 @@
|
||||||
"@exampledev/new.css": "^1.1.3",
|
"@exampledev/new.css": "^1.1.3",
|
||||||
"@fastify/http-proxy": "^8.2.2",
|
"@fastify/http-proxy": "^8.2.2",
|
||||||
"@lexical/history": "^0.4.1",
|
"@lexical/history": "^0.4.1",
|
||||||
"@lexical/plain-text": "^0.4.1",
|
"@lexical/link": "^0.4.1",
|
||||||
|
"@lexical/rich-text": "^0.4.1",
|
||||||
"@tsconfig/node18-strictest-esm": "^1.0.1",
|
"@tsconfig/node18-strictest-esm": "^1.0.1",
|
||||||
"esbuild": "^0.15.7",
|
"esbuild": "^0.15.7",
|
||||||
"esbuild-register": "^3.3.3",
|
"esbuild-register": "^3.3.3",
|
||||||
|
@ -138,6 +139,17 @@
|
||||||
"lexical": "0.4.1"
|
"lexical": "0.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@lexical/link": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lexical/link/-/link-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-566lQymmuBe3Y7UDyaaTs+VDlElbu1WhnjT9lVDk0BXag7MA8tv/f60XptWnTK1pv/Dobm/CyLmyLae55OuflQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lexical/utils": "0.4.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"lexical": "0.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@lexical/list": {
|
"node_modules/@lexical/list": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.4.1.tgz",
|
||||||
|
@ -149,10 +161,10 @@
|
||||||
"lexical": "0.4.1"
|
"lexical": "0.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@lexical/plain-text": {
|
"node_modules/@lexical/rich-text": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@lexical/plain-text/-/plain-text-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.4.1.tgz",
|
||||||
"integrity": "sha512-cNFLXhOfR0coUFGA6aPGcHr6a+Y9ZrkETMM78+XV9J8iVsKij4/katFhsqACQDna4vSfXuqjTitCRtiFaDevDg==",
|
"integrity": "sha512-EI4ul3y1hqMp0VS/4D8aOyR41ysz1KaYgkm6PyrRXEMyK8uKmVubJP83RkOU2fWkTVtdrMjM6aeT1qX849LetA==",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@lexical/clipboard": "0.4.1",
|
"@lexical/clipboard": "0.4.1",
|
||||||
"@lexical/selection": "0.4.1",
|
"@lexical/selection": "0.4.1",
|
||||||
|
@ -1199,6 +1211,14 @@
|
||||||
"@lexical/selection": "0.4.1"
|
"@lexical/selection": "0.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@lexical/link": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lexical/link/-/link-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-566lQymmuBe3Y7UDyaaTs+VDlElbu1WhnjT9lVDk0BXag7MA8tv/f60XptWnTK1pv/Dobm/CyLmyLae55OuflQ==",
|
||||||
|
"requires": {
|
||||||
|
"@lexical/utils": "0.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@lexical/list": {
|
"@lexical/list": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.4.1.tgz",
|
||||||
|
@ -1207,10 +1227,10 @@
|
||||||
"@lexical/utils": "0.4.1"
|
"@lexical/utils": "0.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@lexical/plain-text": {
|
"@lexical/rich-text": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@lexical/plain-text/-/plain-text-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.4.1.tgz",
|
||||||
"integrity": "sha512-cNFLXhOfR0coUFGA6aPGcHr6a+Y9ZrkETMM78+XV9J8iVsKij4/katFhsqACQDna4vSfXuqjTitCRtiFaDevDg==",
|
"integrity": "sha512-EI4ul3y1hqMp0VS/4D8aOyR41ysz1KaYgkm6PyrRXEMyK8uKmVubJP83RkOU2fWkTVtdrMjM6aeT1qX849LetA==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"@lexical/selection": {
|
"@lexical/selection": {
|
||||||
|
|
|
@ -14,7 +14,8 @@
|
||||||
"@exampledev/new.css": "^1.1.3",
|
"@exampledev/new.css": "^1.1.3",
|
||||||
"@fastify/http-proxy": "^8.2.2",
|
"@fastify/http-proxy": "^8.2.2",
|
||||||
"@lexical/history": "^0.4.1",
|
"@lexical/history": "^0.4.1",
|
||||||
"@lexical/plain-text": "^0.4.1",
|
"@lexical/link": "^0.4.1",
|
||||||
|
"@lexical/rich-text": "^0.4.1",
|
||||||
"@tsconfig/node18-strictest-esm": "^1.0.1",
|
"@tsconfig/node18-strictest-esm": "^1.0.1",
|
||||||
"esbuild": "^0.15.7",
|
"esbuild": "^0.15.7",
|
||||||
"esbuild-register": "^3.3.3",
|
"esbuild-register": "^3.3.3",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"extends": "@tsconfig/node18-strictest-esm/tsconfig.json",
|
"extends": "@tsconfig/node18-strictest-esm/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["dom", "dom.iterable"],
|
"lib": ["esnext", "dom", "dom.iterable"],
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"jsxImportSource": "solid-js/h",
|
"jsxImportSource": "solid-js/h",
|
||||||
"noPropertyAccessFromIndexSignature": false
|
"noPropertyAccessFromIndexSignature": false
|
||||||
|
|
|
@ -23,8 +23,7 @@ header h1 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
header h1 > a,
|
header h1 > :is(a, a:hover) {
|
||||||
header h1 > a:hover {
|
|
||||||
color: var(--nc-ac-1);
|
color: var(--nc-ac-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,3 +8,12 @@
|
||||||
border-top-color: var(--nc-bg-3);
|
border-top-color: var(--nc-bg-3);
|
||||||
box-shadow: 0 1px 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;
|
||||||
|
}
|
||||||
|
|
|
@ -1,17 +1,23 @@
|
||||||
import {
|
import {
|
||||||
$createLineBreakNode,
|
|
||||||
$createParagraphNode,
|
$createParagraphNode,
|
||||||
$createTextNode,
|
$createTextNode,
|
||||||
$getRoot,
|
$getRoot,
|
||||||
|
$isElementNode,
|
||||||
createEditor,
|
createEditor,
|
||||||
} from "lexical";
|
} from "lexical";
|
||||||
import { registerHistory, createEmptyHistoryState } from "@lexical/history";
|
import { registerHistory, createEmptyHistoryState } from "@lexical/history";
|
||||||
import { registerPlainText } from "@lexical/plain-text";
|
import {
|
||||||
|
$createHeadingNode,
|
||||||
|
HeadingNode,
|
||||||
|
registerRichText,
|
||||||
|
} from "@lexical/rich-text";
|
||||||
|
import { $createAutoLinkNode, AutoLinkNode } from "@lexical/link";
|
||||||
import { onCleanup, onMount } from "solid-js";
|
import { onCleanup, onMount } from "solid-js";
|
||||||
import type Pages from "../../protocol/pages";
|
import type Pages from "../../protocol/pages";
|
||||||
import "./editor.css";
|
import "./editor.css";
|
||||||
|
|
||||||
const editor = createEditor();
|
const urlMatcher = /https?:\/\/[^\s]+/;
|
||||||
|
const editor = createEditor({ nodes: [HeadingNode, AutoLinkNode] });
|
||||||
|
|
||||||
function ref(el: HTMLElement) {
|
function ref(el: HTMLElement) {
|
||||||
editor.setRootElement(el);
|
editor.setRootElement(el);
|
||||||
|
@ -25,25 +31,66 @@ export default (props: {
|
||||||
}) => {
|
}) => {
|
||||||
const initialEditorState = () => {
|
const initialEditorState = () => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
if (root.getFirstChild()) return;
|
const [title, ...lines] = props.text.split("\n");
|
||||||
const paragraphNode = $createParagraphNode();
|
const titleNode = $createHeadingNode("h2");
|
||||||
const text = props.text
|
titleNode.append($createTextNode(title));
|
||||||
.split("\n")
|
root.append(titleNode);
|
||||||
.flatMap((line) => [$createTextNode(line), $createLineBreakNode()])
|
for (const line of lines) {
|
||||||
.slice(0, -1);
|
const lineNode = $createParagraphNode();
|
||||||
paragraphNode.append(...text);
|
const indent = line.match(/^\s*/)?.[0]?.length ?? 0;
|
||||||
root.append(paragraphNode);
|
lineNode.setIndent(indent);
|
||||||
|
let text = line.slice(indent);
|
||||||
|
let match: RegExpMatchArray | null = null;
|
||||||
|
while ((match = text.match(urlMatcher))) {
|
||||||
|
const offset = text.slice(0, match.index!);
|
||||||
|
const input = match[0]!;
|
||||||
|
const link = $createAutoLinkNode(input);
|
||||||
|
link.append($createTextNode(match[0]));
|
||||||
|
lineNode.append($createTextNode(offset), link);
|
||||||
|
text = text.slice(offset.length + input.length);
|
||||||
|
}
|
||||||
|
lineNode.append($createTextNode(text));
|
||||||
|
root.append(lineNode);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
onCleanup(registerPlainText(editor, initialEditorState));
|
onCleanup(registerRichText(editor, initialEditorState));
|
||||||
onCleanup(registerHistory(editor, createEmptyHistoryState(), 333));
|
onCleanup(registerHistory(editor, createEmptyHistoryState(), 333));
|
||||||
onMount(() => {
|
|
||||||
onCleanup(
|
onCleanup(
|
||||||
editor.registerTextContentListener((text) => {
|
editor.registerUpdateListener(() =>
|
||||||
const [title] = text.split("\n");
|
editor.update(() => {
|
||||||
props.onUpdatePage({ id: props.id, title: title ?? "", text });
|
const root = $getRoot();
|
||||||
|
const defaultTitle = new Date()
|
||||||
|
.toLocaleDateString(navigator.language, {
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
})
|
})
|
||||||
);
|
.replaceAll("/", "-");
|
||||||
editor.focus();
|
const [titleNode, ...lineNodes] = root.getChildren();
|
||||||
|
const title =
|
||||||
|
titleNode?.getTextContent().trim() ||
|
||||||
|
(lineNodes.length === 0 ? "" : defaultTitle);
|
||||||
|
const lines = lineNodes.map((line) => {
|
||||||
|
const indent = $isElementNode(line) ? line.getIndent() : 0;
|
||||||
|
return `${" ".repeat(indent)}${line.getTextContent()}`;
|
||||||
});
|
});
|
||||||
return <article ref={ref} class="editor" contenteditable />;
|
const text = [title, ...lines].join("\n");
|
||||||
|
props.onUpdatePage({ id: props.id, title, text });
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
onMount(() => editor.focus());
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
ref={ref}
|
||||||
|
onclick={(e) => {
|
||||||
|
const el = e.target.parentElement;
|
||||||
|
if (el instanceof HTMLAnchorElement) {
|
||||||
|
window.open(el.href, "_blank", "noreferrer");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
class="editor"
|
||||||
|
contenteditable
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue