Skip to content

Commit 94a95d2

Browse files
author
ComputelessComputer
committed
store TipTap JSON as in-memory format, convert to markdown only at disk boundary
1 parent f91fbf6 commit 94a95d2

5 files changed

Lines changed: 34 additions & 26 deletions

File tree

src/components/journal/EditableNote.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import StarterKit from "@tiptap/starter-kit";
1414
import { forwardRef, useEffect, useImperativeHandle, useRef, } from "react";
1515
import { useDebounceCallback, } from "usehooks-ts";
1616
import "../editor/Editor.css";
17-
import { json2md, md2json, } from "../../lib/markdown";
17+
import { parseJsonContent, } from "../../lib/markdown";
1818
import { resolveAssetUrl, saveImage, } from "../../services/images";
1919
import { saveDailyNote, } from "../../services/storage";
2020
import type { DailyNote, } from "../../types/note";
@@ -44,8 +44,8 @@ const EditableNote = forwardRef<EditableNoteHandle, EditableNoteProps>(
4444
const onSaveRef = useRef(onSave,);
4545
onSaveRef.current = onSave;
4646

47-
const saveDebounced = useDebounceCallback((markdown: string,) => {
48-
const updated = { ...noteRef.current, content: markdown, };
47+
const saveDebounced = useDebounceCallback((jsonStr: string,) => {
48+
const updated = { ...noteRef.current, content: jsonStr, };
4949
if (onSaveRef.current) {
5050
onSaveRef.current(updated,);
5151
} else {
@@ -127,7 +127,7 @@ const EditableNote = forwardRef<EditableNoteHandle, EditableNoteProps>(
127127
},
128128
},),
129129
],
130-
content: md2json(note.content,),
130+
content: parseJsonContent(note.content,),
131131
editable: true,
132132
immediatelyRender: false,
133133
editorProps: {
@@ -163,7 +163,7 @@ const EditableNote = forwardRef<EditableNoteHandle, EditableNoteProps>(
163163
},
164164
},
165165
onUpdate: ({ editor, },) => {
166-
saveDebounced(json2md(editor.getJSON(),),);
166+
saveDebounced(JSON.stringify(editor.getJSON(),),);
167167
},
168168
},);
169169

@@ -182,7 +182,7 @@ const EditableNote = forwardRef<EditableNoteHandle, EditableNoteProps>(
182182

183183
useEffect(() => {
184184
if (!editor || editor.isDestroyed) return;
185-
const incoming = md2json(note.content,);
185+
const incoming = parseJsonContent(note.content,);
186186
if (!editor.isFocused) {
187187
editor.commands.setContent(incoming, { emitUpdate: false, },);
188188
}

src/lib/markdown.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,13 @@ export function json2md(json: JSONContent,): string {
106106
return "";
107107
}
108108
}
109+
110+
export function parseJsonContent(raw: string | undefined | null,): JSONContent {
111+
if (typeof raw !== "string" || !raw.trim()) return EMPTY_DOC;
112+
try {
113+
const parsed = JSON.parse(raw,);
114+
return isValidContent(parsed,) ? parsed : EMPTY_DOC;
115+
} catch {
116+
return EMPTY_DOC;
117+
}
118+
}

src/services/storage.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { exists, mkdir, readTextFile, writeTextFile, } from "@tauri-apps/plugin-fs";
2+
import { EMPTY_DOC, json2md, md2json, parseJsonContent, } from "../lib/markdown";
23
import { DailyNote, getDaysAgo, } from "../types/note";
34
import { resolveMarkdownImages, unresolveMarkdownImages, } from "./images";
45
import { getNoteDir, getNotePath, } from "./paths";
@@ -31,7 +32,8 @@ export async function saveDailyNote(note: DailyNote,): Promise<void> {
3132
const noteDir = await getNoteDir(note.date,);
3233
await ensureDir(noteDir,);
3334
const filepath = await getNotePath(note.date,);
34-
let body = unresolveMarkdownImages(note.content,);
35+
const json = parseJsonContent(note.content,);
36+
let body = unresolveMarkdownImages(json2md(json,),);
3537
if (!body.endsWith("\n",)) body += "\n";
3638
await writeTextFile(filepath, buildFrontmatter(note.city, body,),);
3739
}
@@ -46,14 +48,15 @@ export async function loadDailyNote(date: string,): Promise<DailyNote | null> {
4648

4749
const raw = await readTextFile(filepath,);
4850
const { city, body, } = parseFrontmatter(raw,);
49-
const content = await resolveMarkdownImages(body.replace(/&nbsp;/g, "",),);
51+
const resolved = await resolveMarkdownImages(body.replace(/&nbsp;/g, "",),);
52+
const content = JSON.stringify(md2json(resolved,),);
5053
return { date, content, city, };
5154
}
5255

5356
export async function createEmptyDailyNote(date: string,): Promise<DailyNote> {
5457
const note: DailyNote = {
5558
date,
56-
content: "",
59+
content: JSON.stringify(EMPTY_DOC,),
5760
};
5861

5962
await saveDailyNote(note,);

src/services/tasks.ts

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { json2md, md2json, parseJsonContent, } from "../lib/markdown";
12
import { getDaysAgo, getToday, } from "../types/note";
23
import type { DailyNote, } from "../types/note";
34
import { loadDailyNote, saveDailyNote, } from "./storage";
@@ -141,29 +142,27 @@ function prependTasks(content: string, tasks: TaskLine[],): string {
141142
*/
142143
export async function rolloverTasks(days: number = 30,): Promise<boolean> {
143144
const today = getToday();
144-
// Map from task text → TaskLine (preserves indent, deduplicates by text)
145145
const taskMap = new Map<string, TaskLine>();
146146
const modifiedNotes: DailyNote[] = [];
147147

148-
// Scan past notes
149148
for (let i = 1; i <= days; i++) {
150149
const date = getDaysAgo(i,);
151150
const note = await loadDailyNote(date,);
152151
if (!note || !note.content.trim()) continue;
152+
const markdown = json2md(parseJsonContent(note.content,),);
153+
if (!markdown.trim()) continue;
153154

154-
// 1. Extract unchecked tasks → move to today (preserving nesting)
155-
const { tasks, cleaned, } = extractUncheckedTasks(note.content,);
155+
const { tasks, cleaned, } = extractUncheckedTasks(markdown,);
156156
if (tasks.length > 0) {
157157
tasks.forEach((t,) => {
158158
if (!taskMap.has(t.text,)) {
159159
taskMap.set(t.text, t,);
160160
}
161161
},);
162-
modifiedNotes.push({ ...note, content: cleaned, },);
162+
modifiedNotes.push({ ...note, content: JSON.stringify(md2json(cleaned,),), },);
163163
}
164164

165-
// 2. Find checked recurring tasks that are due again
166-
const checkedRecurring = extractCheckedRecurringTasks(note.content,);
165+
const checkedRecurring = extractCheckedRecurringTasks(markdown,);
167166
for (const taskText of checkedRecurring) {
168167
const recurrence = parseRecurrence(taskText,)!;
169168
const nextDue = addDaysToDate(date, recurrence.intervalDays,);
@@ -175,19 +174,15 @@ export async function rolloverTasks(days: number = 30,): Promise<boolean> {
175174

176175
if (taskMap.size === 0) return false;
177176

178-
// Save cleaned past notes (unchecked tasks removed)
179177
await Promise.all(modifiedNotes.map((note,) => saveDailyNote(note,)),);
180-
181-
// Deduplicate against tasks already in today's note
182178
const todayNote = await loadDailyNote(today,);
183-
const todayContent = todayNote?.content ?? "";
184-
const existingTasks = extractAllTaskTexts(todayContent,);
179+
const todayMarkdown = todayNote ? json2md(parseJsonContent(todayNote.content,),) : "";
180+
const existingTasks = extractAllTaskTexts(todayMarkdown,);
185181
const newTasks = [...taskMap.values(),].filter((t,) => !existingTasks.has(t.text,));
186-
187182
if (newTasks.length === 0) return false;
188183

189-
const updated = prependTasks(todayContent, newTasks,);
190-
await saveDailyNote({ date: today, content: updated, },);
191-
184+
const updated = prependTasks(todayMarkdown, newTasks,);
185+
const updatedJson = JSON.stringify(md2json(updated,),);
186+
await saveDailyNote({ date: today, content: updatedJson, city: todayNote?.city, },);
192187
return true;
193188
}

src/types/note.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export interface DailyNote {
22
date: string; // ISO date string (YYYY-MM-DD)
3-
content: string; // markdown
3+
content: string; // TipTap JSON string (in-memory), markdown on disk
44
city?: string | null;
55
}
66

0 commit comments

Comments
 (0)