@remigius42/morg 0.8.0 → 0.9.1

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.
Files changed (40) hide show
  1. package/README.md +22 -6
  2. package/dist/cli/args.d.ts +2 -0
  3. package/dist/cli/args.js +2 -0
  4. package/dist/cli/conversion.d.ts +3 -3
  5. package/dist/cli/conversion.js +4 -4
  6. package/dist/cli/flags.d.ts +1 -1
  7. package/dist/cli/flags.js +15 -1
  8. package/dist/cli/formats.d.ts +2 -1
  9. package/dist/cli/formats.js +1 -0
  10. package/dist/cli/presets.d.ts +13 -2
  11. package/dist/cli/presets.js +17 -3
  12. package/dist/cli.js +4 -5
  13. package/dist/config.d.ts +9 -4
  14. package/dist/config.js +2 -0
  15. package/dist/conversionOptions.d.ts +19 -3
  16. package/dist/conversionOptions.js +61 -0
  17. package/dist/core/frontmatterBlock.d.ts +7 -0
  18. package/dist/core/frontmatterBlock.js +7 -2
  19. package/dist/core/lineSyntax.d.ts +13 -0
  20. package/dist/core/lineSyntax.js +15 -2
  21. package/dist/core/mdastToUniorg/blocks.js +15 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/markdownToOrg.js +36 -18
  24. package/dist/normalize.js +16 -2
  25. package/dist/options.d.ts +3 -7
  26. package/dist/orgToMarkdown.js +13 -9
  27. package/dist/presets/hooks.d.ts +49 -0
  28. package/dist/presets/hooks.js +72 -0
  29. package/dist/presets/logseq.js +187 -29
  30. package/dist/presets/logseqOutline.d.ts +29 -4
  31. package/dist/presets/logseqOutline.js +308 -92
  32. package/dist/presets/logseqVanillaMarkdown.d.ts +9 -0
  33. package/dist/presets/logseqVanillaMarkdown.js +295 -0
  34. package/dist/presets/obsidian.js +4 -2
  35. package/dist/presets/registry.d.ts +1 -1
  36. package/dist/presets/registry.js +5 -3
  37. package/dist/presets/sides.d.ts +40 -0
  38. package/dist/presets/sides.js +42 -0
  39. package/dist/presets/types.d.ts +49 -16
  40. package/package.json +2 -2
@@ -1,5 +1,9 @@
1
1
  import { consumesBracedScripts } from "../core/bracedScripts.js";
2
+ import { mayBeLineSyntax, readsAsLineSyntax } from "../core/lineSyntax.js";
3
+ import { positionParser, tryParse } from "../core/render.js";
4
+ import { ZERO_WIDTH_SPACE } from "../core/markupBoundary.js";
2
5
  import { isDrawerStart, isOrgBlockStart, orgElementEnd } from "../core/passthroughSource.js";
6
+ import { readVanillaMarkdownOutline } from "./logseqVanillaMarkdown.js";
3
7
  // Logseq stores a page as an outline of blocks, each block a content
4
8
  // string it parses on its own: org writes a block as its level's stars,
5
9
  // a space and the content (an empty block as the bare stars), Markdown
@@ -14,6 +18,7 @@ const MD_PROPERTY_RE = /^([\w.-]+)::(?: (.*))?$/;
14
18
  // a repeated task's log line, which Logseq bullets per format
