mirror of
https://github.com/kou029w/quot.git
synced 2025-01-19 00:18:09 +00:00
テキストの編集に対応
This commit is contained in:
parent
34f4d26c84
commit
e50e65c839
5 changed files with 227 additions and 21 deletions
158
app/package-lock.json
generated
158
app/package-lock.json
generated
|
@ -9,8 +9,10 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@exampledev/new.css": "^1.1.3",
|
"@exampledev/new.css": "^1.1.3",
|
||||||
|
"@lexical/plain-text": "^0.3.11",
|
||||||
"esbuild": "^0.15.5",
|
"esbuild": "^0.15.5",
|
||||||
"esbuild-register": "^3.3.3",
|
"esbuild-register": "^3.3.3",
|
||||||
|
"lexical": "^0.3.11",
|
||||||
"solid-js": "^1.4.8"
|
"solid-js": "^1.4.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -27,6 +29,90 @@
|
||||||
"resolved": "https://registry.npmjs.org/@exampledev/new.css/-/new.css-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@exampledev/new.css/-/new.css-1.1.3.tgz",
|
||||||
"integrity": "sha512-qhbGfqBRwUlM6MCSaJdUfjq86opNCMvM+6kVvs6S0kYhy0V8dKbe4rDMIklEJGuMc5QH5OuPjdCReu9I0tim2w=="
|
"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": {
|
"node_modules/@tsconfig/node18-strictest-esm": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tsconfig/node18-strictest-esm/-/node18-strictest-esm-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tsconfig/node18-strictest-esm/-/node18-strictest-esm-1.0.0.tgz",
|
||||||
|
@ -97,6 +183,11 @@
|
||||||
"esbuild": ">=0.12 <1"
|
"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": {
|
"node_modules/solid-js": {
|
||||||
"version": "1.4.8",
|
"version": "1.4.8",
|
||||||
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.4.8.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/@exampledev/new.css/-/new.css-1.1.3.tgz",
|
||||||
"integrity": "sha512-qhbGfqBRwUlM6MCSaJdUfjq86opNCMvM+6kVvs6S0kYhy0V8dKbe4rDMIklEJGuMc5QH5OuPjdCReu9I0tim2w=="
|
"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": {
|
"@tsconfig/node18-strictest-esm": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tsconfig/node18-strictest-esm/-/node18-strictest-esm-1.0.0.tgz",
|
"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==",
|
"integrity": "sha512-eFHOkutgIMJY5gc8LUp/7c+LLlDqzNi9T6AwCZ2WKKl3HmT+5ef3ZRyPPxDOynInML0fgaC50yszPKfPnjC0NQ==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
"lexical": {
|
||||||
|
"version": "0.3.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/lexical/-/lexical-0.3.11.tgz",
|
||||||
|
"integrity": "sha512-HZvQ2T3g0jWBX6MC/A0HY1N7NMvR+FrmTfR4vn6WnoCg56UPlkgIX3GKa6rCyGOAnOtykXRPJ831JWEKJUHalQ=="
|
||||||
|
},
|
||||||
"solid-js": {
|
"solid-js": {
|
||||||
"version": "1.4.8",
|
"version": "1.4.8",
|
||||||
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.4.8.tgz",
|
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.4.8.tgz",
|
||||||
|
|
|
@ -12,8 +12,10 @@
|
||||||
"packageManager": "npm@8.15.0",
|
"packageManager": "npm@8.15.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@exampledev/new.css": "^1.1.3",
|
"@exampledev/new.css": "^1.1.3",
|
||||||
|
"@lexical/plain-text": "^0.3.11",
|
||||||
"esbuild": "^0.15.5",
|
"esbuild": "^0.15.5",
|
||||||
"esbuild-register": "^3.3.3",
|
"esbuild-register": "^3.3.3",
|
||||||
|
"lexical": "^0.3.11",
|
||||||
"solid-js": "^1.4.8"
|
"solid-js": "^1.4.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
.editor {
|
.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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 type Pages from "../protocol/pages";
|
||||||
import "./editor.css";
|
import "./editor.css";
|
||||||
|
|
||||||
|
const editor = createEditor();
|
||||||
|
|
||||||
|
function ref(el: HTMLElement) {
|
||||||
|
editor.setRootElement(el);
|
||||||
|
}
|
||||||
|
|
||||||
export default (props: {
|
export default (props: {
|
||||||
id: number;
|
id: number;
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
onUpdatePage: (content: Pages.RequestContentPage) => void;
|
onUpdatePage: (content: Pages.RequestContentPage) => void;
|
||||||
}) => (
|
}) => {
|
||||||
<textarea
|
const initialEditorState = () => {
|
||||||
id={String(props.id)}
|
const root = $getRoot();
|
||||||
class="editor"
|
if (root.getFirstChild()) return;
|
||||||
autofocus
|
const paragraphNode = $createParagraphNode();
|
||||||
onInput={(e) => {
|
const textNode = $createTextNode(props.text);
|
||||||
const text = e.currentTarget.value;
|
paragraphNode.append(textNode);
|
||||||
const lines = text.split("\n");
|
root.append(paragraphNode);
|
||||||
props.onUpdatePage({ id: props.id, title: lines[0] ?? "", text });
|
};
|
||||||
e.currentTarget.setAttribute("rows", String(Math.max(2, lines.length)));
|
onCleanup(registerPlainText(editor, initialEditorState));
|
||||||
}}
|
onMount(() => {
|
||||||
></textarea>
|
onCleanup(
|
||||||
);
|
editor.registerTextContentListener((text) => {
|
||||||
|
const [title] = text.split("\n");
|
||||||
|
props.onUpdatePage({ id: props.id, title: title ?? "", text });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
editor.focus();
|
||||||
|
});
|
||||||
|
return <article ref={ref} class="editor" contenteditable />;
|
||||||
|
};
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { createResource } from "solid-js";
|
import { createResource } from "solid-js";
|
||||||
import type Pages from "../protocol/pages";
|
import type Pages from "../protocol/pages";
|
||||||
import Editor from "../components/editor";
|
import Editor from "../components/editor";
|
||||||
import Card from "../components/card";
|
|
||||||
import beforeunload from "../helpers/beforeunload";
|
import beforeunload from "../helpers/beforeunload";
|
||||||
import throttle from "../helpers/throttle";
|
import throttle from "../helpers/throttle";
|
||||||
|
|
||||||
|
@ -23,6 +22,14 @@ async function updatePage(
|
||||||
return res.ok;
|
return res.ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deletePage(id: number): Promise<boolean> {
|
||||||
|
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<Pages.ResponsePage> {
|
async function fetchPage(id: number): Promise<Pages.ResponsePage> {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
new URL(`/pages?id=eq.${id}`, import.meta.env.QUOT_API_URL)
|
new URL(`/pages?id=eq.${id}`, import.meta.env.QUOT_API_URL)
|
||||||
|
@ -32,13 +39,12 @@ async function fetchPage(id: number): Promise<Pages.ResponsePage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (props: { id: number }) => {
|
export default (props: { id: number }) => {
|
||||||
const [page, { refetch }] = createResource(props.id, fetchPage);
|
const [page] = createResource(props.id, fetchPage);
|
||||||
const throttledUpdate = throttle(
|
const throttledUpdate = throttle(
|
||||||
async (id: number, content: Pages.RequestContentPage) => {
|
async (id: number, content: Pages.RequestContentPage) => {
|
||||||
if (await updatePage(id, content)) {
|
if (await (content.text ? updatePage(id, content) : deletePage(id))) {
|
||||||
unblock();
|
unblock();
|
||||||
refetch();
|
window.history.replaceState({}, "", `/${content.text ? id : "new"}`);
|
||||||
window.history.replaceState({}, "", `/${id}`);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
intervalMs
|
intervalMs
|
||||||
|
@ -51,8 +57,17 @@ export default (props: { id: number }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<Editor id={props.id} onUpdatePage={onUpdatePage} />
|
{() =>
|
||||||
{() => <Card id={props.id} title="" text="" {...page()} />}
|
!page.loading && (
|
||||||
|
<Editor
|
||||||
|
id={props.id}
|
||||||
|
title=""
|
||||||
|
text=""
|
||||||
|
{...page()}
|
||||||
|
onUpdatePage={onUpdatePage}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue