diff --git a/app/package.json b/app/package.json
index 4176401..3572b85 100644
--- a/app/package.json
+++ b/app/package.json
@@ -18,6 +18,8 @@
"@tanstack/react-query": "^5.37.1",
"@tauri-apps/api": "^1",
"@uiw/react-md-editor": "^4.0.4",
+ "classnames": "^2.5.1",
+ "hast-util-to-jsx-runtime": "^2.3.0",
"hast-util-to-mdast": "^10.1.0",
"javascript-time-ago": "^2.5.10",
"jotai": "^2.8.1",
@@ -26,10 +28,14 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^9.0.1",
- "react-time-ago": "^7.3.3"
+ "react-time-ago": "^7.3.3",
+ "remark": "^15.0.1",
+ "remark-rehype": "^11.1.0",
+ "vfile": "^6.0.1"
},
"devDependencies": {
"@tauri-apps/cli": "^1",
+ "@types/mdast": "^4.0.4",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.2.1",
diff --git a/app/src/components/NodeDisplay.tsx b/app/src/components/NodeDisplay.tsx
index f4da92d..981ea5a 100644
--- a/app/src/components/NodeDisplay.tsx
+++ b/app/src/components/NodeDisplay.tsx
@@ -1,9 +1,6 @@
import { useQuery } from "@tanstack/react-query";
import styles from "./NodeDisplay.module.scss";
import ReactTimeAgo from "react-time-ago";
-import Markdown from "react-markdown";
-import MDEditor, { commands } from "@uiw/react-md-editor";
-import { useState } from "react";
import JournalPage from "./nodes/JournalPage";
export interface NodeDisplayProps {
@@ -19,6 +16,7 @@ export default function NodeDisplay({ id }: NodeDisplayProps) {
return json;
},
});
+
const { isSuccess, status, data } = query;
return (
@@ -31,7 +29,7 @@ export default function NodeDisplay({ id }: NodeDisplayProps) {
)}
- {data.title ?? (untitled)}
+ {data?.title ?? (untitled)}
{isSuccess ? (
diff --git a/app/src/components/nodes/JournalPage.module.scss b/app/src/components/nodes/JournalPage.module.scss
index 3622f1f..ae3a855 100644
--- a/app/src/components/nodes/JournalPage.module.scss
+++ b/app/src/components/nodes/JournalPage.module.scss
@@ -6,4 +6,23 @@
.mdEditor {
flex-grow: 1;
+}
+
+.block {
+ padding: 12px;
+
+ &:not(.isEditing) {
+ user-select: none;
+ -webkit-user-select: none;
+ }
+
+ &.isEditing {
+ background-color: rgb(235, 243, 246);
+ outline: 2px solid skyblue;
+ }
+
+ &:hover {
+ background-color: rgb(235, 243, 246);
+ cursor: text;
+ }
}
\ No newline at end of file
diff --git a/app/src/components/nodes/JournalPage.tsx b/app/src/components/nodes/JournalPage.tsx
index 84eef03..424251f 100644
--- a/app/src/components/nodes/JournalPage.tsx
+++ b/app/src/components/nodes/JournalPage.tsx
@@ -1,21 +1,59 @@
-import { createContext, useCallback, useContext, useState } from "react";
+import {
+ ReactNode,
+ createContext,
+ useCallback,
+ useContext,
+ useEffect,
+ useRef,
+ useState,
+} from "react";
+import { Fragment, jsx, jsxs } from "react/jsx-runtime";
import styles from "./JournalPage.module.scss";
import MDEditor from "@uiw/react-md-editor";
import Markdown from "react-markdown";
import { toMdast } from "hast-util-to-mdast";
+import { Node as MdastNode } from "mdast";
import { fromMarkdown } from "mdast-util-from-markdown";
import { toMarkdown } from "mdast-util-to-markdown";
+import { toJsxRuntime } from "hast-util-to-jsx-runtime";
+import remarkRehype from "remark-rehype";
+import { VFile } from "vfile";
+import { common } from "@mui/material/colors";
+import classNames from "classnames";
-const MDContext = createContext(null);
+interface MDContextValue {
+ isEditing: boolean;
+}
+
+// biome-ignore lint/style/noNonNullAssertion:
+const MDContext = createContext(null!);
+
+const emptyContent = { type: "root", children: [] };
export default function JournalPage({ id, data }) {
const [content, setContent] = useState(() => data.content);
const [isEditing, setIsEditing] = useState(() => false);
+ const [currentlyFocused, setCurrentlyFocused] = useState(
+ () => undefined,
+ );
- const tree = fromMarkdown(data.content);
- console.log("tree", tree);
+ useEffect(() => {
+ if (content === null) {
+ setContent(() => ({
+ type: "root",
+ children: [
+ { type: "paragraph", children: [{ type: "text", value: "" }] },
+ ],
+ }));
+ setCurrentlyFocused(".children[0]");
+ setIsEditing(true);
+ }
+ }, [content]);
const contextValue = { content, setContent, isEditing, setIsEditing };
+
+ const jsxContent = convertToJsx(content, { currentlyFocused });
+
return (
<>
@@ -23,7 +61,79 @@ export default function JournalPage({ id, data }) {
{JSON.stringify(data, null, 2)}
-
+
+
+ {jsxContent}
+
+
+
{JSON.stringify(content, null, 2)}
+
>
);
}
+
+interface ConvertToJsxOpts {
+ currentlyFocused?: string | undefined;
+ parent?: MdastNode | undefined;
+}
+
+function convertToJsx(
+ tree: MdastNode,
+ opts?: ConvertToJsxOpts | undefined,
+): ReactNode {
+ console.log("tree", tree);
+
+ if (tree === null) return;
+
+ const commonProps = {
+ node: tree,
+ parent: opts?.parent,
+ };
+
+ switch (tree.type) {
+ case "root":
+ return tree.children.map((child) =>
+ convertToJsx(child, { parent: tree }),
+ );
+
+ case "paragraph":
+ return ;
+
+ default:
+ throw new Error(`unhandled ${tree.type}`);
+ }
+}
+
+function Paragraph({ ...args }) {
+ // const { isEditing } = useContext(MDContext);
+ const [isEditing, setIsEditing] = useState(() => false);
+ const [localValue, setLocalValue] = useState(null);
+
+ const onDoubleClick = useCallback(() => {
+ if (!isEditing) {
+ setIsEditing(true);
+ }
+ }, [isEditing]);
+
+ const save = useCallback(() => {
+ console.log("saving!", localValue);
+ });
+
+ const onPaste = useCallback((evt) => {
+ console.log("pasted");
+ }, []);
+
+ return (
+
+ );
+}
diff --git a/app/src/components/nodes/convertToJsx.tsx b/app/src/components/nodes/convertToJsx.tsx
new file mode 100644
index 0000000..575d07e
--- /dev/null
+++ b/app/src/components/nodes/convertToJsx.tsx
@@ -0,0 +1,9 @@
+import { ReactNode } from "react";
+import { Nodes as MdastNodes } from "mdast";
+
+export function convertToJsx(tree: MdastNodes): ReactNode {
+ console.log("tree", tree);
+
+ switch (tree.type) {
+ }
+}
diff --git a/crates/panorama-daemon/src/error.rs b/crates/panorama-daemon/src/error.rs
index 96ab4a3..3227b3f 100644
--- a/crates/panorama-daemon/src/error.rs
+++ b/crates/panorama-daemon/src/error.rs
@@ -3,7 +3,6 @@ use axum::{
response::{IntoResponse, Response},
};
-
pub type AppResult = std::result::Result;
// Make our own error that wraps `anyhow::Error`.
@@ -13,6 +12,7 @@ pub struct AppError(miette::Report);
impl IntoResponse for AppError {
fn into_response(self) -> Response {
eprintln!("Encountered error: {}", self.0);
+ eprintln!("{:?}", self.0);
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", self.0),
diff --git a/crates/panorama-daemon/src/journal.rs b/crates/panorama-daemon/src/journal.rs
index 57675d7..0a1ee0f 100644
--- a/crates/panorama-daemon/src/journal.rs
+++ b/crates/panorama-daemon/src/journal.rs
@@ -14,7 +14,7 @@ pub async fn get_todays_journal_id(
let result = state.db.run_script(
"
- ?[node_id] := *journal_days[day, node_id], day = $day
+ ?[node_id] := *journal_day[day, node_id], day = $day
",
btmap! {
"day".to_owned() => today.clone().into(),
@@ -29,27 +29,28 @@ pub async fn get_todays_journal_id(
let uuid = Uuid::now_v7();
let node_id = uuid.to_string();
- let _result = state.db.run_script_fold_err(
+ state.db.run_script(
"
{
- ?[id, type] <- [[$node_id, 'panorama/journal/page']]
- :put node { id, type }
+ ?[id, title, type] <- [[$node_id, $title, 'panorama/journal/page']]
+ :put node { id, title, type }
}
{
- ?[node_id, content] <- [[$node_id, 'Default **content**']]
+ ?[node_id, content] <- [[$node_id, {}]]
:put journal { node_id => content }
}
{
?[day, node_id] <- [[$day, $node_id]]
- :put journal_days { day => node_id }
+ :put journal_day { day => node_id }
}
",
btmap! {
"node_id".to_owned() => node_id.clone().into(),
"day".to_owned() => today.clone().into(),
+ "title".to_owned() => today.clone().into(),
},
ScriptMutability::Mutable,
- );
+ )?;
return Ok(Json(json!({
"node_id": node_id
@@ -58,7 +59,8 @@ pub async fn get_todays_journal_id(
let node_id = result.rows[0][0].get_str().unwrap();
Ok(Json(json!({
- "node_id": node_id
+ "node_id": node_id,
+ "day": today,
})))
}
diff --git a/crates/panorama-daemon/src/migrations.rs b/crates/panorama-daemon/src/migrations.rs
index 3c799fc..fa4fd86 100644
--- a/crates/panorama-daemon/src/migrations.rs
+++ b/crates/panorama-daemon/src/migrations.rs
@@ -119,11 +119,11 @@ fn migration_01(db: &DbInstance) -> Result<()> {
{ :create node_refers_to { node_id: String => other_node_id: String } }
# Create journal type
- { :create journal { node_id: String => content: String } }
- { :create journal_days { day: String => node_id: String } }
+ { :create journal { node_id: String => content: Json } }
+ { :create journal_day { day: String => node_id: String } }
{
::fts create journal:text_index {
- extractor: content,
+ extractor: dump_json(content),
extract_filter: !is_null(content),
tokenizer: Simple,
filters: [Lowercase, Stemmer('english'), Stopwords('en')],
diff --git a/crates/panorama-daemon/src/node.rs b/crates/panorama-daemon/src/node.rs
index c1f4664..559f21d 100644
--- a/crates/panorama-daemon/src/node.rs
+++ b/crates/panorama-daemon/src/node.rs
@@ -19,12 +19,12 @@ pub async fn get_node(
j[content] := *journal{ node_id, content }, node_id = $node_id
j[content] := not *journal{ node_id }, node_id = $node_id, content = null
- jd[day] := *journal_days{ node_id, day }, node_id = $node_id
- jd[day] := not *journal_days{ node_id }, node_id = $node_id, day = null
+ jd[day] := *journal_day{ node_id, day }, node_id = $node_id
+ jd[day] := not *journal_day{ node_id }, node_id = $node_id, day = null
?[
- extra_data, content, day, created_at, updated_at, type
- ] := *node{ id, type, created_at, updated_at, extra_data },
+ extra_data, content, day, created_at, updated_at, type, title
+ ] := *node{ id, type, title, created_at, updated_at, extra_data },
j[content],
jd[day],
id = $node_id
@@ -52,6 +52,7 @@ pub async fn get_node(
"created_at": row[3].get_float(),
"updated_at": row[4].get_float(),
"type": row[5].get_str(),
+ "title": row[6].get_str(),
})),
))
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b2b0c4a..dbc21e5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -35,6 +35,12 @@ importers:
'@uiw/react-md-editor':
specifier: ^4.0.4
version: 4.0.4(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ classnames:
+ specifier: ^2.5.1
+ version: 2.5.1
+ hast-util-to-jsx-runtime:
+ specifier: ^2.3.0
+ version: 2.3.0
hast-util-to-mdast:
specifier: ^10.1.0
version: 10.1.0
@@ -62,10 +68,22 @@ importers:
react-time-ago:
specifier: ^7.3.3
version: 7.3.3(javascript-time-ago@2.5.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ remark:
+ specifier: ^15.0.1
+ version: 15.0.1
+ remark-rehype:
+ specifier: ^11.1.0
+ version: 11.1.0
+ vfile:
+ specifier: ^6.0.1
+ version: 6.0.1
devDependencies:
'@tauri-apps/cli':
specifier: ^1
version: 1.5.14
+ '@types/mdast':
+ specifier: ^4.0.4
+ version: 4.0.4
'@types/react':
specifier: ^18.2.15
version: 18.3.3
@@ -836,6 +854,9 @@ packages:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
+ classnames@2.5.1:
+ resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
+
clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
@@ -1440,6 +1461,9 @@ packages:
remark-stringify@11.0.0:
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
+ remark@15.0.1:
+ resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==}
+
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@@ -2316,6 +2340,8 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
+ classnames@2.5.1: {}
+
clsx@2.1.1: {}
color-convert@1.9.3:
@@ -3298,6 +3324,15 @@ snapshots:
mdast-util-to-markdown: 2.1.0
unified: 11.0.4
+ remark@15.0.1:
+ dependencies:
+ '@types/mdast': 4.0.4
+ remark-parse: 11.0.0
+ remark-stringify: 11.0.0
+ unified: 11.0.4
+ transitivePeerDependencies:
+ - supports-color
+
resolve-from@4.0.0: {}
resolve@1.22.8: