@remigius42/morg 0.6.0 → 0.8.0

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 (37) hide show
  1. package/README.md +23 -11
  2. package/dist/core/affiliated.d.ts +13 -0
  3. package/dist/core/affiliated.js +43 -0
  4. package/dist/core/backslashCommands.d.ts +10 -0
  5. package/dist/core/backslashCommands.js +43 -0
  6. package/dist/core/bracedScripts.d.ts +7 -0
  7. package/dist/core/bracedScripts.js +10 -0
  8. package/dist/core/frontmatterBlock.d.ts +89 -0
  9. package/dist/core/frontmatterBlock.js +448 -0
  10. package/dist/core/keyValueLines.d.ts +6 -0
  11. package/dist/core/keyValueLines.js +18 -0
  12. package/dist/core/lineSyntax.d.ts +16 -0
  13. package/dist/core/lineSyntax.js +25 -2
  14. package/dist/core/mdastToUniorg/blocks.d.ts +0 -1
  15. package/dist/core/mdastToUniorg/blocks.js +7 -32
  16. package/dist/core/mdastToUniorg/index.d.ts +6 -0
  17. package/dist/core/mdastToUniorg/index.js +72 -7
  18. package/dist/core/passthroughSource.d.ts +21 -0
  19. package/dist/core/passthroughSource.js +94 -0
  20. package/dist/core/tablePipes.d.ts +12 -3
  21. package/dist/core/tablePipes.js +38 -5
  22. package/dist/core/uniorgToMdast/elements.js +6 -1
  23. package/dist/core/uniorgToMdast/index.d.ts +3 -2
  24. package/dist/core/uniorgToMdast/index.js +100 -31
  25. package/dist/core/uniorgToMdast/lists.js +1 -1
  26. package/dist/core/uniorgToMdast/objects.js +6 -0
  27. package/dist/core/uniorgToMdast/shared.d.ts +5 -0
  28. package/dist/core/uniorgToMdast/shared.js +9 -13
  29. package/dist/index.d.ts +1 -2
  30. package/dist/markdownToOrg.js +41 -25
  31. package/dist/orgToMarkdown.js +18 -2
  32. package/dist/presets/logseq.d.ts +3 -22
  33. package/dist/presets/logseq.js +269 -186
  34. package/dist/presets/logseqOutline.d.ts +22 -0
  35. package/dist/presets/logseqOutline.js +251 -0
  36. package/dist/presets/types.d.ts +12 -1
  37. package/package.json +1 -1
@@ -1,41 +1,143 @@
1
1
  import { visit } from "unist-util-visit";
2
2
  import { toString } from "orgast-util-to-string";
3
+ import { isScalar, isSeq } from "yaml";
4
+ import { fitsKeywordLine, isFrontmatterNode, isModeLine, isModeLineComment, takeFrontmatterEntries } from "../core/frontmatterBlock.js";
5
+ import { keyValueEntries } from "../core/keyValueLines.js";
6
+ import { tryParse } from "../core/render.js";
7
+ import { markdownOutlineToOrg, orgOutlineToMarkdown } from "./logseqOutline.js";
3
8
  /**
4
- * Logseq dialect preset: `heading::` properties and outline nesting.
9
+ * Logseq dialect preset: the outline of blocks, page properties, page
10
+ * and block references, highlights and hiccup.
5
11
  */
