vertex-notes 0.3.1 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -130
- package/bin/run.js +3 -3
- package/dist/{chunk-JGE6NM2D.js → chunk-23CKP3YP.js} +1 -2
- package/dist/{chunk-PBF5EE4Y.js → chunk-DRIWYEQE.js} +0 -1
- package/dist/{chunk-HJR4UBO3.js → chunk-QQO4JMNC.js} +2 -3
- package/dist/{chunk-QEOOUDO3.js → chunk-XJVEK2OB.js} +552 -47
- package/dist/commands/capture.js +3 -4
- package/dist/commands/daily.js +3 -4
- package/dist/commands/delete.js +3 -4
- package/dist/commands/edit.js +3 -8
- package/dist/commands/export.js +3 -4
- package/dist/commands/hello.js +1 -2
- package/dist/commands/help.js +1 -2
- package/dist/commands/howto.js +0 -1
- package/dist/commands/import.js +3 -4
- package/dist/commands/interactive.js +45 -4
- package/dist/commands/login.js +2 -3
- package/dist/commands/logout.js +1 -2
- package/dist/commands/new.js +3 -4
- package/dist/commands/notes.js +3 -4
- package/dist/commands/restore.js +3 -4
- package/dist/commands/search.js +3 -4
- package/dist/commands/status.js +3 -4
- package/dist/commands/syntax.js +0 -1
- package/dist/commands/tags.js +3 -4
- package/dist/commands/today.js +3 -4
- package/dist/commands/trash/empty.js +4 -5
- package/dist/commands/trash/index.js +3 -4
- package/dist/commands/view.js +3 -4
- package/dist/commands/whoami.js +1 -2
- package/dist/index.js +0 -1
- package/dist/lib/client.js +3 -4
- package/dist/lib/config.js +1 -2
- package/dist/lib/file-cleanup.js +2 -3
- package/package.json +56 -54
- package/dist/chunk-HJR4UBO3.js.map +0 -1
- package/dist/chunk-JGE6NM2D.js.map +0 -1
- package/dist/chunk-PBF5EE4Y.js.map +0 -1
- package/dist/chunk-QEOOUDO3.js.map +0 -1
- package/dist/commands/capture.js.map +0 -1
- package/dist/commands/daily.js.map +0 -1
- package/dist/commands/delete.js.map +0 -1
- package/dist/commands/edit.js.map +0 -1
- package/dist/commands/export.js.map +0 -1
- package/dist/commands/hello.js.map +0 -1
- package/dist/commands/help.js.map +0 -1
- package/dist/commands/howto.js.map +0 -1
- package/dist/commands/import.js.map +0 -1
- package/dist/commands/interactive.js.map +0 -1
- package/dist/commands/login.js.map +0 -1
- package/dist/commands/logout.js.map +0 -1
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/notes.js.map +0 -1
- package/dist/commands/restore.js.map +0 -1
- package/dist/commands/search.js.map +0 -1
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/syntax.js.map +0 -1
- package/dist/commands/tags.js.map +0 -1
- package/dist/commands/today.js.map +0 -1
- package/dist/commands/trash/empty.js.map +0 -1
- package/dist/commands/trash/index.js.map +0 -1
- package/dist/commands/view.js.map +0 -1
- package/dist/commands/whoami.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/client.js.map +0 -1
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/file-cleanup.js.map +0 -1
- package/dist/lib/md-to-tiptap.js +0 -250
- package/dist/lib/md-to-tiptap.js.map +0 -1
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
// ../core/dist/index.js
|
|
2
2
|
import { createClient } from "@supabase/supabase-js";
|
|
3
|
+
import { WebSocket } from "ws";
|
|
3
4
|
function createSupabaseClient(url, anonKey) {
|
|
4
5
|
return createClient(url, anonKey, {
|
|
5
6
|
auth: {
|
|
6
7
|
autoRefreshToken: true,
|
|
7
8
|
persistSession: true
|
|
9
|
+
},
|
|
10
|
+
realtime: {
|
|
11
|
+
transport: WebSocket
|
|
8
12
|
}
|
|
9
13
|
});
|
|
10
14
|
}
|
|
@@ -78,6 +82,17 @@ async function hardDeleteNote(client, noteId, onFilesRemoved) {
|
|
|
78
82
|
if (src && (b.type === "file" || b.type === "image") && !src.startsWith("data:")) {
|
|
79
83
|
fileUrls.push({ src, filesize: attrs.filesize ?? 0 });
|
|
80
84
|
}
|
|
85
|
+
if (tiptap?.type === "excalidrawBlock" && attrs.data) {
|
|
86
|
+
try {
|
|
87
|
+
const excalidrawData = JSON.parse(attrs.data);
|
|
88
|
+
for (const file of Object.values(excalidrawData.files ?? {})) {
|
|
89
|
+
if (file.dataURL?.startsWith("https://")) {
|
|
90
|
+
fileUrls.push({ src: file.dataURL, filesize: 0 });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
}
|
|
95
|
+
}
|
|
81
96
|
}
|
|
82
97
|
for (const file of fileUrls) {
|
|
83
98
|
try {
|
|
@@ -187,11 +202,28 @@ async function getUserTags(client, userId) {
|
|
|
187
202
|
if (error) throw error;
|
|
188
203
|
return data ?? [];
|
|
189
204
|
}
|
|
205
|
+
async function deleteTag(client, tagId) {
|
|
206
|
+
const { error } = await client.from("tags").delete().eq("id", tagId);
|
|
207
|
+
if (error) throw error;
|
|
208
|
+
}
|
|
190
209
|
async function addTagEdge(client, userId, parentId, childId) {
|
|
191
210
|
const { data, error } = await client.from("tag_edges").insert({ parent_id: parentId, child_id: childId, user_id: userId }).select().single();
|
|
192
211
|
if (error) throw error;
|
|
193
212
|
return data;
|
|
194
213
|
}
|
|
214
|
+
async function removeTagEdge(client, parentId, childId) {
|
|
215
|
+
const { error } = await client.from("tag_edges").delete().eq("parent_id", parentId).eq("child_id", childId);
|
|
216
|
+
if (error) throw error;
|
|
217
|
+
}
|
|
218
|
+
async function getTagChildren(client, tagId) {
|
|
219
|
+
const { data, error } = await client.from("tag_edges").select("child_id").eq("parent_id", tagId);
|
|
220
|
+
if (error) throw error;
|
|
221
|
+
if (!data || data.length === 0) return [];
|
|
222
|
+
const childIds = data.map((e) => e.child_id);
|
|
223
|
+
const { data: tags, error: tagErr } = await client.from("tags").select("*").in("id", childIds);
|
|
224
|
+
if (tagErr) throw tagErr;
|
|
225
|
+
return tags ?? [];
|
|
226
|
+
}
|
|
195
227
|
async function getTagDescendants(client, tagId) {
|
|
196
228
|
const { data, error } = await client.rpc("get_tag_descendants", {
|
|
197
229
|
root_tag_id: tagId
|
|
@@ -241,6 +273,29 @@ async function getBlocksForNote(client, noteId) {
|
|
|
241
273
|
return data ?? [];
|
|
242
274
|
}
|
|
243
275
|
var saveLocks = /* @__PURE__ */ new Map();
|
|
276
|
+
var tagPathCache = /* @__PURE__ */ new Map();
|
|
277
|
+
var TAG_PATH_REGEX = /#([a-zA-Z][a-zA-Z0-9_-]*(?:\/[a-zA-Z][a-zA-Z0-9_-]*)*)/g;
|
|
278
|
+
function extractAllTagPaths(blockRows) {
|
|
279
|
+
const paths = /* @__PURE__ */ new Set();
|
|
280
|
+
for (const block of blockRows) {
|
|
281
|
+
let m;
|
|
282
|
+
while ((m = TAG_PATH_REGEX.exec(block.content)) !== null) {
|
|
283
|
+
paths.add(m[1].toLowerCase());
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return paths;
|
|
287
|
+
}
|
|
288
|
+
function setsAreEqual(a, b) {
|
|
289
|
+
if (a.size !== b.size) return false;
|
|
290
|
+
for (const item of a) {
|
|
291
|
+
if (!b.has(item)) return false;
|
|
292
|
+
}
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
async function getNoteTagPathsFromDb(client, noteId) {
|
|
296
|
+
const { data } = await client.from("note_tags").select("tag_path").eq("note_id", noteId);
|
|
297
|
+
return new Set((data ?? []).map((r) => r.tag_path));
|
|
298
|
+
}
|
|
244
299
|
async function saveNoteContent(client, noteId, userId, tiptapJson) {
|
|
245
300
|
const existing = saveLocks.get(noteId);
|
|
246
301
|
const doSave = async () => {
|
|
@@ -268,35 +323,73 @@ async function _saveNoteContentInner(client, noteId, userId, tiptapJson) {
|
|
|
268
323
|
metadata: { tiptap: node }
|
|
269
324
|
}));
|
|
270
325
|
if (blocksToInsert.length === 0) return;
|
|
271
|
-
const {
|
|
272
|
-
|
|
326
|
+
const { data: existingBlocks } = await client.from("blocks").select("id").eq("note_id", noteId);
|
|
327
|
+
const oldBlockIds = (existingBlocks ?? []).map((b) => b.id);
|
|
273
328
|
const { error: insertError } = await client.from("blocks").insert(blocksToInsert);
|
|
274
329
|
if (insertError) throw insertError;
|
|
330
|
+
if (oldBlockIds.length > 0) {
|
|
331
|
+
const { error: deleteError } = await client.from("blocks").delete().in("id", oldBlockIds);
|
|
332
|
+
if (deleteError) throw deleteError;
|
|
333
|
+
}
|
|
275
334
|
const { data: insertedBlocks } = await client.from("blocks").select("id, content, type").eq("note_id", noteId).order("position");
|
|
276
335
|
const blockRows = insertedBlocks ?? [];
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if (
|
|
283
|
-
|
|
284
|
-
for (const tagName of leafTags) {
|
|
285
|
-
const tag = await getOrCreateTag(client, userId, tagName);
|
|
286
|
-
await addBlockTag(client, block.id, tag.id).catch(() => {
|
|
287
|
-
});
|
|
288
|
-
noteTagIds.add(tag.id);
|
|
336
|
+
const currentPaths = extractAllTagPaths(blockRows);
|
|
337
|
+
const prevPaths = tagPathCache.get(noteId);
|
|
338
|
+
if (prevPaths && setsAreEqual(currentPaths, prevPaths)) {
|
|
339
|
+
} else {
|
|
340
|
+
let oldPathsForCleanup = prevPaths;
|
|
341
|
+
if (!oldPathsForCleanup) {
|
|
342
|
+
oldPathsForCleanup = await getNoteTagPathsFromDb(client, noteId);
|
|
289
343
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
344
|
+
await client.from("block_tags").delete().in("block_id", blockRows.map((b) => b.id));
|
|
345
|
+
await client.from("note_tags").delete().eq("note_id", noteId);
|
|
346
|
+
const SKIP_TAG_TYPES = /* @__PURE__ */ new Set(["code", "mermaid", "math", "image", "divider", "chart", "file", "summary"]);
|
|
347
|
+
const insertedNoteTags = [];
|
|
348
|
+
for (const block of blockRows) {
|
|
349
|
+
if (SKIP_TAG_TYPES.has(block.type)) continue;
|
|
350
|
+
const tagPaths = extractTagPaths(block.content);
|
|
351
|
+
for (const tagPath of tagPaths) {
|
|
352
|
+
const segments = tagPath.split("/");
|
|
353
|
+
const leafName = segments[segments.length - 1];
|
|
354
|
+
const tag = await getOrCreateTag(client, userId, leafName);
|
|
355
|
+
await addBlockTag(client, block.id, tag.id).catch(() => {
|
|
356
|
+
});
|
|
357
|
+
insertedNoteTags.push({ note_id: noteId, tag_id: tag.id, tag_path: tagPath });
|
|
358
|
+
}
|
|
359
|
+
const scopedTags = extractScopedTags(block.content);
|
|
360
|
+
for (const { parent, child } of scopedTags) {
|
|
361
|
+
const parentTag = await getOrCreateTag(client, userId, parent);
|
|
362
|
+
const childTag = await getOrCreateTag(client, userId, child);
|
|
363
|
+
await addTagEdge(client, userId, parentTag.id, childTag.id).catch(() => {
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const seen = /* @__PURE__ */ new Set();
|
|
368
|
+
for (const nt of insertedNoteTags) {
|
|
369
|
+
if (seen.has(nt.tag_path)) continue;
|
|
370
|
+
seen.add(nt.tag_path);
|
|
371
|
+
await client.from("note_tags").upsert({ note_id: nt.note_id, tag_id: nt.tag_id, tag_path: nt.tag_path }, { onConflict: "note_id,tag_path" });
|
|
372
|
+
}
|
|
373
|
+
tagPathCache.set(noteId, currentPaths);
|
|
374
|
+
if (oldPathsForCleanup) {
|
|
375
|
+
const oldEdges = extractEdgesFromPaths(oldPathsForCleanup);
|
|
376
|
+
const newEdges = extractEdgesFromPaths(currentPaths);
|
|
377
|
+
const edgesToRemove = oldEdges.filter(
|
|
378
|
+
(oe) => !newEdges.some((ne) => ne.parent === oe.parent && ne.child === oe.child)
|
|
379
|
+
);
|
|
380
|
+
for (const { parent: parentName, child: childName } of edgesToRemove) {
|
|
381
|
+
const parentTag = await getOrCreateTag(client, userId, parentName);
|
|
382
|
+
const childTag = await getOrCreateTag(client, userId, childName);
|
|
383
|
+
await removeTagEdge(client, parentTag.id, childTag.id).catch(() => {
|
|
384
|
+
});
|
|
385
|
+
const { count: parentNoteTagCount } = await client.from("note_tags").select("*", { count: "exact", head: true }).eq("tag_id", parentTag.id);
|
|
386
|
+
const children = await getTagChildren(client, parentTag.id);
|
|
387
|
+
if (parentNoteTagCount === 0 && children.length === 0) {
|
|
388
|
+
await deleteTag(client, parentTag.id).catch(() => {
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
296
392
|
}
|
|
297
|
-
}
|
|
298
|
-
for (const tagId of noteTagIds) {
|
|
299
|
-
await client.from("note_tags").upsert({ note_id: noteId, tag_id: tagId }, { onConflict: "note_id,tag_id" });
|
|
300
393
|
}
|
|
301
394
|
const fullText = blockRows.map((b) => b.content).join(" ");
|
|
302
395
|
const wikiLinkTitles = extractWikiLinks(fullText);
|
|
@@ -319,10 +412,15 @@ function mapTiptapTypeToBlockType(tiptapType) {
|
|
|
319
412
|
mathBlock: "math",
|
|
320
413
|
calloutBlock: "callout",
|
|
321
414
|
queryBlock: "query",
|
|
415
|
+
summaryBlock: "summary",
|
|
322
416
|
horizontalRule: "divider",
|
|
323
417
|
fileBlock: "file",
|
|
324
418
|
linkPreview: "text",
|
|
325
|
-
inlineMath: "text"
|
|
419
|
+
inlineMath: "text",
|
|
420
|
+
table: "text",
|
|
421
|
+
tableRow: "text",
|
|
422
|
+
tableCell: "text",
|
|
423
|
+
tableHeader: "text"
|
|
326
424
|
};
|
|
327
425
|
return MAP[tiptapType] ?? "text";
|
|
328
426
|
}
|
|
@@ -332,21 +430,26 @@ function extractPlainText(node) {
|
|
|
332
430
|
if (!content) return "";
|
|
333
431
|
return content.map(extractPlainText).join("");
|
|
334
432
|
}
|
|
335
|
-
function
|
|
433
|
+
function extractTagPaths(text) {
|
|
336
434
|
const matches = text.match(/#([a-zA-Z][a-zA-Z0-9_-]*(?:\/[a-zA-Z][a-zA-Z0-9_-]*)*)/g);
|
|
337
435
|
if (!matches) return [];
|
|
338
436
|
const tags = /* @__PURE__ */ new Set();
|
|
339
437
|
for (const m of matches) {
|
|
340
|
-
|
|
341
|
-
if (full.includes("/")) {
|
|
342
|
-
const parts = full.split("/");
|
|
343
|
-
tags.add(parts[parts.length - 1]);
|
|
344
|
-
} else {
|
|
345
|
-
tags.add(full);
|
|
346
|
-
}
|
|
438
|
+
tags.add(m.slice(1).toLowerCase());
|
|
347
439
|
}
|
|
348
440
|
return [...tags];
|
|
349
441
|
}
|
|
442
|
+
function extractEdgesFromPaths(paths) {
|
|
443
|
+
const edges = [];
|
|
444
|
+
for (const path of paths) {
|
|
445
|
+
if (!path.includes("/")) continue;
|
|
446
|
+
const segments = path.split("/");
|
|
447
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
448
|
+
edges.push({ parent: segments[i], child: segments[i + 1] });
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return edges;
|
|
452
|
+
}
|
|
350
453
|
function extractScopedTags(text) {
|
|
351
454
|
const matches = text.match(/#([a-zA-Z][a-zA-Z0-9_-]*(?:\/[a-zA-Z][a-zA-Z0-9_-]*)+)/g);
|
|
352
455
|
if (!matches) return [];
|
|
@@ -365,7 +468,7 @@ function extractWikiLinks(text) {
|
|
|
365
468
|
return [...new Set(matches.map((m) => m.slice(2, -2)))];
|
|
366
469
|
}
|
|
367
470
|
async function fullTextSearch(client, userId, query, limit = 30) {
|
|
368
|
-
const { data, error } = await client.from("blocks").select("*, notes!inner(id, title)").eq("user_id", userId).is("deleted_at", null).
|
|
471
|
+
const { data, error } = await client.from("blocks").select("*, notes!inner(id, title)").eq("user_id", userId).is("deleted_at", null).ilike("content", `%${query}%`).limit(limit);
|
|
369
472
|
if (error) throw error;
|
|
370
473
|
return (data ?? []).map((row) => {
|
|
371
474
|
const notes = row.notes;
|
|
@@ -378,15 +481,35 @@ async function fullTextSearch(client, userId, query, limit = 30) {
|
|
|
378
481
|
});
|
|
379
482
|
}
|
|
380
483
|
async function searchByTag(client, userId, tagName, includeDescendants = true) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
484
|
+
if (tagName.includes("/")) {
|
|
485
|
+
const matchPattern = includeDescendants ? `${tagName}%` : tagName;
|
|
486
|
+
const { data: noteTagRows } = await client.from("note_tags").select("note_id").ilike("tag_path", matchPattern);
|
|
487
|
+
if (!noteTagRows || noteTagRows.length === 0) return [];
|
|
488
|
+
const noteIds = [...new Set(noteTagRows.map((r) => r.note_id))];
|
|
489
|
+
const { data: blocks } = await client.from("blocks").select("*, notes!inner(id, title)").in("note_id", noteIds).eq("user_id", userId).is("deleted_at", null);
|
|
490
|
+
return (blocks ?? []).map((row) => {
|
|
491
|
+
const notes = row.notes;
|
|
492
|
+
return {
|
|
493
|
+
block: row,
|
|
494
|
+
noteId: notes.id,
|
|
495
|
+
noteTitle: notes.title,
|
|
496
|
+
matchType: "tag"
|
|
497
|
+
};
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
const { data: matchedTags } = await client.from("tags").select("id").eq("user_id", userId).ilike("name", `${tagName}%`);
|
|
501
|
+
if (!matchedTags || matchedTags.length === 0) return [];
|
|
502
|
+
const allTagIds = /* @__PURE__ */ new Set();
|
|
503
|
+
for (const tag of matchedTags) {
|
|
504
|
+
if (includeDescendants) {
|
|
505
|
+
const descendants = await getTagDescendants(client, tag.id);
|
|
506
|
+
for (const id of descendants) allTagIds.add(id);
|
|
507
|
+
} else {
|
|
508
|
+
allTagIds.add(tag.id);
|
|
509
|
+
}
|
|
389
510
|
}
|
|
511
|
+
const tagIds = [...allTagIds];
|
|
512
|
+
if (tagIds.length === 0) return [];
|
|
390
513
|
const { data, error } = await client.from("block_tags").select("block_id, blocks!inner(*, notes!inner(id, title))").in("tag_id", tagIds);
|
|
391
514
|
if (error) throw error;
|
|
392
515
|
return (data ?? []).map((row) => {
|
|
@@ -419,8 +542,7 @@ function parseQuery(input) {
|
|
|
419
542
|
const textParts = [];
|
|
420
543
|
for (const part of parts) {
|
|
421
544
|
if (part.startsWith("#")) {
|
|
422
|
-
|
|
423
|
-
filter.tag = raw.includes("/") ? raw.split("/").pop() : raw;
|
|
545
|
+
filter.tag = part.slice(1).toLowerCase();
|
|
424
546
|
} else if (part.startsWith("type:")) {
|
|
425
547
|
filter.type = part.slice(5);
|
|
426
548
|
} else if (part.startsWith("status:")) {
|
|
@@ -439,7 +561,7 @@ function parseQuery(input) {
|
|
|
439
561
|
async function executeQuery(client, userId, input, limit = 30) {
|
|
440
562
|
const filter = parseQuery(input);
|
|
441
563
|
let results = [];
|
|
442
|
-
if (filter.tag) {
|
|
564
|
+
if (filter.tag !== void 0) {
|
|
443
565
|
results = await searchByTag(client, userId, filter.tag);
|
|
444
566
|
} else if (filter.type) {
|
|
445
567
|
results = await searchByType(client, userId, filter.type, limit);
|
|
@@ -458,6 +580,344 @@ async function executeQuery(client, userId, input, limit = 30) {
|
|
|
458
580
|
}
|
|
459
581
|
return results;
|
|
460
582
|
}
|
|
583
|
+
var HELP_SECTIONS = {
|
|
584
|
+
"shortcuts": `# Keyboard Shortcuts
|
|
585
|
+
|
|
586
|
+
## Global
|
|
587
|
+
- Cmd+B \u2014 Toggle sidebar
|
|
588
|
+
- Cmd+K \u2014 Command palette
|
|
589
|
+
- Cmd+P \u2014 Global search
|
|
590
|
+
- Cmd+G \u2014 Graph view
|
|
591
|
+
- Cmd+O \u2014 New note
|
|
592
|
+
- Cmd+S \u2014 Save snapshot (version checkpoint)
|
|
593
|
+
- Cmd+Shift+D \u2014 Open/create today's daily note
|
|
594
|
+
- F2 \u2014 Rename current note
|
|
595
|
+
- Cmd+Backspace \u2014 Delete current note (to Trash)
|
|
596
|
+
- Cmd+W \u2014 Close current tab
|
|
597
|
+
- Alt+Right \u2014 Next tab
|
|
598
|
+
- Alt+Left \u2014 Previous tab
|
|
599
|
+
- Cmd+\\ \u2014 Toggle focus mode
|
|
600
|
+
- Alt+Space \u2014 Quick capture to Inbox
|
|
601
|
+
- Cmd+/ \u2014 Open help
|
|
602
|
+
- Cmd+Shift+A \u2014 Toggle AI Chat panel
|
|
603
|
+
- Alt+Up \u2014 Move block up
|
|
604
|
+
- Alt+Down \u2014 Move block down
|
|
605
|
+
- Cmd+Z \u2014 Undo
|
|
606
|
+
- Cmd+Shift+Z \u2014 Redo
|
|
607
|
+
|
|
608
|
+
## Multi-Pane
|
|
609
|
+
- Cmd+D \u2014 Split right (side by side)
|
|
610
|
+
- Cmd+Shift+E \u2014 Split down (top/bottom)
|
|
611
|
+
- Cmd+J \u2014 Close active pane
|
|
612
|
+
- Cmd+] \u2014 Focus next pane
|
|
613
|
+
- Cmd+[ \u2014 Focus previous pane
|
|
614
|
+
|
|
615
|
+
## Text Formatting
|
|
616
|
+
- Cmd+B \u2014 Bold
|
|
617
|
+
- Cmd+I \u2014 Italic
|
|
618
|
+
- Cmd+E \u2014 Inline code
|
|
619
|
+
- Cmd+Shift+X \u2014 Strikethrough
|
|
620
|
+
- Cmd+Shift+M \u2014 Insert math block
|
|
621
|
+
|
|
622
|
+
## Block Navigation
|
|
623
|
+
- Cmd+Enter \u2014 Exit code/math/mermaid block \u2192 preview + new paragraph
|
|
624
|
+
- / \u2014 Open slash command menu
|
|
625
|
+
- # \u2014 Tag autocomplete (type to filter)
|
|
626
|
+
- [[ \u2014 Wiki link autocomplete (type to filter)
|
|
627
|
+
|
|
628
|
+
## Reminders
|
|
629
|
+
- Click clock icon on a todo \u2014 Set reminder
|
|
630
|
+
- Amber clock means a reminder is already set
|
|
631
|
+
- Sidebar \u2192 Reminders section \u2014 View all active reminders`,
|
|
632
|
+
"editor": `# Editor
|
|
633
|
+
|
|
634
|
+
## Markdown Shortcuts (type at start of line)
|
|
635
|
+
- # \u2014 Heading 1
|
|
636
|
+
- ## \u2014 Heading 2
|
|
637
|
+
- ### \u2014 Heading 3
|
|
638
|
+
- #### \u2014 Heading 4
|
|
639
|
+
- - \u2014 Bullet list
|
|
640
|
+
- 1. \u2014 Numbered list
|
|
641
|
+
- [ ] \u2014 Task/checkbox
|
|
642
|
+
- > \u2014 Blockquote
|
|
643
|
+
- --- \u2014 Horizontal divider
|
|
644
|
+
- \`\`\` \u2014 Code block
|
|
645
|
+
|
|
646
|
+
## Inline Syntax
|
|
647
|
+
- **text** \u2014 Bold
|
|
648
|
+
- *text* \u2014 Italic
|
|
649
|
+
- \`code\` \u2014 Inline code
|
|
650
|
+
- #tagname \u2014 Tag (renders as purple pill)
|
|
651
|
+
- #parent/child \u2014 Scoped tag (creates hierarchy)
|
|
652
|
+
- [[Note Name]] \u2014 Wiki link to another note
|
|
653
|
+
- $...$ \u2014 Inline math (LaTeX)
|
|
654
|
+
|
|
655
|
+
## Note Title
|
|
656
|
+
The first H1 heading in a note automatically becomes the note title. You can also rename via F2, double-click the title in the status bar, or Cmd+K \u2192 Rename Note.`,
|
|
657
|
+
"blocks": `# Block Types
|
|
658
|
+
|
|
659
|
+
## Slash Menu (type / at start of a line)
|
|
660
|
+
|
|
661
|
+
Available blocks:
|
|
662
|
+
- Text \u2014 Plain paragraph
|
|
663
|
+
- Heading 1\u20133 \u2014 Section headings
|
|
664
|
+
- Bullet List \u2014 Unordered list
|
|
665
|
+
- Numbered List \u2014 Ordered list
|
|
666
|
+
- Todo \u2014 Checkbox task item (supports nesting, has a clock icon for reminders)
|
|
667
|
+
- Code Block \u2014 Syntax-highlighted code with language selector and copy button
|
|
668
|
+
- Quote \u2014 Blockquote
|
|
669
|
+
- Divider \u2014 Horizontal rule
|
|
670
|
+
- Math \u2014 LaTeX equation. Edit the source, then press Cmd+Enter to preview the rendered formula (uses KaTeX).
|
|
671
|
+
- Callout \u2014 Info/Warning/Error/Tip box with a dropdown to switch type. Has colored backgrounds per variant.
|
|
672
|
+
- Mermaid \u2014 Diagram (flowchart, sequence, class, state, Gantt, pie, etc.). Edit the source, then press Cmd+Enter to preview.
|
|
673
|
+
- Table \u2014 Insert a 3x3 table with header row. Use Tab to navigate cells.
|
|
674
|
+
- Image \u2014 Upload, paste, or drag an image. Stored in Supabase Storage. Supports common formats.
|
|
675
|
+
- Excalidraw \u2014 Embedded whiteboard canvas. Opens a full drawing interface inline.
|
|
676
|
+
- File \u2014 PDF or audio file. Upload and view inline. Stored in R2 (Cloudflare).
|
|
677
|
+
- Summary \u2014 Collapsible preview block. Shows a snippet with a "Show more" toggle.
|
|
678
|
+
- Query \u2014 Live search block. Type a query and click Run to see matching results inline within the note.
|
|
679
|
+
|
|
680
|
+
## Query Block Syntax
|
|
681
|
+
- type:todo \u2014 All todo blocks
|
|
682
|
+
- type:todo status:open \u2014 Open todos only
|
|
683
|
+
- type:todo status:done \u2014 Completed todos
|
|
684
|
+
- type:code \u2014 All code blocks
|
|
685
|
+
- #tagname \u2014 Blocks tagged with that tag
|
|
686
|
+
- keyword \u2014 Full-text search
|
|
687
|
+
|
|
688
|
+
## Exiting Blocks
|
|
689
|
+
Press Cmd+Enter inside a code block, math block, or mermaid block to exit it, switch to preview mode, and create a new paragraph below.`,
|
|
690
|
+
"search": `# Search
|
|
691
|
+
|
|
692
|
+
## Global Search (Cmd+P)
|
|
693
|
+
Search across all your notes and blocks. Results show the block content with a link to the source note. Click a result to navigate.
|
|
694
|
+
|
|
695
|
+
### Filter Syntax
|
|
696
|
+
- keyword \u2014 Full-text search across all blocks
|
|
697
|
+
- #tagname \u2014 Find all blocks tagged with that tag (includes child tags via DAG)
|
|
698
|
+
- #parent/child \u2014 Searches by the leaf tag (e.g. #dsa/dp/tabular searches tabular)
|
|
699
|
+
- type:todo \u2014 All todo/checkbox blocks
|
|
700
|
+
- type:code \u2014 All code blocks
|
|
701
|
+
- type:heading \u2014 All headings
|
|
702
|
+
- status:open \u2014 Unchecked todos
|
|
703
|
+
- status:done \u2014 Checked todos
|
|
704
|
+
- Combine filters: type:todo status:open #work
|
|
705
|
+
|
|
706
|
+
## Command Palette (Cmd+K)
|
|
707
|
+
Quick access to actions and note switching. Type to fuzzy-search.
|
|
708
|
+
|
|
709
|
+
Available actions:
|
|
710
|
+
- New Note \u2014 Create a blank note
|
|
711
|
+
- Today's Daily Note \u2014 Open or create today's daily note
|
|
712
|
+
- Rename Note \u2014 Rename the current note
|
|
713
|
+
- Delete Note \u2014 Soft-delete to Trash
|
|
714
|
+
- Tag Extend \u2014 Set parent\u2192child relationship between tags
|
|
715
|
+
- Graph View \u2014 Open the visual knowledge graph
|
|
716
|
+
- [note titles] \u2014 Switch to any note by typing its name`,
|
|
717
|
+
"tags": `# Tags
|
|
718
|
+
|
|
719
|
+
Tags are a core organizational feature of Vertex. They work like folders/tags in other tools but are more flexible.
|
|
720
|
+
|
|
721
|
+
## Basic Usage
|
|
722
|
+
Type #tagname anywhere in your note text. Tags render as purple pills inline.
|
|
723
|
+
|
|
724
|
+
## Hierarchical / Scoped Tags
|
|
725
|
+
Use #parent/child to create hierarchical tags:
|
|
726
|
+
- #work/projects \u2014 creates a "projects" tag with "work" as its parent
|
|
727
|
+
- #work/projects/alpha \u2014 deeper nesting
|
|
728
|
+
- Multi-parent and multi-level are supported (it's a DAG, not a tree)
|
|
729
|
+
|
|
730
|
+
## Folder Structure
|
|
731
|
+
Tags create a folder-like hierarchy in the sidebar. Each tag path (e.g. "company/projects") gets its own folder node with correct note counts. This lets you organize notes like a file system \u2014 think of tags as folders:
|
|
732
|
+
- #design/mockups \u2014 all design mockup notes
|
|
733
|
+
- #engineering/frontend \u2014 frontend engineering notes
|
|
734
|
+
- #personal/finances \u2014 personal finance notes
|
|
735
|
+
|
|
736
|
+
## How Tags Work
|
|
737
|
+
- Tags are stored per-block (not per-note), so different blocks in the same note can have different tags
|
|
738
|
+
- The sidebar shows a "Tags" section listing all tags with color dots and lock toggles
|
|
739
|
+
- Tag autocomplete: type # and start typing to see suggestions from existing tags
|
|
740
|
+
- Tag Extend: Cmd+K \u2192 "Tag Extend" to manually create parent\u2192child edges between existing tags
|
|
741
|
+
- The tag DAG (directed acyclic graph) supports complex hierarchies \u2014 a child can have multiple parents
|
|
742
|
+
|
|
743
|
+
## Tag Locking
|
|
744
|
+
Lock any tag from the sidebar to prevent AI from reading notes under that tag. Locking walks the entire DAG \u2014 all descendant tags are also locked.
|
|
745
|
+
|
|
746
|
+
## Tag Colors
|
|
747
|
+
Each tag can have a custom color (shown as a dot in the sidebar).`,
|
|
748
|
+
"wiki-links": `# Wiki Links & Backlinks
|
|
749
|
+
|
|
750
|
+
## Creating Wiki Links
|
|
751
|
+
Type [[Note Name]] to create a link to another note. An autocomplete dropdown suggests existing notes as you type.
|
|
752
|
+
|
|
753
|
+
## Navigation
|
|
754
|
+
Click any wiki link to navigate to the linked note.
|
|
755
|
+
|
|
756
|
+
## Backlinks
|
|
757
|
+
The backlinks panel at the bottom of the editor shows all notes that link to the current note. Each backlink shows the source note title and you can click to navigate.`,
|
|
758
|
+
"version-history": `# Version History (Snapshots)
|
|
759
|
+
|
|
760
|
+
## Saving Snapshots
|
|
761
|
+
- Manual: Press Cmd+S to save a version checkpoint
|
|
762
|
+
- Auto: Snapshots are automatically created every 5 saves
|
|
763
|
+
|
|
764
|
+
## Viewing History
|
|
765
|
+
Click "Show version history" below the editor to see all snapshots. Each snapshot shows the timestamp.
|
|
766
|
+
|
|
767
|
+
## Comparing Versions
|
|
768
|
+
Select any two versions to see a side-by-side diff view (opens as a new tab). Additions and removals are highlighted at the block level.
|
|
769
|
+
|
|
770
|
+
## Rollback
|
|
771
|
+
Click "Restore" on any snapshot to rollback the note to that version with one click.`,
|
|
772
|
+
"graph-view": `# Graph View
|
|
773
|
+
|
|
774
|
+
Press Cmd+G to open the knowledge graph.
|
|
775
|
+
|
|
776
|
+
## What You See
|
|
777
|
+
- Gray dots represent notes
|
|
778
|
+
- Purple dots represent tags
|
|
779
|
+
- Lines show wiki link connections (between notes) and tag relationships
|
|
780
|
+
|
|
781
|
+
## Controls
|
|
782
|
+
- Hover over any dot to enlarge its label
|
|
783
|
+
- Click a note to navigate to it
|
|
784
|
+
- Mouse wheel to zoom
|
|
785
|
+
- Drag to pan
|
|
786
|
+
|
|
787
|
+
The graph uses ForceAtlas2 layout (powered by graphology + sigma.js) for natural-looking node positioning.`,
|
|
788
|
+
"quick-capture": `# Quick Capture
|
|
789
|
+
|
|
790
|
+
Press Alt+Space from anywhere in the app to open a quick capture input dialog.
|
|
791
|
+
|
|
792
|
+
Type anything and press Enter \u2014 the text is saved directly to your Inbox note. You stay on your current note (no navigation). This is useful for quickly jotting down ideas without context switching.`,
|
|
793
|
+
"multi-pane": `# Multi-Pane Layout
|
|
794
|
+
|
|
795
|
+
## Splitting
|
|
796
|
+
- Cmd+D \u2014 Split the editor side-by-side (horizontal split)
|
|
797
|
+
- Cmd+Shift+E \u2014 Split top/bottom (vertical split)
|
|
798
|
+
|
|
799
|
+
## Each Pane
|
|
800
|
+
Each pane has its own:
|
|
801
|
+
- Tab bar with open notes
|
|
802
|
+
- Editor with full functionality
|
|
803
|
+
- Status bar
|
|
804
|
+
|
|
805
|
+
## Controls
|
|
806
|
+
- Drag the purple divider to resize panes
|
|
807
|
+
- Cmd+J \u2014 Close the active pane
|
|
808
|
+
- Cmd+] \u2014 Focus the next pane
|
|
809
|
+
- Cmd+[ \u2014 Focus the previous pane
|
|
810
|
+
|
|
811
|
+
The active pane's editor is shared with the AI Chat panel, so the AI knows which note you're currently editing.`,
|
|
812
|
+
"reminders": `# Reminders & Notifications
|
|
813
|
+
|
|
814
|
+
## Setting Reminders
|
|
815
|
+
Every todo item (checkbox task) has a clock icon next to it. Click the clock to open the reminder picker. Set:
|
|
816
|
+
- Due date and time
|
|
817
|
+
- Repeat: once, daily, weekly, monthly, yearly, or custom
|
|
818
|
+
- "Custom" lets you pick specific days of the week (Mon, Tue, Wed, etc.) that fire every week, or specific dates of the month (1st\u201331st) that fire every month
|
|
819
|
+
- "Keep notifying until done": re-fires the reminder every N minutes until the task is checked off
|
|
820
|
+
|
|
821
|
+
## Visual Indicator
|
|
822
|
+
An amber-colored clock icon means a reminder is already set on that todo. Gray means no reminder.
|
|
823
|
+
|
|
824
|
+
## Delivery
|
|
825
|
+
- Push notifications are delivered via the browser's Web Push API (works on Chrome, Firefox, Edge)
|
|
826
|
+
- A Cloudflare R2 worker checks for due reminders every 5 minutes
|
|
827
|
+
- When a reminder fires, you get a browser notification \u2014 click it to open the note
|
|
828
|
+
- Notification title shows the task text
|
|
829
|
+
|
|
830
|
+
## Repeating Reminders
|
|
831
|
+
For daily/weekly/monthly/yearly reminders:
|
|
832
|
+
- After firing, the reminder auto-reschedules to the next interval
|
|
833
|
+
- The task is automatically unchecked so you get reminded again
|
|
834
|
+
- For example, a daily reminder at 9 AM will fire every day at 9 AM
|
|
835
|
+
|
|
836
|
+
## Notify Until Done
|
|
837
|
+
The reminder re-fires every N minutes (configurable delay) until you mark the task as done. Marking it done deletes the reminder and checks the task.
|
|
838
|
+
|
|
839
|
+
## Viewing Reminders
|
|
840
|
+
- Sidebar \u2192 Reminders section lists all active reminders
|
|
841
|
+
- Each entry shows: task text, note title, and relative due date
|
|
842
|
+
- You can mark a reminder done directly from the sidebar
|
|
843
|
+
|
|
844
|
+
## Stable Block IDs
|
|
845
|
+
Each task item has a persistent UUID (crypto.randomUUID()) that survives page reloads. This ensures reminders stay matched to their todo even after editing.`,
|
|
846
|
+
"ai-integration": `# AI Integration
|
|
847
|
+
|
|
848
|
+
## AI Chat Panel
|
|
849
|
+
Press Cmd+Shift+A or click the AI button in the status bar to toggle the AI Chat panel.
|
|
850
|
+
|
|
851
|
+
### Two Modes
|
|
852
|
+
1. Ask mode \u2014 Q&A and research. The AI can search your notes, list todos, find tagged blocks, read daily notes, and answer questions based on your data.
|
|
853
|
+
2. Edit mode \u2014 Structured inline content editing. The AI returns actions (insert, replace, delete, etc.) that appear as green/red suggestions in the editor. Accept or reject each change individually.
|
|
854
|
+
|
|
855
|
+
### How Ask Mode Works
|
|
856
|
+
The AI has 7 tools it can use to fetch information:
|
|
857
|
+
1. get_current_note \u2014 Read the currently open note's content
|
|
858
|
+
2. search_notes \u2014 Full-text search across all notes
|
|
859
|
+
3. get_note_by_title \u2014 Find a note by its title
|
|
860
|
+
4. get_note_content \u2014 Get full content of a specific note by its ID
|
|
861
|
+
5. get_recent_daily_notes \u2014 Get recent daily journal entries
|
|
862
|
+
6. list_all_notes \u2014 List all note titles and dates
|
|
863
|
+
7. list_todos \u2014 List tasks from notes, scoped to the current note + latest daily note by default
|
|
864
|
+
|
|
865
|
+
### How Edit Mode Works
|
|
866
|
+
The AI understands the block structure of your note. It can:
|
|
867
|
+
- Insert new blocks after/before a specific block
|
|
868
|
+
- Replace a block's content
|
|
869
|
+
- Delete a block
|
|
870
|
+
- Use special syntax: {summary}...{/summary}, {callout info}...{/callout}, {query value="..."}{/query}
|
|
871
|
+
|
|
872
|
+
Changes appear as inline suggestions (green for inserts, red strikethrough for deletions) with accept/reject buttons. The editor is locked while changes are pending.
|
|
873
|
+
|
|
874
|
+
### Providers & Settings
|
|
875
|
+
Supported AI providers: OpenRouter, Groq, OpenAI, Gemini, Cerebras
|
|
876
|
+
Configure API keys via the status bar gear icon \u2192 AI Settings. Keys are encrypted using AES-256-GCM and stored in the database.
|
|
877
|
+
|
|
878
|
+
## AI Privacy (Tag Locking)
|
|
879
|
+
Lock any tag from the sidebar to prevent AI from reading notes under that tag. The AI will receive "locked: true" with no content and will stop trying to access that note. Locking walks the full tag DAG \u2014 locking a parent locks all descendants.`,
|
|
880
|
+
"share-links": `# Share Links
|
|
881
|
+
|
|
882
|
+
Click the share button in the status bar to generate a public share link. Anyone with the link can view the note without signing in. The link uses an unguessable random token.
|
|
883
|
+
|
|
884
|
+
You can revoke share links at any time from the share dialog. Shared notes appear in a "Shared Notes" section in the sidebar.`,
|
|
885
|
+
"tag-locking": `# Tag Locking (AI Privacy)
|
|
886
|
+
|
|
887
|
+
## What It Does
|
|
888
|
+
Locking prevents AI from reading any note that has a locked tag. This is useful for sensitive notes that you don't want AI to access.
|
|
889
|
+
|
|
890
|
+
## How It Works
|
|
891
|
+
- Note-level: Toggle the lock in the status bar \u2014 locks the current note
|
|
892
|
+
- Tag-level: Lock a tag from the sidebar \u2014 ALL notes under that tag are locked
|
|
893
|
+
- The locking walks the entire tag DAG: if you lock #work, every note tagged with #work/projects, #work/meetings, etc. is also locked
|
|
894
|
+
- Locked notes show a lock icon in the tab bar, sidebar, and status bar
|
|
895
|
+
- The AI Chat panel shows a banner for locked notes
|
|
896
|
+
- When AI tries to access a locked note, it receives "NOTE LOCKED" and is instructed to stop immediately`,
|
|
897
|
+
"mobile": `# Mobile
|
|
898
|
+
|
|
899
|
+
On mobile devices, Vertex provides:
|
|
900
|
+
- A bottom toolbar with quick access to AI chat, undo, redo, quick capture, and search
|
|
901
|
+
- Touch-friendly interface with adjusted hit targets
|
|
902
|
+
- Sidebar slides in as a full-width overlay with backdrop
|
|
903
|
+
- Responsive design adapts to smaller screens`,
|
|
904
|
+
"trash": `# Trash
|
|
905
|
+
|
|
906
|
+
Deleted notes go to Trash (soft delete \u2014 they can be recovered).
|
|
907
|
+
|
|
908
|
+
- Open the sidebar \u2192 Trash section to see trashed notes
|
|
909
|
+
- Hover over a trashed note to reveal:
|
|
910
|
+
- \u21BA Restore \u2014 brings the note back
|
|
911
|
+
- \u2297 Permanently delete \u2014 deletes the note and all associated blocks, tags, links, and snapshots
|
|
912
|
+
- "Empty" button deletes all trashed notes permanently
|
|
913
|
+
- Notes in trash still count toward your total note count`,
|
|
914
|
+
"themes": `# Themes
|
|
915
|
+
|
|
916
|
+
Click the \u2600\uFE0F/\u{1F319} button in the top bar to toggle between dark and light mode. Your preference is saved in localStorage and persists across sessions.
|
|
917
|
+
|
|
918
|
+
The app uses CSS variables for theming (--foreground, --background, --accent, --border, etc.) giving a consistent look across all UI elements.`
|
|
919
|
+
};
|
|
920
|
+
var HELP_TOPICS = Object.keys(HELP_SECTIONS).sort();
|
|
461
921
|
function extractTextFromNode(node) {
|
|
462
922
|
if (typeof node.text === "string") return node.text;
|
|
463
923
|
const content = node.content;
|
|
@@ -571,6 +1031,25 @@ function tiptapNodeToMarkdown(node) {
|
|
|
571
1031
|
lines.push(``);
|
|
572
1032
|
break;
|
|
573
1033
|
}
|
|
1034
|
+
case "table": {
|
|
1035
|
+
const rows = content.map((row) => row.content ?? []);
|
|
1036
|
+
const colCount = Math.max(...rows.map((r) => r.length));
|
|
1037
|
+
const colWidths = Array.from(
|
|
1038
|
+
{ length: colCount },
|
|
1039
|
+
(_, ci) => Math.max(3, ...rows.map((r) => (r[ci] ? extractTextFromNode(r[ci]) : "").length))
|
|
1040
|
+
);
|
|
1041
|
+
rows.forEach((cells, ri) => {
|
|
1042
|
+
const line = "| " + colWidths.map((w, ci) => {
|
|
1043
|
+
const text = cells[ci] ? extractTextFromNode(cells[ci]) : "";
|
|
1044
|
+
return text.padEnd(w);
|
|
1045
|
+
}).join(" | ") + " |";
|
|
1046
|
+
lines.push(line);
|
|
1047
|
+
if (ri === 0) {
|
|
1048
|
+
lines.push("| " + colWidths.map((w) => "-".repeat(w)).join(" | ") + " |");
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
break;
|
|
1052
|
+
}
|
|
574
1053
|
case "linkPreview": {
|
|
575
1054
|
const url = attrs.url ?? "";
|
|
576
1055
|
lines.push(`> [!embed]`, `> ${url}`);
|
|
@@ -751,6 +1230,33 @@ function markdownToTiptap(md) {
|
|
|
751
1230
|
i++;
|
|
752
1231
|
continue;
|
|
753
1232
|
}
|
|
1233
|
+
const trimmedLine = line.trimStart();
|
|
1234
|
+
if (trimmedLine.startsWith("|") && lines[i + 1]?.trimStart().match(/^\|[\s\-|:]+\|/)) {
|
|
1235
|
+
const parseRow = (raw) => raw.trimStart().split("|").slice(1, -1).map((c) => c.trim());
|
|
1236
|
+
const headers = parseRow(lines[i]);
|
|
1237
|
+
i += 2;
|
|
1238
|
+
const rows = [];
|
|
1239
|
+
while (i < lines.length && lines[i].trimStart().startsWith("|")) {
|
|
1240
|
+
rows.push(parseRow(lines[i]));
|
|
1241
|
+
i++;
|
|
1242
|
+
}
|
|
1243
|
+
const makeCell = (text, isHeader) => ({
|
|
1244
|
+
type: isHeader ? "tableHeader" : "tableCell",
|
|
1245
|
+
attrs: { colspan: 1, rowspan: 1, colwidth: null },
|
|
1246
|
+
content: [{ type: "paragraph", content: parseInline(text) }]
|
|
1247
|
+
});
|
|
1248
|
+
nodes.push({
|
|
1249
|
+
type: "table",
|
|
1250
|
+
content: [
|
|
1251
|
+
{ type: "tableRow", content: headers.map((h) => makeCell(h, true)) },
|
|
1252
|
+
...rows.map((row) => ({
|
|
1253
|
+
type: "tableRow",
|
|
1254
|
+
content: headers.map((_, ci) => makeCell(row[ci] ?? "", false))
|
|
1255
|
+
}))
|
|
1256
|
+
]
|
|
1257
|
+
});
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
754
1260
|
if (line.match(/^!\[.*?\]\(.*?\)$/)) {
|
|
755
1261
|
const imgMatch = line.match(/^!\[(.*?)\]\((.*?)\)$/);
|
|
756
1262
|
if (imgMatch) {
|
|
@@ -809,7 +1315,7 @@ function markdownToTiptap(md) {
|
|
|
809
1315
|
continue;
|
|
810
1316
|
}
|
|
811
1317
|
const paraLines = [];
|
|
812
|
-
while (i < lines.length && lines[i].trim() !== "" && !lines[i].match(/^#{1,4}\s/) && !lines[i].match(/^```/) && !lines[i].match(/^\$\$/) && !lines[i].match(/^---\s*$/) && !lines[i].match(/^[-*]\s/) && !lines[i].match(/^\d+\.\s/) && !lines[i].match(/^>\s/) && !lines[i].match(/^!\[/)) {
|
|
1318
|
+
while (i < lines.length && lines[i].trim() !== "" && !lines[i].match(/^#{1,4}\s/) && !lines[i].match(/^```/) && !lines[i].match(/^\$\$/) && !lines[i].match(/^---\s*$/) && !lines[i].match(/^[-*]\s/) && !lines[i].match(/^\d+\.\s/) && !lines[i].match(/^>\s/) && !lines[i].match(/^!\[/) && !lines[i].startsWith("|")) {
|
|
813
1319
|
paraLines.push(lines[i]);
|
|
814
1320
|
i++;
|
|
815
1321
|
}
|
|
@@ -858,7 +1364,7 @@ function parseInline(text) {
|
|
|
858
1364
|
}
|
|
859
1365
|
return nodes.length > 0 ? nodes : [{ type: "text", text: text || " " }];
|
|
860
1366
|
}
|
|
861
|
-
var VERTEX_VERSION = "0.
|
|
1367
|
+
var VERTEX_VERSION = "0.3.4";
|
|
862
1368
|
|
|
863
1369
|
export {
|
|
864
1370
|
createSupabaseClient,
|
|
@@ -879,4 +1385,3 @@ export {
|
|
|
879
1385
|
markdownToTiptap,
|
|
880
1386
|
VERTEX_VERSION
|
|
881
1387
|
};
|
|
882
|
-
//# sourceMappingURL=chunk-QEOOUDO3.js.map
|