@geml/logseq-sync 2.0.6 → 2.0.8
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 +369 -359
- package/core/src/bridge.mjs +8 -8
- package/core/src/mapping.mjs +325 -243
- package/core/src/og-markdown.mjs +67 -0
- package/core/src/sync-engine.mjs +460 -449
- package/docs/how-it-works.svg +42 -42
- package/package.json +2 -2
- package/watcher/bin/live-roundtrip.mjs +131 -131
- package/watcher/bin/logseq-sync.mjs +918 -916
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// A GEML vault page → the Markdown a FILE-VERSION (OG) Logseq graph is made of.
|
|
2
|
+
//
|
|
3
|
+
// This is not "GEML converted to Markdown" — that is `geml <file> --to md`, and
|
|
4
|
+
// it belongs to the parser, not here. What this writes is Logseq's own dialect,
|
|
5
|
+
// so the output directory opens as a graph in the file version of the app:
|
|
6
|
+
//
|
|
7
|
+
// - one bullet per block, two spaces of indent per outline level
|
|
8
|
+
// - `id:: <uuid>` under a block that has one, which is how OG stores identity
|
|
9
|
+
// - block references as `((uuid))` — OG's spelling of what the DB version
|
|
10
|
+
// exports as `[[uuid]]` and the vault carries as a checked `[[#uuid]]`
|
|
11
|
+
// - page references `[[Some Page]]` untouched: both versions spell those the
|
|
12
|
+
// same way
|
|
13
|
+
//
|
|
14
|
+
// It is DELIBERATELY lossy, and one-way. Typed properties, tags, tables and
|
|
15
|
+
// data blocks have no OG equivalent, so they do not survive; the `.geml` tree
|
|
16
|
+
// stays the one that round-trips, and `restore` never reads this. What the
|
|
17
|
+
// Markdown mirror buys is a graph you can open in the old app, hand to a tool
|
|
18
|
+
// that reads nothing else, or keep as a copy your future self can still read.
|
|
19
|
+
|
|
20
|
+
const REF_TO_OG = /\[\[([^\[\]]*?)#([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})\]\]/g;
|
|
21
|
+
const LEVEL_CLASS = /^level-(\d+)$/;
|
|
22
|
+
|
|
23
|
+
/** Depth from the block's class, as the vault writes it; 1 when absent. */
|
|
24
|
+
function levelOf(node) {
|
|
25
|
+
for (const c of node.classes ?? []) {
|
|
26
|
+
const m = LEVEL_CLASS.exec(c);
|
|
27
|
+
if (m) {
|
|
28
|
+
const n = Number(m[1]);
|
|
29
|
+
if (Number.isInteger(n) && n >= 1) return n;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return typeof node.attrs?.["level"] === "number" ? node.attrs["level"] : 1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* One vault document → one OG Markdown page.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} gemlText A page document (pages/… or journals/…).
|
|
39
|
+
* @param {object} lib { parse, addressedUnits, sliceUnit } from @geml/geml.
|
|
40
|
+
* @returns {string} Markdown, LF-terminated. Empty when the document holds no
|
|
41
|
+
* blocks OG can represent — the caller writes no file for that.
|
|
42
|
+
*/
|
|
43
|
+
export function gemlToOgMarkdown(gemlText, lib) {
|
|
44
|
+
const { parse, addressedUnits, sliceUnit } = lib;
|
|
45
|
+
const nodes = parse(gemlText).children.filter((c) => c.kind === "block");
|
|
46
|
+
const units = [...addressedUnits(gemlText)].map((a) => a.unit).filter((u) => u.kind === "block");
|
|
47
|
+
|
|
48
|
+
const out = [];
|
|
49
|
+
nodes.forEach((node, i) => {
|
|
50
|
+
// Only the text blocks are the outline. The EDN ride-alongs (.page-meta,
|
|
51
|
+
// .page-extra, .block-meta) carry what OG has no shape for — skipped here,
|
|
52
|
+
// which is exactly the documented lossiness.
|
|
53
|
+
if (node.type !== "text") return;
|
|
54
|
+
const raw = sliceUnit(gemlText, units[i].span, "body");
|
|
55
|
+
const body = (raw.endsWith("\n") ? raw.slice(0, -1) : raw).replace(REF_TO_OG, (_m, _p, uuid) => `((${uuid}))`);
|
|
56
|
+
|
|
57
|
+
const indent = " ".repeat(levelOf(node) - 1);
|
|
58
|
+
const lines = body.split("\n");
|
|
59
|
+
out.push(`${indent}- ${lines[0] ?? ""}`);
|
|
60
|
+
// A block's own continuation lines sit under its bullet, indented with it,
|
|
61
|
+
// so the next bullet is unambiguous.
|
|
62
|
+
for (const line of lines.slice(1)) out.push(`${indent} ${line}`);
|
|
63
|
+
if (node.id) out.push(`${indent} id:: ${node.id}`);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return out.length > 0 ? out.join("\n") + "\n" : "";
|
|
67
|
+
}
|