6
- export function logseq(options = {}) {
7
- const nestUnderHeadings = options.nestUnderHeadings ?? true;
8
- return {
12
+ export function logseq() {
13
+ const page = {
9
14
  name: "logseq",
10
- applyToUniorg: uniorgAst => applyLogseqSpecificsToUniorgAst(uniorgAst, nestUnderHeadings),
11
- extractFromUniorg: extractLogseqSpecificsFromUniorgAst
15
+ applyToMdast: keepPagePropertySource,
16
+ applyToUniorg: uniorgAst => {
17
+ rewriteLabeledPageRefs(uniorgAst);
18
+ takePageProperties(uniorgAst);
19
+ return uniorgAst;
20
+ },
21
+ extractFromUniorg: uniorgAst => {
22
+ pageProperties(uniorgAst);
23
+ return extractInlineSpecifics(uniorgAst);
24
+ }
25
+ };
26
+ const bareUrls = new Map();
27
+ const block = {
28
+ name: "logseq",
29
+ applyToMdast: mdast => {
30
+ countBareUrls(mdast, bareUrls);
31
+ emailLinksToText(mdast);
32
+ },
33
+ applyToUniorg: uniorgAst => {
34
+ rewriteLabeledPageRefs(uniorgAst);
35
+ restoreBareUrls(uniorgAst, bareUrls);
36
+ return uniorgAst;
37
+ },
38
+ extractFromUniorg: uniorgAst => {
39
+ bareUrlsToText(uniorgAst);
40
+ return extractInlineSpecifics(uniorgAst);
41
+ }
12
42
  };
13
- }
14
- function headingProperty(level) {
15
- return { type: "node-property", key: "heading", value: String(level) };
16
- }
17
- function headingDrawer(level) {
18
43
  return {
19
- type: "property-drawer",
20
- children: [headingProperty(level)],
21
- contentsBegin: 0,
22
- contentsEnd: 0
44
+ ...page,
45
+ convertOrg: (org, convert) => orgOutlineToMarkdown(org, convert, { page, block }),
46
+ convertMarkdown: (markdown, convert) => markdownOutlineToOrg(markdown, convert, { page, block })
23
47
  };
24
48
  }
25
- // org fixes the order below a headline: the planning line first, then a
26
- // single property drawer. :heading: therefore has to let the planning
27
- // line pass and join an existing drawer rather than displace either.
28
- // Returns whether the property has been placed on `node`.
29
- function placeHeadingProperty(node, level) {
30
- if (node.type === "planning") {
31
- return false;
49
+ // Logseq md writes a url bare, as org writes a plain link; the core
50
+ // carries a md link as a [[url]] bracket link, so md→org counts the
51
+ // links that were bare in the source and turns as many back to plain.
52
+ // Where org's plain link ends short of md's autolink on trailing
53
+ // punctuation (a macro's `}}`), that tail splits off as text
54
+ function countBareUrls(mdast, counts) {
55
+ counts.clear();
56
+ visit(mdast, "link", (node, index, parent) => {
57
+ const url = bareUrl(node);
58
+ if (url === null) {
59
+ return undefined;
60
+ }
61
+ counts.set(url, (counts.get(url) ?? 0) + 1);
62
+ const tail = node.url.slice(url.length);
63
+ if (tail && parent && index !== undefined) {
64
+ node.url = url;
65
+ node.children = [{ type: "text", value: url }];
66
+ parent.children.splice(index + 1, 0, { type: "text", value: tail });
67
+ }
68
+ return undefined;
69
+ });
70
+ }
71
+ // the plain link org reads for a link written bare, if the rest is
72
+ // trailing punctuation
73
+ function bareUrl(node) {
74
+ const start = node.position?.start.offset;
75
+ const end = node.position?.end.offset;
76
+ if (start === undefined ||
77
+ end === undefined ||
78
+ end - start !== node.url.length) {
79
+ return null;
32
80
  }
33
- if (node.type !== "property-drawer") {
34
- return false;
81
+ const url = orgPlainLink(node.url);
82
+ return url !== null && /^[^\w(]*$/.test(node.url.slice(url.length))
83
+ ? url
84
+ : null;
85
+ }
86
+ // the url of the plain link org reads at the start of `url`, if any;
87
+ // it ends early at a `(` (`…/Bandwidth_(signal)`) or a macro's `}}`
88
+ function orgPlainLink(url) {
89
+ if (!/^https?:\/\//.test(url)) {
90
+ return null;
35
91
  }
36
- ;
37
- node.children.unshift(headingProperty(level));
38
- return true;
92
+ const [paragraph] = tryParse(`${url}\n`)?.children ?? [];
93
+ const [link] = paragraph?.children ?? [];
94
+ return link?.format === "plain"
95
+ ? link.rawLink
96
+ : null;
97
+ }
98
+ function restoreBareUrls(uniorgAst, counts) {
99
+ visit(uniorgAst, "link", (node) => {
100
+ const count = counts.get(node.rawLink) ?? 0;
101
+ if (node.format === "bracket" && !node.children.length && count) {
102
+ node.format = "plain";
103
+ counts.set(node.rawLink, count - 1);
104
+ }
105
+ });
106
+ }
107
+ // org→md: a plain http(s) link stays a bare url, unescaped
108
+ // an email address is text in Logseq org and written bare in Logseq md,
109
+ // where GFM links it: md→org takes such a link back to its text, and
110
+ // org→md keeps the address unescaped (keepVerbatimText)
111
+ const EMAIL_RE = /[\w.+-]+@[\w-]+(?:\.[\w-]+)+/;
112
+ function emailLinksToText(mdast) {
113
+ visit(mdast, "link", (node, index, parent) => {
114
+ const start = node.position?.start.offset;
115
+ const end = node.position?.end.offset;
116
+ const text = node.url.replace(/^mailto:/, "");
117
+ if (node.url.startsWith("mailto:") &&
118
+ start !== undefined &&
119
+ end !== undefined &&
120
+ end - start === text.length &&
121
+ parent &&
122
+ index !== undefined) {
123
+ parent.children[index] = { type: "text", value: text };
124
+ }
125
+ });
126
+ }
127
+ function bareUrlsToText(uniorgAst) {
128
+ visit(uniorgAst, "link", (node, index, parent) => {
129
+ if (node.format !== "plain" ||
130
+ !/^https?$/.test(node.linkType) ||
131
+ !parent ||
132
+ index === undefined) {
133
+ return undefined;
134
+ }
135
+ parent.children[index] = {
136
+ type: "verbatim-inline",
137
+ value: node.rawLink
138
+ };
139
+ return undefined;
140
+ });
39
141
  }
40
142
  // the page name of a [[page]] link destination, or null. The core
41
143
  // transform percent-encodes brackets in urls (an org link path cannot
@@ -62,112 +164,159 @@ function rewriteLabeledPageRefs(uniorgAst) {
62
164
  node.path = page;
63
165
  });
64
166
  }
65
- function toBlockHeadline(node, level) {
66
- const blockHeadline = {
67
- type: "headline",
68
- level,
69
- todoKeyword: null,
70
- priority: null,
71
- commented: false,
72
- rawValue: "",
73
- tags: [],
74
- children: node.children
75
- };
76
- takeTaskMarker(blockHeadline);
77
- return blockHeadline;
167
+ // Logseq reads a page's first block of `key:: value` lines (md) and its
168
+ // leading `#+key: value` lines (org) as page properties (ADR 0005)
169
+ function pagePropertyEntries(text) {
170
+ const entries = keyValueEntries(text);
171
+ // a key org reads as no keyword name (begin_src) would not come back
172
+ return entries?.every(entry => fitsKeywordLine(...entry)) ? entries : null;
78
173
  }
79
- /**
80
- * Applies Logseq-specific conventions to a uniorg AST: every heading gets
81
- * a `:heading:` property drawer, and with `nestUnderHeadings` following
82
- * paragraphs become child block headlines (in Logseq's org format every
83
- * outline block is a headline).
84
- * @param uniorgAst The uniorg AST to transform.
85
- * @param nestUnderHeadings Nest content as child blocks. Default: `true`.
86
- * @returns The Logseq-flavored uniorg AST.
87
- */
88
- export function applyLogseqSpecificsToUniorgAst(uniorgAst, nestUnderHeadings = true) {
89
- rewriteLabeledPageRefs(uniorgAst);
174
+ // md→org: the page property block's values are Logseq's, not Markdown;
175
+ // its source text replaces the parse, so urls and markup stay verbatim
176
+ function keepPagePropertySource(mdast, markdown) {
177
+ // below the frontmatter and an Emacs mode line
178
+ const node = mdast.children.find(child => child.type !== "yaml" &&
179
+ !(child.type === "html" &&
180
+ child.value.startsWith("<!--") &&
181
+ isModeLine(child.value)));
182
+ const start = node?.position?.start.offset;
183
+ const end = node?.position?.end.offset;
184
+ if (node?.type !== "paragraph" || start === undefined || end === undefined) {
185
+ return;
186
+ }
187
+ const source = markdown.slice(start, end);
188
+ if (pagePropertyEntries(source)) {
189
+ node.children = [{ type: "text", value: source }];
190
+ }
191
+ }
192
+ function takePageProperties(uniorgAst) {
90
193
  const children = uniorgAst.children;
91
- const result = [];
92
- let currentLevel = 0;
93
- // level of a headline whose :heading: property still needs a home
94
- let pending = null;
95
- const separate = () => {
96
- if (!nestUnderHeadings) {
97
- result.push({ type: "text", value: "\n" });
98
- }
99
- };
100
- // no drawer took the property: give it one of its own
101
- const settle = () => {
102
- if (pending === null) {
103
- return;
104
- }
105
- result.push(headingDrawer(pending));
106
- pending = null;
107
- separate();
108
- };
109
- for (const node of children) {
110
- if (node.type === "headline") {
111
- settle();
112
- const headline = node;
113
- currentLevel = headline.level;
114
- takeTaskMarker(headline);
115
- result.push(node);
116
- pending = headline.level;
117
- continue;
118
- }
119
- if (pending !== null) {
120
- if (placeHeadingProperty(node, pending)) {
121
- pending = null;
122
- result.push(node);
123
- separate();
124
- continue;
125
- }
126
- if (node.type !== "planning") {
127
- settle();
128
- }
129
- }
130
- result.push(nestUnderHeadings && node.type === "paragraph"
131
- ? toBlockHeadline(node, currentLevel + 1)
132
- : node);
194
+ const keywords = takeFrontmatterKeywords(children);
195
+ // below the keywords, the file-level drawer and a mode line
196
+ const index = children.findIndex(node => !["keyword", "property-drawer"].includes(node.type) &&
197
+ !isModeLineComment(node) &&
198
+ !isFrontmatterNode(node));
199
+ const lines = pagePropertyLines(children[index]);
200
+ if (lines) {
201
+ children.splice(index, 1);
202
+ keywords.push(...lines);
133
203
  }
134
- settle();
135
- uniorgAst.children = result;
136
- return uniorgAst;
204
+ // ahead of the block, so all of them lead the page
205
+ const at = children.findIndex(node => node.type !== "keyword");
206
+ children.splice(at === -1 ? children.length : at, 0, ...keywords.map(([key, value]) => ({ type: "keyword", key, value })));
137
207
  }
138
- // Logseq md keeps TODO/DONE as leading text markers and priorities as
139
- // [#A] text; in org they are the headline's TODO keyword and priority
140
- // (other Logseq markers like DOING are not org keywords and simply
141
- // stay in the title text)
142
- function takeTaskMarker(headline) {
143
- const first = headline.children[0];
144
- if (first?.type !== "text") {
145
- return;
208
+ function takeFrontmatterKeywords(children) {
209
+ const frontmatter = children.find(isFrontmatterNode);
210
+ if (!frontmatter) {
211
+ return [];
146
212
  }
147
- const marker = /^(TODO|DONE) (?:\[#([A-Z])\] )?/.exec(first.value);
148
- if (!marker) {
149
- return;
213
+ const keywords = takeFlatEntries(frontmatter);
214
+ // an emptied block goes
215
+ if (!frontmatter.yaml && keywords.length) {
216
+ children.splice(children.indexOf(frontmatter), 1);
150
217
  }
151
- headline.todoKeyword = marker[1];
152
- if (marker[2]) {
153
- headline.priority = marker[2];
218
+ return keywords;
219
+ }
220
+ function pagePropertyLines(node) {
221
+ // plain text only: markup in a key means the source was no key:: line
222
+ const children = node?.children;
223
+ if (node?.type !== "paragraph" ||
224
+ !children?.every(child => child.type === "text")) {
225
+ return null;
154
226
  }
155
- first.value = first.value.slice((marker[0] ?? "").length);
227
+ return pagePropertyEntries(toString(node));
156
228
  }
157
- /**
158
- * Extracts Logseq-specific conventions from a uniorg AST: headlines with
159
- * a `:heading:` property become plain headings of that level, headlines
160
- * without one are outline blocks and become paragraphs.
161
- * @param uniorgAst The Logseq-flavored uniorg AST to transform.
162
- * @returns The generic uniorg AST.
163
- */
164
- function extractLogseqSpecificsFromUniorgAst(uniorgAst) {
165
- extractInParent(uniorgAst);
229
+ // keywords that act in Emacs or in export (TODO states, startup and
230
+ // export options, file inclusion, babel calls, a dynamic block's begin
231
+ // and end lines, citations, a table of
232
+ // contents, index entries, raw export lines and exporter options, of
233
+ // org's own exporters, org-info.js and KOMA letter class files among
234
+ // them, and of ox-hugo and org-re-reveal): as frontmatter they were
235
+ // passive data, so they stay in the block. A denylist: other
236
+ // third-party exporters' keys pass. Export metadata (`title`, `author`)
237
+ // passes too, and `tags`, Logseq's page tags property
238
+ const ACTING_KEYWORD_RE = /^(?:(?:SEQ_|TYP_)?TODO|STARTUP|OPTIONS|INCLUDE|SETUPFILE|BIND|MACRO|CALL|PROPERTY|LINK|CONSTANTS|PRIORITIES|BIBLIOGRAPHY|CITE_EXPORT|PRINT_BIBLIOGRAPHY|ARCHIVE|CATEGORY|COLUMNS|FILETAGS|EXCLUDE_TAGS|SELECT_TAGS|BEGIN|END|TOC|(?:C|F|K|P|T|V)?INDEX|INFOJS_OPT|LCO|EXPORT_\w+|(?:HTML|LATEX|BEAMER|ODT|TEXINFO|MAN|ASCII|MD|MARKDOWN|ICALENDAR|HUGO|REVEAL)(?:_\w+)?)$/i;
239
+ // flat frontmatter entries are page properties too, a sequence written
240
+ // as Logseq writes it (`a, b`); what a keyword line cannot hold stays
241
+ function takeFlatEntries(frontmatter) {
242
+ const { keywords, yaml } = takeFrontmatterEntries(frontmatter.yaml, (key, value, text) => {
243
+ const flat = flatValue(value, text);
244
+ // a key that comes back as a key:: line as written: uniorg
245
+ // upper-cases it, Logseq reads it lower-cased
246
+ return flat !== null &&
247
+ /^[a-z0-9_-]+$/.test(text(key)) &&
248
+ fitsKeywordLine(text(key), flat) &&
249
+ !ACTING_KEYWORD_RE.test(text(key))
250
+ ? [[text(key), flat]]
251
+ : null;
252
+ });
253
+ frontmatter.yaml = yaml;
254
+ return keywords;
255
+ }
256
+ function flatValue(value, text) {
257
+ const single = (item) => isScalar(item) && item.value !== null ? text(item) : null;
258
+ if (!isSeq(value)) {
259
+ return single(value);
260
+ }
261
+ const items = value.items.map(single);
262
+ return items.length &&
263
+ items.every(item => item !== null && !item.includes(","))
264
+ ? items.join(", ")
265
+ : null;
266
+ }
267
+ // the reverse: leading keywords become the first block, verbatim, keys
268
+ // lower-cased (uniorg upper-cases them, Logseq reads only lower case)
269
+ function pageProperties(uniorgAst) {
270
+ const children = uniorgAst.children;
271
+ // below a mode line, as on the way in
272
+ const first = children.findIndex(node => !isModeLineComment(node));
273
+ let count = 0;
274
+ while (first !== -1 && children[first + count]?.type === "keyword") {
275
+ count++;
276
+ }
277
+ const leading = children.slice(first, first + count);
278
+ // a key no `key::` line holds (`CAPTION[short]`) stays a keyword
279
+ const properties = leading.filter(keyword => /^[\w-]+$/.test(keyword.key));
280
+ if (!properties.length) {
281
+ return;
282
+ }
283
+ const lines = properties.map(keyword => `${keyword.key.toLowerCase()}::${keyword.value ? ` ${keyword.value}` : ""}`);
284
+ children.splice(first, count, ...leading.filter(keyword => !properties.includes(keyword)), {
285
+ type: "paragraph",
286
+ children: [{ type: "verbatim-inline", value: lines.join("\n") }],
287
+ contentsBegin: 0,
288
+ contentsEnd: 0
289
+ });
290
+ }
291
+ function extractInlineSpecifics(uniorgAst) {
292
+ keepVerbatimText(uniorgAst);
166
293
  repairHighlights(uniorgAst);
167
294
  fuzzyLinksToPageRefs(uniorgAst);
168
295
  markHiccupParagraphs(uniorgAst);
169
296
  return uniorgAst;
170
297
  }
298
+ // text Logseq md writes as it is, which remark would escape: a task's
299
+ // priority ([#A]), an email address, and a tag's # (`#tag` starting a
300
+ // line, which mldoc reads as escaped plain text once written `\#`);
301
+ // verbatim-inline keeps it
302
+ const VERBATIM_TEXT_RE = new RegExp(`(\\[#[A-Z]\\]|${EMAIL_RE.source}|(?<!\\S)#(?=[^\\s#]|$))`);
303
+ function keepVerbatimText(uniorgAst) {
304
+ visit(uniorgAst, "text", (node, index, parent) => {
305
+ const parts = node.value.split(VERBATIM_TEXT_RE);
306
+ if (parts.length === 1 || !parent || index === undefined) {
307
+ return undefined;
308
+ }
309
+ // split's captures sit at the odd indices
310
+ const nodes = parts
311
+ .map((value, i) => ({
312
+ type: i % 2 ? "verbatim-inline" : "text",
313
+ value
314
+ }))
315
+ .filter(part => part.value);
316
+ parent.children.splice(index, 1, ...nodes);
317
+ return index + nodes.length;
318
+ });
319
+ }
171
320
  // Logseq highlight markup (^^words^^) re-parses as a caret plus a
172
321
  // superscript; merge the pieces back into literal text
173
322
  function repairHighlights(uniorgAst) {
@@ -230,69 +379,3 @@ function markHiccupParagraphs(uniorgAst) {
230
379
  }
231
380
  });
232
381
  }
233
- function extractInParent(parent) {
234
- const children = parent.children;
235
- for (let i = 0; i < children.length; i++) {
236
- const node = children[i];
237
- if (!node) {
238
- continue;
239
- }
240
- if (node.type === "section") {
241
- extractInParent(node);
242
- continue;
243
- }
244
- if (node.type !== "headline") {
245
- continue;
246
- }
247
- const headline = node;
248
- if (headline.todoKeyword) {
249
- // back to Logseq md's text conventions (TODO [#A] Ship it);
250
- // verbatim-inline keeps the [#A] brackets unescaped
251
- const priority = headline.priority ? `[#${headline.priority}] ` : "";
252
- headline.children.unshift({
253
- type: "verbatim-inline",
254
- value: `${headline.todoKeyword} ${priority}`
255
- });
256
- headline.todoKeyword = null;
257
- headline.priority = null;
258
- }
259
- // a planning line sits between the headline and its drawer
260
- const drawerIndex = children[i + 1]?.type === "planning" ? i + 2 : i + 1;
261
- const heading = takeHeadingProperty(children, drawerIndex);
262
- if (heading !== null) {
263
- headline.level = heading;
264
- }
265
- else {
266
- // a block headline (no :heading:) is outline structure only; its
267
- // title is the block's content
268
- children[i] = {
269
- type: "paragraph",
270
- children: headline.children,
271
- contentsBegin: 0,
272
- contentsEnd: 0
273
- };
274
- }
275
- }
276
- }
277
- // removes the heading property from a drawer at `index` (and the drawer
278
- // itself if that empties it); returns the heading level or null
279
- function takeHeadingProperty(children, index) {
280
- const drawer = children[index];
281
- if (drawer?.type !== "property-drawer") {
282
- return null;
283
- }
284
- const properties = drawer.children;
285
- const heading = properties.find(property => property.key === "heading");
286
- if (!heading) {
287
- return null;
288
- }
289
- const remaining = properties.filter(property => property !== heading);
290
- if (remaining.length) {
291
- ;
292
- drawer.children = remaining;
293
- }
294
- else {
295
- children.splice(index, 1);
296
- }
297
- return parseInt(heading.value, 10) || null;
298
- }
@@ -0,0 +1,22 @@
1
+ import type { FragmentConverter, Preset } from "./types.js";
2
+ interface Presets {
3
+ page: Preset;
4
+ block: Preset;
5
+ }
6
+ /**
7
+ * Converts a Logseq org page to Logseq Markdown, block by block.
8
+ * @param org The org page.
9
+ * @param convert The core's fragment converter.
10
+ * @param presets The presets for the page properties and for a block.
11
+ * @returns The Markdown page.
12
+ */
13
+ export declare function orgOutlineToMarkdown(org: string, convert: FragmentConverter, presets: Presets): string;
14
+ /**
15
+ * Converts a Logseq Markdown page to Logseq org, block by block.
16
+ * @param markdown The Markdown page.
17
+ * @param convert The core's fragment converter.
18
+ * @param presets The presets for the page properties and for a block.
19
+ * @returns The org page.
20
+ */
21
+ export declare function markdownOutlineToOrg(markdown: string, convert: FragmentConverter, presets: Presets): string;
22
+ export {};