edit title

This commit is contained in:
Michael Zhang 2024-05-26 23:35:18 -05:00
parent ccebb53879
commit 7307f92f55
8 changed files with 182 additions and 10 deletions

View file

@ -24,15 +24,19 @@
"hast-util-to-mdast": "^10.1.0", "hast-util-to-mdast": "^10.1.0",
"javascript-time-ago": "^2.5.10", "javascript-time-ago": "^2.5.10",
"jotai": "^2.8.1", "jotai": "^2.8.1",
"katex": "^0.16.10",
"mdast-util-from-markdown": "^2.0.0", "mdast-util-from-markdown": "^2.0.0",
"mdast-util-to-markdown": "^2.1.0", "mdast-util-to-markdown": "^2.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-markdown": "^9.0.1", "react-markdown": "^9.0.1",
"react-time-ago": "^7.3.3", "react-time-ago": "^7.3.3",
"rehype-katex": "^7.0.0",
"remark": "^15.0.1", "remark": "^15.0.1",
"remark-math": "^6.0.0",
"remark-rehype": "^11.1.0", "remark-rehype": "^11.1.0",
"use-debounce": "^10.0.1", "use-debounce": "^10.0.1",
"uuidv7": "^1.0.0",
"vfile": "^6.0.1" "vfile": "^6.0.1"
}, },
"devDependencies": { "devDependencies": {

View file

@ -3,6 +3,7 @@ import styles from "./App.module.scss";
import "@fontsource/inter"; import "@fontsource/inter";
import "./global.scss"; import "./global.scss";
import "katex/dist/katex.min.css";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import NodeDisplay from "./components/NodeDisplay"; import NodeDisplay from "./components/NodeDisplay";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

View file

@ -23,7 +23,7 @@
.body { .body {
flex-grow: 1; flex-grow: 1;
padding: 12px; // padding: 12px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -31,7 +31,10 @@
.title { .title {
padding: 12px; padding: 12px;
border: none;
border-bottom: 1px solid lightgray; border-bottom: 1px solid lightgray;
outline: none;
font-size: 1.2em;
} }
.untitled { .untitled {

View file

@ -2,6 +2,7 @@ import { useQuery } from "@tanstack/react-query";
import styles from "./NodeDisplay.module.scss"; import styles from "./NodeDisplay.module.scss";
import ReactTimeAgo from "react-time-ago"; import ReactTimeAgo from "react-time-ago";
import JournalPage from "./nodes/JournalPage"; import JournalPage from "./nodes/JournalPage";
import { useCallback, useEffect, useState } from "react";
export interface NodeDisplayProps { export interface NodeDisplayProps {
id: string; id: string;
@ -19,6 +20,28 @@ export default function NodeDisplay({ id }: NodeDisplayProps) {
const { isSuccess, status, data } = query; const { isSuccess, status, data } = query;
const [isEditingTitle, setIsEditingTitle] = useState(false);
const [title, setTitle] = useState(() =>
isSuccess && data ? data.title : undefined,
);
useEffect(() => {
setTitle(data.title);
}, [data]);
const saveChangedTitle = useCallback(() => {
(async () => {
const resp = await fetch(`http://localhost:5195/node/${id}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ title: title }),
});
setIsEditingTitle(false);
})();
}, [title, id]);
return ( return (
<div className={styles.container}> <div className={styles.container}>
<div className={styles.header}> <div className={styles.header}>
@ -28,9 +51,31 @@ export default function NodeDisplay({ id }: NodeDisplayProps) {
<>ID {id}</> <>ID {id}</>
)} )}
</div> </div>
<div className={styles.title}> {isEditingTitle ? (
{data?.title ?? <span className={styles.untitled}>(untitled)</span>} <form
onSubmit={(evt) => {
evt.preventDefault();
saveChangedTitle();
}}
>
<input
className={styles.title}
type="text"
value={title}
onChange={(evt) => setTitle(evt.target.value)}
onBlur={() => saveChangedTitle()}
// biome-ignore lint/a11y/noAutofocus: <explanation>
autoFocus
/>
</form>
) : (
<div
className={styles.title}
onDoubleClick={() => setIsEditingTitle(true)}
>
{title ?? <span className={styles.untitled}>(untitled)</span>}
</div> </div>
)}
<div className={styles.body}> <div className={styles.body}>
{isSuccess ? ( {isSuccess ? (
<NodeDisplayLoaded id={id} data={data} /> <NodeDisplayLoaded id={id} data={data} />

View file

@ -13,6 +13,8 @@
.mdEditor { .mdEditor {
flex-grow: 1; flex-grow: 1;
border-radius: 0;
} }
.block { .block {

View file

@ -3,6 +3,8 @@ import MDEditor from "@uiw/react-md-editor";
import { usePrevious, useDebounce } from "@uidotdev/usehooks"; import { usePrevious, useDebounce } from "@uidotdev/usehooks";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import styles from "./JournalPage.module.scss"; import styles from "./JournalPage.module.scss";
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
export interface JournalPageProps { export interface JournalPageProps {
id: string; id: string;
@ -43,16 +45,16 @@ export default function JournalPage({ id, data }: JournalPageProps) {
return ( return (
<div data-color-mode="light" className={styles.container}> <div data-color-mode="light" className={styles.container}>
<details>
<summary>JSON</summary>
<pre>{JSON.stringify(data, null, 2)}</pre>
</details>
<MDEditor <MDEditor
value={value} value={value}
className={styles.mdEditor} className={styles.mdEditor}
onChange={(newValue) => newValue && setValue(newValue)} onChange={(newValue) => newValue && setValue(newValue)}
preview="preview" preview="preview"
visibleDragbar={false}
previewOptions={{
remarkPlugins: [remarkMath],
rehypePlugins: [rehypeKatex],
}}
/> />
</div> </div>
); );

View file

@ -75,6 +75,22 @@ pub async fn update_node(
let tx = state.db.multi_transaction(true); let tx = state.db.multi_transaction(true);
if let Some(title) = update_data.title {
let title = DataValue::from(title);
tx.run_script(
"
# Always update the time
?[ id, title ] <- [[ $node_id, $title ]]
:update node { id, title }
",
btmap! {
"node_id".to_owned() => node_id_data.clone(),
"title".to_owned() => title,
},
)?;
}
if let Some(extra_data) = update_data.extra_data { if let Some(extra_data) = update_data.extra_data {
let result = tx.run_script( let result = tx.run_script(
" "
@ -133,7 +149,7 @@ pub async fn update_node(
btmap! { btmap! {
"node_id".to_owned() => node_id_data, "node_id".to_owned() => node_id_data,
}, },
); )?;
tx.commit()?; tx.commit()?;

View file

@ -53,6 +53,9 @@ importers:
jotai: jotai:
specifier: ^2.8.1 specifier: ^2.8.1
version: 2.8.1(@types/react@18.3.3)(react@18.3.1) version: 2.8.1(@types/react@18.3.3)(react@18.3.1)
katex:
specifier: ^0.16.10
version: 0.16.10
mdast-util-from-markdown: mdast-util-from-markdown:
specifier: ^2.0.0 specifier: ^2.0.0
version: 2.0.0 version: 2.0.0
@ -71,15 +74,24 @@ importers:
react-time-ago: react-time-ago:
specifier: ^7.3.3 specifier: ^7.3.3
version: 7.3.3(javascript-time-ago@2.5.10)(react-dom@18.3.1)(react@18.3.1) version: 7.3.3(javascript-time-ago@2.5.10)(react-dom@18.3.1)(react@18.3.1)
rehype-katex:
specifier: ^7.0.0
version: 7.0.0
remark: remark:
specifier: ^15.0.1 specifier: ^15.0.1
version: 15.0.1 version: 15.0.1
remark-math:
specifier: ^6.0.0
version: 6.0.0
remark-rehype: remark-rehype:
specifier: ^11.1.0 specifier: ^11.1.0
version: 11.1.0 version: 11.1.0
use-debounce: use-debounce:
specifier: ^10.0.1 specifier: ^10.0.1
version: 10.0.1(react@18.3.1) version: 10.0.1(react@18.3.1)
uuidv7:
specifier: ^1.0.0
version: 1.0.0
vfile: vfile:
specifier: ^6.0.1 specifier: ^6.0.1
version: 6.0.1 version: 6.0.1
@ -1223,6 +1235,10 @@ packages:
'@types/unist': 3.0.2 '@types/unist': 3.0.2
dev: false dev: false
/@types/katex@0.16.7:
resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==}
dev: false
/@types/mdast@4.0.4: /@types/mdast@4.0.4:
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
dependencies: dependencies:
@ -1477,6 +1493,11 @@ packages:
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
dev: false dev: false
/commander@8.3.0:
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
engines: {node: '>= 12'}
dev: false
/convert-source-map@1.9.0: /convert-source-map@1.9.0:
resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
dev: false dev: false
@ -1678,6 +1699,23 @@ packages:
hast-util-is-element: 3.0.0 hast-util-is-element: 3.0.0
dev: false dev: false
/hast-util-from-dom@5.0.0:
resolution: {integrity: sha512-d6235voAp/XR3Hh5uy7aGLbM3S4KamdW0WEgOaU1YoewnuYw4HXb5eRtv9g65m/RFGEfUY1Mw4UqCc5Y8L4Stg==}
dependencies:
'@types/hast': 3.0.4
hastscript: 8.0.0
web-namespaces: 2.0.1
dev: false
/hast-util-from-html-isomorphic@2.0.0:
resolution: {integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==}
dependencies:
'@types/hast': 3.0.4
hast-util-from-dom: 5.0.0
hast-util-from-html: 2.0.1
unist-util-remove-position: 5.0.0
dev: false
/hast-util-from-html@2.0.1: /hast-util-from-html@2.0.1:
resolution: {integrity: sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==} resolution: {integrity: sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==}
dependencies: dependencies:
@ -2027,6 +2065,13 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/katex@0.16.10:
resolution: {integrity: sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==}
hasBin: true
dependencies:
commander: 8.3.0
dev: false
/lines-and-columns@1.2.4: /lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
dev: false dev: false
@ -2149,6 +2194,20 @@ packages:
- supports-color - supports-color
dev: false dev: false
/mdast-util-math@3.0.0:
resolution: {integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==}
dependencies:
'@types/hast': 3.0.4
'@types/mdast': 4.0.4
devlop: 1.1.0
longest-streak: 3.1.0
mdast-util-from-markdown: 2.0.0
mdast-util-to-markdown: 2.1.0
unist-util-remove-position: 5.0.0
transitivePeerDependencies:
- supports-color
dev: false
/mdast-util-mdx-expression@2.0.0: /mdast-util-mdx-expression@2.0.0:
resolution: {integrity: sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==} resolution: {integrity: sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==}
dependencies: dependencies:
@ -2332,6 +2391,18 @@ packages:
micromark-util-types: 2.0.0 micromark-util-types: 2.0.0
dev: false dev: false
/micromark-extension-math@3.0.0:
resolution: {integrity: sha512-iJ2Q28vBoEovLN5o3GO12CpqorQRYDPT+p4zW50tGwTfJB+iv/VnB6Ini+gqa24K97DwptMBBIvVX6Bjk49oyQ==}
dependencies:
'@types/katex': 0.16.7
devlop: 1.1.0
katex: 0.16.10
micromark-factory-space: 2.0.0
micromark-util-character: 2.1.0
micromark-util-symbol: 2.0.0
micromark-util-types: 2.0.0
dev: false
/micromark-factory-destination@2.0.0: /micromark-factory-destination@2.0.0:
resolution: {integrity: sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==} resolution: {integrity: sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==}
dependencies: dependencies:
@ -2736,6 +2807,18 @@ packages:
unist-util-visit: 5.0.0 unist-util-visit: 5.0.0
dev: false dev: false
/rehype-katex@7.0.0:
resolution: {integrity: sha512-h8FPkGE00r2XKU+/acgqwWUlyzve1IiOKwsEkg4pDL3k48PiE0Pt+/uLtVHDVkN1yA4iurZN6UES8ivHVEQV6Q==}
dependencies:
'@types/hast': 3.0.4
'@types/katex': 0.16.7
hast-util-from-html-isomorphic: 2.0.0
hast-util-to-text: 4.0.2
katex: 0.16.10
unist-util-visit-parents: 6.0.1
vfile: 6.0.1
dev: false
/rehype-minify-whitespace@6.0.0: /rehype-minify-whitespace@6.0.0:
resolution: {integrity: sha512-i9It4YHR0Sf3GsnlR5jFUKXRr9oayvEk9GKQUkwZv6hs70OH9q3OCZrq9PpLvIGKt3W+JxBOxCidNVpH/6rWdA==} resolution: {integrity: sha512-i9It4YHR0Sf3GsnlR5jFUKXRr9oayvEk9GKQUkwZv6hs70OH9q3OCZrq9PpLvIGKt3W+JxBOxCidNVpH/6rWdA==}
dependencies: dependencies:
@ -2833,6 +2916,17 @@ packages:
unist-util-visit: 5.0.0 unist-util-visit: 5.0.0
dev: false dev: false
/remark-math@6.0.0:
resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==}
dependencies:
'@types/mdast': 4.0.4
mdast-util-math: 3.0.0
micromark-extension-math: 3.0.0
unified: 11.0.4
transitivePeerDependencies:
- supports-color
dev: false
/remark-parse@11.0.0: /remark-parse@11.0.0:
resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
dependencies: dependencies:
@ -3096,6 +3190,11 @@ packages:
react: 18.3.1 react: 18.3.1
dev: false dev: false
/uuidv7@1.0.0:
resolution: {integrity: sha512-XkvPwTtSmYwxIE1FSYQTYg79zHL1ZWV5vM/Qyl9ahXCU8enOPPA4bTjzvafvYUB7l2+miv4EqK/qEe75cOXIdA==}
hasBin: true
dev: false
/vfile-location@5.0.2: /vfile-location@5.0.2:
resolution: {integrity: sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==} resolution: {integrity: sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==}
dependencies: dependencies: