@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.
- package/README.md +23 -11
- package/dist/core/affiliated.d.ts +13 -0
- package/dist/core/affiliated.js +43 -0
- package/dist/core/backslashCommands.d.ts +10 -0
- package/dist/core/backslashCommands.js +43 -0
- package/dist/core/bracedScripts.d.ts +7 -0
- package/dist/core/bracedScripts.js +10 -0
- package/dist/core/frontmatterBlock.d.ts +89 -0
- package/dist/core/frontmatterBlock.js +448 -0
- package/dist/core/keyValueLines.d.ts +6 -0
- package/dist/core/keyValueLines.js +18 -0
- package/dist/core/lineSyntax.d.ts +16 -0
- package/dist/core/lineSyntax.js +25 -2
- package/dist/core/mdastToUniorg/blocks.d.ts +0 -1
- package/dist/core/mdastToUniorg/blocks.js +7 -32
- package/dist/core/mdastToUniorg/index.d.ts +6 -0
- package/dist/core/mdastToUniorg/index.js +72 -7
- package/dist/core/passthroughSource.d.ts +21 -0
- package/dist/core/passthroughSource.js +94 -0
- package/dist/core/tablePipes.d.ts +12 -3
- package/dist/core/tablePipes.js +38 -5
- package/dist/core/uniorgToMdast/elements.js +6 -1
- package/dist/core/uniorgToMdast/index.d.ts +3 -2
- package/dist/core/uniorgToMdast/index.js +100 -31
- package/dist/core/uniorgToMdast/lists.js +1 -1
- package/dist/core/uniorgToMdast/objects.js +6 -0
- package/dist/core/uniorgToMdast/shared.d.ts +5 -0
- package/dist/core/uniorgToMdast/shared.js +9 -13
- package/dist/index.d.ts +1 -2
- package/dist/markdownToOrg.js +41 -25
- package/dist/orgToMarkdown.js +18 -2
- package/dist/presets/logseq.d.ts +3 -22
- package/dist/presets/logseq.js +269 -186
- package/dist/presets/logseqOutline.d.ts +22 -0
- package/dist/presets/logseqOutline.js +251 -0
- package/dist/presets/types.d.ts +12 -1
- package/package.json +1 -1
package/dist/presets/logseq.js
CHANGED
|
@@ -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:
|
|
9
|
+
* Logseq dialect preset: the outline of blocks, page properties, page
|
|
10
|
+
* and block references, highlights and hiccup.
|
|
5
11
|
*/
|
|
6
|
-
export function logseq(
|
|
7
|
-
const
|
|
8
|
-
return {
|
|
12
|
+
export function logseq() {
|
|
13
|
+
const page = {
|
|
9
14
|
name: "logseq",
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
38
|
-
return
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
227
|
+
return pagePropertyEntries(toString(node));
|
|
156
228
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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 {};
|