diff --git a/app/package.json b/app/package.json
index 8b768c3..96823fd 100644
--- a/app/package.json
+++ b/app/package.json
@@ -15,11 +15,13 @@
"@fontsource/inter": "^5.0.18",
"@mui/icons-material": "^5.15.18",
"@mui/material": "^5.15.18",
+ "@remark-embedder/core": "^3.0.3",
"@tanstack/react-query": "^5.37.1",
"@tauri-apps/api": "^1",
"@uidotdev/usehooks": "^2.4.1",
"@uiw/react-md-editor": "^4.0.4",
"classnames": "^2.5.1",
+ "date-fns": "^3.6.0",
"hast-util-to-jsx-runtime": "^2.3.0",
"hast-util-to-mdast": "^10.1.0",
"javascript-time-ago": "^2.5.10",
diff --git a/app/src-tauri/Cargo.toml b/app/src-tauri/Cargo.toml
index 3b81fb1..0d6c15c 100644
--- a/app/src-tauri/Cargo.toml
+++ b/app/src-tauri/Cargo.toml
@@ -11,7 +11,7 @@ edition = "2021"
tauri-build = { version = "1", features = [] }
[dependencies]
-tauri = { version = "1", features = [ "http-request", "shell-open"] }
+tauri = { version = "1", features = [ "app-all", "http-request", "shell-open"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
diff --git a/app/src-tauri/tauri.conf.json b/app/src-tauri/tauri.conf.json
index bf2af5b..600cbe4 100644
--- a/app/src-tauri/tauri.conf.json
+++ b/app/src-tauri/tauri.conf.json
@@ -7,11 +7,16 @@
},
"package": {
"productName": "panorama",
- "version": "0.0.0"
+ "version": "0.1.0"
},
"tauri": {
"allowlist": {
"all": false,
+ "app": {
+ "all": true,
+ "hide": true,
+ "show": true
+ },
"shell": {
"all": false,
"open": true
diff --git a/app/src/App.module.scss b/app/src/App.module.scss
index d2eb2d1..b52331b 100644
--- a/app/src/App.module.scss
+++ b/app/src/App.module.scss
@@ -8,9 +8,12 @@
height: 100%;
}
-.nodeContainer {
+.main {
flex-grow: 1;
+ display: flex;
+}
+.nodeContainer {
display: flex;
justify-items: stretch;
diff --git a/app/src/App.tsx b/app/src/App.tsx
index 07a2fea..6fe1e3a 100644
--- a/app/src/App.tsx
+++ b/app/src/App.tsx
@@ -2,6 +2,7 @@ import Header from "./components/Header";
import styles from "./App.module.scss";
import "@fontsource/inter";
+import "@fontsource/inter/700.css";
import "./global.scss";
import "katex/dist/katex.min.css";
import { useEffect, useState } from "react";
@@ -9,6 +10,7 @@ import NodeDisplay from "./components/NodeDisplay";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import TimeAgo from "javascript-time-ago";
import en from "javascript-time-ago/locale/en";
+import Sidebar from "./components/Sidebar";
const queryClient = new QueryClient();
@@ -41,7 +43,10 @@ function App() {
);
diff --git a/app/src/components/Header.module.scss b/app/src/components/Header.module.scss
index f6eb526..38f05fe 100644
--- a/app/src/components/Header.module.scss
+++ b/app/src/components/Header.module.scss
@@ -7,4 +7,26 @@
background: rgb(204, 201, 255);
background: linear-gradient(90deg, rgba(204, 201, 255, 1) 0%, rgba(255, 255, 255, 1) 100%);
+}
+
+.headerBorder {
+ height: 1px;
+ background: rgb(170, 166, 255);
+ background: linear-gradient(90deg, rgba(170, 166, 255, 1) 0%, rgba(255, 255, 255, 1) 100%);
+}
+
+.brand {
+ display: flex;
+ flex-direction: column;
+
+ .title {
+ font-size: 1.2em;
+ margin: 0;
+ }
+
+ .version {
+ font-size: .6em;
+ color: rgb(0, 0, 0, 0.5);
+ margin: 0;
+ }
}
\ No newline at end of file
diff --git a/app/src/components/Header.tsx b/app/src/components/Header.tsx
index a88315c..1f48b50 100644
--- a/app/src/components/Header.tsx
+++ b/app/src/components/Header.tsx
@@ -1,15 +1,34 @@
import styles from "./Header.module.scss";
import NoteAddIcon from "@mui/icons-material/NoteAdd";
import SearchBar from "./SearchBar";
+import { getVersion } from "@tauri-apps/api/app";
+import ListIcon from "@mui/icons-material/List";
+import { useSetAtom } from "jotai";
+import { sidebarExpandedAtom } from "./Sidebar";
+
+const version = await getVersion();
export default function Header() {
+ const setSidebarExpanded = useSetAtom(sidebarExpandedAtom);
return (
-
- Panorama
-
-
-
+ <>
+
+
+
+ Panorama
+ v{version}
+
+
+
+
+
+ >
);
}
diff --git a/app/src/components/NodeDisplay.tsx b/app/src/components/NodeDisplay.tsx
index c9c19db..0524454 100644
--- a/app/src/components/NodeDisplay.tsx
+++ b/app/src/components/NodeDisplay.tsx
@@ -26,7 +26,9 @@ export default function NodeDisplay({ id }: NodeDisplayProps) {
);
useEffect(() => {
- setTitle(data.title);
+ if (data) {
+ setTitle(data.title);
+ }
}, [data]);
const saveChangedTitle = useCallback(() => {
diff --git a/app/src/components/Sidebar.module.scss b/app/src/components/Sidebar.module.scss
new file mode 100644
index 0000000..8b46c91
--- /dev/null
+++ b/app/src/components/Sidebar.module.scss
@@ -0,0 +1,29 @@
+.sidebar {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+
+ background-color: rgba(204, 201, 255);
+ padding: 6px;
+
+ .item {
+ display: flex;
+ align-items: center;
+ padding: 6px;
+ font-size: 0.95em;
+ border-radius: 4px;
+
+ &:hover {
+ background-color: rgba(255, 255, 255, 0.2);
+ cursor: pointer;
+ }
+ }
+
+ &.expanded .item {
+ gap: 6px;
+ }
+
+ &.collapsed .item .label {
+ display: none;
+ }
+}
\ No newline at end of file
diff --git a/app/src/components/Sidebar.tsx b/app/src/components/Sidebar.tsx
new file mode 100644
index 0000000..6a359ec
--- /dev/null
+++ b/app/src/components/Sidebar.tsx
@@ -0,0 +1,32 @@
+import { atom, useAtomValue } from "jotai";
+import styles from "./Sidebar.module.scss";
+import classNames from "classnames";
+import EmailIcon from "@mui/icons-material/Email";
+import SettingsIcon from "@mui/icons-material/Settings";
+
+export const sidebarExpandedAtom = atom(false);
+
+export default function Sidebar() {
+ const sidebarExpanded = useAtomValue(sidebarExpandedAtom);
+
+ return (
+
+
+
+ Email
+
+
+
+
+
+
+ Settings
+
+
+ );
+}
diff --git a/app/src/components/nodes/JournalPage.module.scss b/app/src/components/nodes/JournalPage.module.scss
index a3badcc..7c9c0a4 100644
--- a/app/src/components/nodes/JournalPage.module.scss
+++ b/app/src/components/nodes/JournalPage.module.scss
@@ -2,7 +2,6 @@
flex-grow: 1;
display: flex;
flex-direction: column;
- gap: 12px;
}
.mdContent {
@@ -17,6 +16,12 @@
border-radius: 0;
}
+.dayIndicator {
+ background-color: lavender;
+ padding: 2px 12px;
+ font-size: 0.8em;
+}
+
.block {
padding: 12px;
diff --git a/app/src/components/nodes/JournalPage.tsx b/app/src/components/nodes/JournalPage.tsx
index 1f5befa..76d805a 100644
--- a/app/src/components/nodes/JournalPage.tsx
+++ b/app/src/components/nodes/JournalPage.tsx
@@ -1,10 +1,12 @@
-import { useEffect, useState } from "react";
-import MDEditor from "@uiw/react-md-editor";
+import { useEffect, useRef, useState } from "react";
+import MDEditor, { PreviewType } from "@uiw/react-md-editor";
import { usePrevious, useDebounce } from "@uidotdev/usehooks";
import { useQueryClient } from "@tanstack/react-query";
import styles from "./JournalPage.module.scss";
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
+import remarkEmbedder from "@remark-embedder/core";
+import { parse as parseDate, format as formatDate } from "date-fns";
export interface JournalPageProps {
id: string;
@@ -14,11 +16,13 @@ export interface JournalPageProps {
}
export default function JournalPage({ id, data }: JournalPageProps) {
+ const { day } = data;
const queryClient = useQueryClient();
const [value, setValue] = useState(() => data.content);
const valueToSave = useDebounce(value, 1000);
const previous = usePrevious(valueToSave);
const changed = valueToSave !== previous;
+ const [mode, setMode] = useState("preview");
useEffect(() => {
if (changed) {
@@ -45,12 +49,15 @@ export default function JournalPage({ id, data }: JournalPageProps) {
return (
+ {day && }
+
newValue && setValue(newValue)}
- preview="preview"
+ onChange={(newValue) => newValue !== undefined && setValue(newValue)}
+ preview={mode}
visibleDragbar={false}
+ onDoubleClick={() => setMode("live")}
previewOptions={{
remarkPlugins: [remarkMath],
rehypePlugins: [rehypeKatex],
@@ -59,3 +66,13 @@ export default function JournalPage({ id, data }: JournalPageProps) {
);
}
+
+function DayIndicator({ day }) {
+ const parsedDate = parseDate(day, "yyyy-MM-dd", new Date());
+ const formattedDate = formatDate(parsedDate, "PPPP");
+ return (
+
+ Journal entry for {formattedDate}
+
+ );
+}
diff --git a/app/src/global.scss b/app/src/global.scss
index d3827ea..e43dedc 100644
--- a/app/src/global.scss
+++ b/app/src/global.scss
@@ -12,4 +12,8 @@ html,
#root {
width: 100%;
height: 100%;
+}
+
+.spacer {
+ flex-grow: 1;
}
\ No newline at end of file
diff --git a/crates/panorama-daemon/src/mail.rs b/crates/panorama-daemon/src/mail.rs
new file mode 100644
index 0000000..e69de29
diff --git a/crates/panorama-daemon/src/main.rs b/crates/panorama-daemon/src/main.rs
index 79d675a..e0502e0 100644
--- a/crates/panorama-daemon/src/main.rs
+++ b/crates/panorama-daemon/src/main.rs
@@ -19,7 +19,7 @@ use std::fs;
use anyhow::Result;
use axum::{
http::Method,
- routing::{get, post},
+ routing::{get, post, put},
Router,
};
use cozo::DbInstance;
@@ -32,7 +32,7 @@ use crate::{
export::export,
journal::get_todays_journal_id,
migrations::run_migrations,
- node::{get_node, node_types, update_node},
+ node::{create_node, get_node, node_types, search_nodes, update_node},
};
#[derive(Clone)]
@@ -67,6 +67,8 @@ async fn main() -> Result<()> {
let app = Router::new()
.route("/", get(|| async { "Hello, World!" }))
.route("/export", get(export))
+ .route("/node", put(create_node))
+ .route("/node/search", get(search_nodes))
.route("/node/:id", get(get_node))
.route("/node/:id", post(update_node))
.route("/node/types", get(node_types))
diff --git a/crates/panorama-daemon/src/node.rs b/crates/panorama-daemon/src/node.rs
index aa44d9a..43c9f15 100644
--- a/crates/panorama-daemon/src/node.rs
+++ b/crates/panorama-daemon/src/node.rs
@@ -1,7 +1,7 @@
use std::collections::HashMap;
use axum::{
- extract::{Path, State},
+ extract::{Path, Query, State},
http::StatusCode,
Json,
};
@@ -163,3 +163,50 @@ pub async fn node_types() -> AppResult> {
]
})))
}
+
+pub async fn create_node() -> AppResult<()> {
+ Ok(())
+}
+
+#[derive(Deserialize)]
+pub struct SearchQuery {
+ query: String,
+}
+
+pub async fn search_nodes(
+ State(state): State,
+ Query(query): Query,
+) -> AppResult> {
+ let results = state.db.run_script(
+ "
+ ?[node_id, content, score] := ~journal:text_index {node_id, content, |
+ query: $q,
+ k: 10,
+ score_kind: 'tf_idf',
+ bind_score: score
+ }
+
+ :order -score
+ ",
+ btmap! {
+ "q".to_owned() => DataValue::from(query.query),
+ },
+ ScriptMutability::Immutable,
+ )?;
+
+ let results = results
+ .rows
+ .into_iter()
+ .map(|row| {
+ json!({
+ "node_id": row[0].get_str().unwrap(),
+ "content": row[1].get_str().unwrap(),
+ "score": row[2].get_float().unwrap(),
+ })
+ })
+ .collect::>();
+
+ Ok(Json(json!({
+ "results": results
+ })))
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ba5dcec..9a7c421 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,6 +26,9 @@ importers:
'@mui/material':
specifier: ^5.15.18
version: 5.15.18(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
+ '@remark-embedder/core':
+ specifier: ^3.0.3
+ version: 3.0.3
'@tanstack/react-query':
specifier: ^5.37.1
version: 5.39.0(react@18.3.1)
@@ -41,6 +44,9 @@ importers:
classnames:
specifier: ^2.5.1
version: 2.5.1
+ date-fns:
+ specifier: ^3.6.0
+ version: 3.6.0
hast-util-to-jsx-runtime:
specifier: ^2.3.0
version: 2.3.0
@@ -926,6 +932,19 @@ packages:
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
dev: false
+ /@remark-embedder/core@3.0.3:
+ resolution: {integrity: sha512-izeW4GT5A/NgArjATndg1KKumL7IHLPZFQODJ07vSEHgCOBq2caMlnuvFGD+7ZmF4NCZks/HhZsYhfF46TOK5w==}
+ engines: {node: '>=12', npm: '>=6'}
+ dependencies:
+ '@babel/runtime': 7.24.6
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ hast-util-from-parse5: 8.0.1
+ parse5: 7.1.2
+ unified: 11.0.4
+ unist-util-visit: 5.0.0
+ dev: false
+
/@rollup/rollup-android-arm-eabi@4.18.0:
resolution: {integrity: sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==}
cpu: [arm]
@@ -1524,6 +1543,10 @@ packages:
/csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+ /date-fns@3.6.0:
+ resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
+ dev: false
+
/debug@4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}