15
19
  const STATE_LINE_RE = /^[-*] (?=State ")/;
16
20
  const MD_HEADING_RE = /^(#{1,6})(?: (.*))?$/;
21
+ const HEADLINE_RE = /^\*+ /;
17
22
  const FENCE_RE = /^\s*(?:```|~~~)/;
18
23
  // md→org adds it for the text's bare `_` and `^`, which a block's
19
24
  // content holds as Logseq writes it
@@ -106,68 +111,95 @@ function convertPage(lines, convert, preset) {
106
111
  ? [`${convert(lines.join("\n"), preset).replace(/\n+$/, "")}\n`]
107
112
  : [];
108
113
  }
109
- /**
110
- * Converts a Logseq org page to Logseq Markdown, block by block.
111
- * @param org The org page.
112
- * @param convert The core's fragment converter.
113
- * @param presets The presets for the page properties and for a block.
114
- * @returns The Markdown page.
115
- */
116
- export function orgOutlineToMarkdown(org, convert, presets) {
117
- const { page, blocks } = splitBlocks(org.replace(/\r?\n$/, "").split(/\r?\n/), line => {
118
- const match = ORG_BLOCK_RE.exec(line);
119
- return match ? [match[1]?.length ?? 0, match[2] ?? ""] : null;
120
- });
121
- return [
122
- ...convertPage(page, convert, presets.page),
123
- ...blocks.map(block => orgBlockToMarkdown(block, convert, presets.block))
124
- ]
125
- .join("\n")
126
- .concat("\n");
127
- }
128
114
  // org→md reads a block's bare `_` and `^` as text, as md→org writes
129
115
  // them without the setting that would say so
130
116
  function withBracedScripts(lines) {
131
117
  const braced = [BRACED_SCRIPTS_LINE, ...lines];
132
118
  return consumesBracedScripts(braced.join("\n")) ? braced : lines;
133
119
  }
134
- function orgBlockToMarkdown(block, convert, preset) {
135
- const [first = "", ...rest] = block.lines;
136
- // content that starts with the block's properties puts their drawer
137
- // on the headline line; its heading level is then a property too
120
+ // Logseq org: a block's properties drawer, as properties; a heading
121
+ // level is one of them, but for content that starts with the drawer,
122
+ // unless written below an empty title (Vanilla org), which both look
123
+ // alike in
124
+ function readOrgBlock({ level, lines }, joined = false) {
125
+ const [first = "", ...rest] = lines;
138
126
  const metaFirst = isDrawerStart(first);
139
- const { meta, body } = takeMeta(metaFirst ? block.lines : rest, () => false);
140
- let heading = "";
141
- const metaLines = meta.flatMap(lines => {
142
- if (lines[0] !== ":PROPERTIES:") {
143
- return logbook(lines, "*");
127
+ const { meta: groups, body } = takeMeta(metaFirst ? lines : rest, () => false);
128
+ let heading = 0;
129
+ const meta = groups.flatMap((group) => {
130
+ if (group[0] !== ":PROPERTIES:") {
131
+ return [{ lines: group }];
144
132
  }
145
- return lines.slice(1, -1).flatMap(line => {
133
+ return group.slice(1, -1).flatMap(line => {
146
134
  const [, key = "", value = ""] = ORG_PROPERTY_RE.exec(line) ?? [];
147
- if (!metaFirst && key === "heading" && /^[1-6]$/.test(value)) {
148
- heading = "#".repeat(Number(value));
135
+ if ((!metaFirst || joined) &&
136
+ key === "heading" &&
137
+ /^[1-6]$/.test(value)) {
138
+ heading = Number(value);
149
139
  return [];
150
140
  }
151
- return [`${key}::${value ? ` ${value}` : ""}`];
141
+ return [{ key, value }];
152
142
  });
153
143
  });
154
- const content = convertContent(withBracedScripts(metaFirst ? body : [first, ...body]), convert, preset);
155
- const [title = "", ...more] = arrange(metaFirst, metaLines, content);
156
- const head = [heading, title].filter(Boolean).join(" ");
157
- const indent = "\t".repeat(block.level - 1);
158
- return [
159
- `${indent}-${head ? ` ${head}` : ""}`,
160
- ...more.map(line => `${indent} ${line}`)
161
- ].join("\n");
144
+ return {
145
+ level,
146
+ heading,
147
+ metaFirst,
148
+ meta,
149
+ content: metaFirst ? body : [first, ...body]
150
+ };
162
151
  }
163
- /**
164
- * Converts a Logseq Markdown page to Logseq org, block by block.
165
- * @param markdown The Markdown page.
166
- * @param convert The core's fragment converter.
167
- * @param presets The presets for the page properties and for a block.
168
- * @returns The org page.
169
- */
170
- export function markdownOutlineToOrg(markdown, convert, presets) {
152
+ function readOrgOutline(org, vanilla) {
153
+ const { page, blocks } = splitBlocks(org.replace(/\r?\n$/, "").split(/\r?\n/), line => {
154
+ const match = ORG_BLOCK_RE.exec(line);
155
+ return match ? [match[1]?.length ?? 0, match[2] ?? ""] : null;
156
+ });
157
+ return {
158
+ page,
159
+ blocks: blocks.map(({ level, lines }) => {
160
+ // Vanilla org: a first line written below an empty title, as it
161
+ // starts an element, is the block's first line again
162
+ const [title, ...rest] = lines;
163
+ const joined = vanilla && title === "" && startsElement(rest);
164
+ return readOrgBlock({ level, lines: joined ? rest : lines }, joined);
165
+ })
166
+ };
167
+ }
168
+ // a continuation line sits two spaces inside its bullet
169
+ function dedent(line, level) {
170
+ const indent = `${"\t".repeat(level - 1)} `;
171
+ return line.startsWith(indent)
172
+ ? line.slice(indent.length)
173
+ : line.trim()
174
+ ? line
175
+ : "";
176
+ }
177
+ // `## title`: the heading level and the title
178
+ function mdHeading(line) {
179
+ const match = MD_HEADING_RE.exec(line);
180
+ return match ? [match[1]?.length ?? 0, match[2] ?? ""] : [0, line];
181
+ }
182
+ function readMarkdownBlock({ level, lines: source }) {
183
+ const [first = "", ...rest] = source;
184
+ const lines = [first, ...rest.map(line => dedent(line, level))];
185
+ const metaFirst = MD_PROPERTY_RE.test(first);
186
+ const [heading, titleLine] = metaFirst ? [0, ""] : mdHeading(first);
187
+ const { meta: groups, body } = takeMeta(metaFirst ? lines : lines.slice(1), line => MD_PROPERTY_RE.test(line));
188
+ const meta = groups.map((group) => {
189
+ const property = MD_PROPERTY_RE.exec(group[0] ?? "");
190
+ return property
191
+ ? { key: property[1] ?? "", value: property[2] ?? "" }
192
+ : { lines: group };
193
+ });
194
+ return {
195
+ level,
196
+ heading,
197
+ metaFirst,
198
+ meta,
199
+ content: metaFirst ? body : [titleLine, ...body]
200
+ };
201
+ }
202
+ function readMarkdownOutline(markdown) {
171
203
  const lines = markdown.replace(/\r?\n$/, "").split(/\r?\n/);
172
204
  // a leading frontmatter is page content, its `- ` lines yaml items
173
205
  const frontmatter = lines.slice(0, lines[0] === "---" ? lines.indexOf("---", 1) + 1 : 0);
@@ -183,38 +215,45 @@ export function markdownOutlineToOrg(markdown, convert, presets) {
183
215
  // writes a page's first block if it is a heading
184
216
  return !fenced && MD_HEADING_RE.test(line) ? [1, line] : null;
185
217
  });
186
- page.unshift(...frontmatter);
187
- return [
188
- ...convertPage(page, convert, presets.page),
189
- ...blocks.map(block => mdBlockToOrg(block, convert, presets.block))
190
- ]
191
- .join("\n")
192
- .concat("\n");
218
+ return {
219
+ page: [...frontmatter, ...page],
220
+ blocks: blocks.map(readMarkdownBlock)
221
+ };
193
222
  }
194
- // a continuation line sits two spaces inside its bullet
195
- function dedent(line, level) {
196
- const indent = `${"\t".repeat(level - 1)} `;
197
- return line.startsWith(indent)
198
- ? line.slice(indent.length)
199
- : line.trim()
200
- ? line
201
- : "";
223
+ // a block's content's first line, its meta lines, then the rest of the
224
+ // content; content that starts with the meta lines keeps them first
225
+ function arrange(metaFirst, meta, content) {
226
+ return metaFirst
227
+ ? [...meta, ...content]
228
+ : [content[0] ?? "", ...meta, ...content.slice(1)];
229
+ }
230
+ function propertyLine({ key, value }) {
231
+ return `${key}::${value ? ` ${value}` : ""}`;
232
+ }
233
+ function writeMarkdownBlock(block) {
234
+ const meta = block.meta.flatMap(item => "lines" in item ? logbook(item.lines, "*") : [propertyLine(item)]);
235
+ const [title = "", ...more] = arrange(block.metaFirst, meta, block.content);
236
+ const head = ["#".repeat(block.heading), title].filter(Boolean).join(" ");
237
+ const indent = "\t".repeat(block.level - 1);
238
+ return [
239
+ `${indent}-${head ? ` ${head}` : ""}`,
240
+ ...more.map(line => `${indent} ${line}`)
241
+ ].join("\n");
202
242
  }
203
- // the md meta lines in org: `key::` lines become the property drawer,
204
- // where the first of them was, or below the planning lines
243
+ // the meta in org: properties become the property drawer, where the
244
+ // first of them was, or below the planning lines
205
245
  function orgMetaLines(meta, heading) {
206
246
  const properties = heading ? [`:heading: ${heading}`] : [];
207
247
  const lines = [];
208
- for (const group of meta) {
209
- const property = MD_PROPERTY_RE.exec(group[0] ?? "");
210
- if (!property) {
211
- lines.push(...logbook(group, "-"));
248
+ for (const item of meta) {
249
+ if ("lines" in item) {
250
+ lines.push(...logbook(item.lines, "-"));
212
251
  continue;
213
252
  }
214
253
  if (!lines.includes(null)) {
215
254
  lines.push(null);
216
255
  }
217
- properties.push(`:${property[1]}:${property[2] ? ` ${property[2]}` : ""}`);
256
+ properties.push(`:${item.key}:${item.value ? ` ${item.value}` : ""}`);
218
257
  }
219
258
  if (properties.length && !lines.includes(null)) {
220
259
  lines.splice(lines.filter(line => PLANNING_RE.test(line ?? "")).length, 0, null);
@@ -222,30 +261,207 @@ function orgMetaLines(meta, heading) {
222
261
  const drawer = [":PROPERTIES:", ...properties, ":END:"];
223
262
  return lines.flatMap(line => (line === null ? drawer : [line]));
224
263
  }
225
- // a block's content's first line, its meta lines, then the rest of the
226
- // content; content that starts with the meta lines keeps them first
227
- function arrange(metaFirst, meta, content) {
228
- return metaFirst
229
- ? [...meta, ...content]
230
- : [content[0] ?? "", ...meta, ...content.slice(1)];
231
- }
232
- // `## title`: the heading level and the title
233
- function mdHeading(line) {
234
- const match = MD_HEADING_RE.exec(line);
235
- return match ? [match[1]?.length ?? 0, match[2] ?? ""] : [0, line];
264
+ // Logseq org writes a block's first line on its stars' line, where
265
+ // Emacs reads it as the title; whether the line starts an element there
266
+ // that runs on below it (a block, a list, a table, a drawer), which the
267
+ // title would cut off; one line of a list or a table is a fine title,
268
+ // as is a headline's own
269
+ function startsElement(lines) {
270
+ const [first = ""] = lines;
271
+ if (!mayBeLineSyntax(first) || HEADLINE_RE.test(first)) {
272
+ return false;
273
+ }
274
+ const text = `${lines.join("\n")}\n`;
275
+ const [element] = tryParse(text, positionParser)?.children ?? [];
276
+ // its end takes in the blank lines after it
277
+ return (!!element &&
278
+ element.type !== "paragraph" &&
279
+ text
280
+ .slice(0, element.position?.end.offset ?? 0)
281
+ .trimEnd()
282
+ .includes("\n"));
236
283
  }
237
- function mdBlockToOrg(block, convert, preset) {
238
- const [first = "", ...rest] = block.lines;
239
- const lines = [first, ...rest.map(line => dedent(line, block.level))];
240
- // content that starts with properties: their drawer opens on the
241
- // headline line
242
- const metaFirst = MD_PROPERTY_RE.test(first);
243
- const [heading, titleLine] = metaFirst ? [0, ""] : mdHeading(first);
244
- const { meta, body } = takeMeta(metaFirst ? lines : lines.slice(1), line => MD_PROPERTY_RE.test(line));
245
- const content = convertMarkdownContent(metaFirst ? body : [titleLine, ...body], convert, preset).filter(line => line !== BRACED_SCRIPTS_LINE);
246
- const [title = "", ...more] = arrange(metaFirst, orgMetaLines(meta, heading), content);
284
+ // Vanilla org: Emacs reads stars without a space after them as text, so
285
+ // an empty title keeps the space Logseq org leaves out, and a first line
286
+ // that starts an element goes below the stars (ADR 0006)
287
+ function writeOrgBlock(block, vanilla = false) {
288
+ const lines = arrange(block.metaFirst, orgMetaLines(block.meta, block.heading), block.content);
289
+ const [title = "", ...more] = vanilla && startsElement(lines) ? ["", ...lines] : lines;
247
290
  return [
248
- `${"*".repeat(block.level)}${title ? ` ${title}` : ""}`,
291
+ `${"*".repeat(block.level)}${title || vanilla ? ` ${title}` : ""}`,
249
292
  ...more
250
293
  ].join("\n");
251
294
  }
295
+ function writeOutline({ page, blocks }, writeBlock) {
296
+ return [...page, ...blocks.map(writeBlock)].join("\n").concat("\n");
297
+ }
298
+ // a block's title is a headline's, which Logseq reads as inline text
299
+ // only; converted with the lines below it, it must not read as a list,
300
+ // a headline or a fixed-width line, so an escape the core drops keeps
301
+ // it text (ADR 0006)
302
+ function inlineTitle(block) {
303
+ const [title = "", ...rest] = block.content;
304
+ return !block.metaFirst && readsAsLineSyntax(title)
305
+ ? [`${ZERO_WIDTH_SPACE}${title}`, ...rest]
306
+ : block.content;
307
+ }
308
+ // a title written from Markdown text keeps it text with an escape that
309
+ // a headline needs none of
310
+ function dropTitleEscape(block) {
311
+ const [title = "", ...rest] = block.content;
312
+ return block.metaFirst || !title.startsWith(ZERO_WIDTH_SPACE)
313
+ ? block
314
+ : { ...block, content: [title.slice(ZERO_WIDTH_SPACE.length), ...rest] };
315
+ }
316
+ // a task marker Logseq shows as a checkbox (0.10, block-checkbox), and
317
+ // the text after it; TODO and DONE are the checkbox's own
318
+ const CHECKBOX_MARKER_RE = /^(TODO|DONE|NOW|LATER|DOING|IN-PROGRESS|WAIT|WAITING) (.*)$/;
319
+ // Vanilla Markdown: a task as a task item, marked as Logseq shows it,
320
+ // so the marker comes back; CANCELED, shown without one, stays text
321
+ function vanillaTitle(title) {
322
+ const [, marker, text] = CHECKBOX_MARKER_RE.exec(title) ?? [];
323
+ if (!marker) {
324
+ return title;
325
+ }
326
+ if (marker === "DONE") {
327
+ return `[x] ${text}`;
328
+ }
329
+ return marker === "TODO" ? `[ ] ${text}` : `[ ] ${marker} ${text}`;
330
+ }
331
+ const PLANNING_ENTRY_RE = /(SCHEDULED|DEADLINE): ([<[][^>\]]*[>\]])/g;
332
+ // Vanilla Markdown: planning and properties as key:: lines, under the
333
+ // org-ism names; a drawer and Logseq's view state have no form there
334
+ function vanillaMeta(meta, context) {
335
+ return meta.flatMap(item => {
336
+ if ("key" in item) {
337
+ if (item.key === "collapsed") {
338
+ context.onWarning?.("collapsed is Logseq's view state; dropped");
339
+ return [];
340
+ }
341
+ return [propertyLine(item)];
342
+ }
343
+ const [first = ""] = item.lines;
344
+ if (isDrawerStart(first)) {
345
+ context.onWarning?.(`a ${first.slice(1, -1)} drawer has no Vanilla Markdown form; dropped`);
346
+ return [];
347
+ }
348
+ return [...first.matchAll(PLANNING_ENTRY_RE)].map(([, key = "", value]) => {
349
+ const canonical = key.toLowerCase();
350
+ return `${context.orgismKeys?.[canonical] ?? canonical}:: ${value}`;
351
+ });
352
+ });
353
+ }
354
+ // a block Logseq shows numbered, as a run of its siblings
355
+ function isNumbered(item) {
356
+ return ("key" in item &&
357
+ item.key === "logseq.order-list-type" &&
358
+ item.value === "number");
359
+ }
360
+ function vanillaBlock(block, indent, bullet, context) {
361
+ const [first = "", ...more] = arrange(block.metaFirst, vanillaMeta(block.meta.filter(item => !isNumbered(item)), context), block.content);
362
+ const hashes = "#".repeat(block.heading);
363
+ // a heading no list holds is a heading of its own
364
+ if (block.heading && !indent) {
365
+ const body = more.length ? `\n\n${more.join("\n")}` : "";
366
+ return { heading: true, text: `${hashes} ${first}${body}` };
367
+ }
368
+ const title = [hashes, vanillaTitle(first)].filter(Boolean).join(" ");
369
+ return {
370
+ heading: false,
371
+ text: [
372
+ `${indent}${bullet}${title ? ` ${title}` : ""}`,
373
+ ...more.map(line => line ? `${indent}${" ".repeat(bullet.length + 1)}${line}` : "")
374
+ ].join("\n")
375
+ };
376
+ }
377
+ // Vanilla Markdown: a block is a list item, its children nested two
378
+ // spaces further in; under a heading block they start a new list
379
+ // (ADR 0006)
380
+ function writeVanillaMarkdownOutline({ page, blocks }, context) {
381
+ // a parent's level, its children's indent and their numbered run
382
+ const parents = [{ level: 0, indent: "", run: 0 }];
383
+ const written = blocks.map(block => {
384
+ while (parents.length > 1 && (parents.at(-1)?.level ?? 0) >= block.level) {
385
+ parents.pop();
386
+ }
387
+ const parent = parents.at(-1) ?? { level: 0, indent: "", run: 0 };
388
+ parent.run = block.meta.some(isNumbered) ? parent.run + 1 : 0;
389
+ const bullet = parent.run ? `${parent.run}.` : "-";
390
+ const result = vanillaBlock(block, parent.indent, bullet, context);
391
+ parents.push({
392
+ level: block.level,
393
+ indent: result.heading
394
+ ? ""
395
+ : `${parent.indent}${" ".repeat(bullet.length + 1)}`,
396
+ run: 0
397
+ });
398
+ return result;
399
+ });
400
+ const body = written
401
+ .map((block, i) => i && (block.heading || written[i - 1]?.heading)
402
+ ? `\n${block.text}`
403
+ : block.text)
404
+ .join("\n");
405
+ return [
406
+ ...page.map(text => text.replace(/\n+$/, "")),
407
+ ...(body ? [body] : [])
408
+ ]
409
+ .join("\n\n")
410
+ .concat("\n");
411
+ }
412
+ /**
413
+ * Converts a Logseq org page to Markdown, block by block: Logseq's,
414
+ * or Vanilla where the preset is on the input side only.
415
+ * @param org The org page.
416
+ * @param convert The core's fragment converter.
417
+ * @param presets The presets for the page properties and for a block.
418
+ * @param context The side the preset is on.
419
+ * @returns The Markdown page.
420
+ */
421
+ export function orgOutlineToMarkdown(org, convert, presets, context) {
422
+ const { page, blocks } = readOrgOutline(org, context.side === "output");
423
+ const vanilla = context.side === "input";
424
+ const pageLines = vanilla ? (presets.vanillaPage?.(page) ?? page) : page;
425
+ const convertCarried = (fragment, preset) => convert(fragment, preset, vanilla ? presets.vanillaInline : undefined);
426
+ const outline = {
427
+ page: convertPage(pageLines, convertCarried, presets.page),
428
+ blocks: blocks.map(block => ({
429
+ ...block,
430
+ content: convertContent(withBracedScripts(vanilla ? inlineTitle(block) : block.content), convertCarried, presets.block)
431
+ }))
432
+ };
433
+ return vanilla
434
+ ? writeVanillaMarkdownOutline(outline, context)
435
+ : writeOutline(outline, writeMarkdownBlock);
436
+ }
437
+ /**
438
+ * Converts a Logseq Markdown page to Logseq org, block by block.
439
+ * @param markdown The Markdown page.
440
+ * @param convert The core's fragment converter.
441
+ * @param presets The presets for the page properties and for a block.
442
+ * @param context The side the preset is on.
443
+ * @returns The org page.
444
+ */
445
+ export function markdownOutlineToOrg(markdown, convert, presets, context) {
446
+ const vanilla = context.side === "output";
447
+ const { page, blocks } = vanilla
448
+ ? readVanillaMarkdownOutline(markdown, context)
449
+ : readMarkdownOutline(markdown);
450
+ // Vanilla Markdown carries Logseq's syntax as Logseq Markdown writes
451
+ // it; Vanilla org is written as Logseq org, which it is (ADR 0006)
452
+ const convertCarried = (fragment, preset) => convert(fragment, preset, context.side === "both" || !preset
453
+ ? undefined
454
+ : vanilla
455
+ ? (presets.vanillaReader?.(preset) ?? preset)
456
+ : preset);
457
+ return writeOutline({
458
+ page: convertPage(page, convertCarried, presets.page),
459
+ blocks: blocks.map(block => {
460
+ const converted = {
461
+ ...block,
462
+ content: convertMarkdownContent(block.content, convertCarried, presets.block).filter(line => line !== BRACED_SCRIPTS_LINE)
463
+ };
464
+ return vanilla ? dropTitleEscape(converted) : converted;
465
+ })
466
+ }, block => writeOrgBlock(block, context.side === "input"));
467
+ }
@@ -0,0 +1,9 @@
1
+ import type { Outline } from "./logseqOutline.js";
2
+ import type { ConversionContext } from "./types.js";
3
+ /**
4
+ * Reads a Vanilla Markdown page into Logseq's outline.
5
+ * @param markdown The Markdown page.
6
+ * @param context The conversion's org-ism key names.
7
+ * @returns The outline, its blocks' content in Markdown.
8
+ */
9
+ export declare function readVanillaMarkdownOutline(markdown: string, context: ConversionContext): Outline;