@remigius42/morg 0.5.0 → 0.7.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 +12 -1
- 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 +12 -0
- package/dist/core/bracedScripts.js +159 -0
- package/dist/core/footnoteReferences.d.ts +10 -0
- package/dist/core/footnoteReferences.js +24 -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 +21 -0
- package/dist/core/lineSyntax.js +221 -0
- package/dist/core/markupBoundary.d.ts +12 -0
- package/dist/core/markupBoundary.js +195 -0
- package/dist/core/mdastToUniorg/blocks.d.ts +0 -1
- package/dist/core/mdastToUniorg/blocks.js +3 -31
- package/dist/core/mdastToUniorg/index.d.ts +6 -0
- package/dist/core/mdastToUniorg/index.js +81 -8
- package/dist/core/mdastToUniorg/lists.d.ts +1 -1
- package/dist/core/mdastToUniorg/lists.js +21 -2
- package/dist/core/mdastToUniorg/phrasing.js +145 -12
- package/dist/core/orgPath.d.ts +9 -0
- package/dist/core/orgPath.js +24 -0
- package/dist/core/render.d.ts +33 -0
- package/dist/core/render.js +101 -0
- package/dist/core/tablePipes.d.ts +17 -0
- package/dist/core/tablePipes.js +47 -0
- package/dist/core/underscoreBullets.d.ts +10 -0
- package/dist/core/underscoreBullets.js +32 -0
- package/dist/core/uniorgToMdast/elements.js +1 -1
- package/dist/core/uniorgToMdast/index.d.ts +3 -2
- package/dist/core/uniorgToMdast/index.js +100 -31
- package/dist/core/uniorgToMdast/lists.js +12 -1
- package/dist/core/uniorgToMdast/objects.js +44 -6
- package/dist/core/uniorgToMdast/shared.d.ts +5 -0
- package/dist/core/uniorgToMdast/shared.js +3 -12
- package/dist/core/uniorgToMdast/tables.js +12 -6
- package/dist/markdownToOrg.js +44 -23
- package/dist/orgToMarkdown.js +39 -5
- package/dist/presets/logseq.js +130 -0
- package/dist/presets/types.d.ts +4 -1
- package/package.json +4 -1
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { mdismEnabled, warn } from "./context.js";
|
|
2
|
+
import { visit } from "unist-util-visit";
|
|
3
|
+
import { escapeOrgPath } from "../orgPath.js";
|
|
4
|
+
import { orgParser, renderInline } from "../render.js";
|
|
2
5
|
// html tags morg itself emits under useHtml; with interpretHtml a bare
|
|
3
6
|
// open/close pair becomes the corresponding native org object
|
|
4
7
|
const INLINE_HTML_ORG_TYPES = {
|
|
@@ -30,20 +33,87 @@ export function transformPhrasingChildren(ctx, children) {
|
|
|
30
33
|
? matchInlineHtmlPair(children, i)
|
|
31
34
|
: null;
|
|
32
35
|
if (pair) {
|
|
33
|
-
result.push({
|
|
36
|
+
result.push(...hoistEdgeWhitespace(joinLines({
|
|
34
37
|
type: pair.orgType,
|
|
35
38
|
children: transformPhrasingChildren(ctx, children.slice(i + 1, pair.end))
|
|
36
|
-
});
|
|
39
|
+
})));
|
|
37
40
|
i = pair.end;
|
|
38
41
|
continue;
|
|
39
42
|
}
|
|
43
|
+
if (node.type === "inlineCode") {
|
|
44
|
+
result.push(...transformMdastInlineCode(ctx, node.value));
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
40
47
|
const transformed = transformMdastPhrasingContentToUniorgObject(ctx, node);
|
|
41
48
|
if (transformed) {
|
|
42
|
-
result.push(transformed);
|
|
49
|
+
result.push(...hoistEdgeWhitespace(joinLines(transformed)));
|
|
43
50
|
}
|
|
44
51
|
}
|
|
45
52
|
return result;
|
|
46
53
|
}
|
|
54
|
+
const CONTAINER_MARKUP = new Set([
|
|
55
|
+
"bold",
|
|
56
|
+
"italic",
|
|
57
|
+
"strike-through",
|
|
58
|
+
"underline"
|
|
59
|
+
]);
|
|
60
|
+
// org markup spans at most two lines; beyond that its line endings
|
|
61
|
+
// become spaces, as CommonMark renders them anyway (see
|
|
62
|
+
// transformMdastInlineCode)
|
|
63
|
+
function joinLines(node) {
|
|
64
|
+
if (!CONTAINER_MARKUP.has(node.type)) {
|
|
65
|
+
return node;
|
|
66
|
+
}
|
|
67
|
+
const texts = [];
|
|
68
|
+
visit(node, "text", (text) => {
|
|
69
|
+
texts.push(text);
|
|
70
|
+
});
|
|
71
|
+
const lineEndings = texts
|
|
72
|
+
.map(text => text.value)
|
|
73
|
+
.join("")
|
|
74
|
+
.split("\n").length - 1;
|
|
75
|
+
if (lineEndings > 1) {
|
|
76
|
+
for (const text of texts) {
|
|
77
|
+
text.value = text.value.replaceAll("\n", " ");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return node;
|
|
81
|
+
}
|
|
82
|
+
// strips the whitespace at the edges of `children`, returning it
|
|
83
|
+
function takeEdgeWhitespace(children) {
|
|
84
|
+
const first = children[0];
|
|
85
|
+
const last = children.at(-1);
|
|
86
|
+
let before = "";
|
|
87
|
+
let after = "";
|
|
88
|
+
if (first?.type === "text") {
|
|
89
|
+
before = /^\s*/.exec(first.value)?.[0] ?? "";
|
|
90
|
+
first.value = first.value.slice(before.length);
|
|
91
|
+
}
|
|
92
|
+
if (last?.type === "text") {
|
|
93
|
+
after = /\s*$/.exec(last.value)?.[0] ?? "";
|
|
94
|
+
last.value = last.value.slice(0, last.value.length - after.length);
|
|
95
|
+
}
|
|
96
|
+
return { before, after };
|
|
97
|
+
}
|
|
98
|
+
// org markup may not start or end with whitespace, which a code span's
|
|
99
|
+
// moved-out edge whitespace (see transformMdastInlineCode) can leave
|
|
100
|
+
// inside it; it moves out further, to the markup's siblings
|
|
101
|
+
function hoistEdgeWhitespace(node) {
|
|
102
|
+
if (!CONTAINER_MARKUP.has(node.type) || !("children" in node)) {
|
|
103
|
+
return [node];
|
|
104
|
+
}
|
|
105
|
+
const { before, after } = takeEdgeWhitespace(node.children);
|
|
106
|
+
if (!before && !after) {
|
|
107
|
+
return [node];
|
|
108
|
+
}
|
|
109
|
+
const content = node.children.filter(child => child.type !== "text" || child.value !== "");
|
|
110
|
+
const text = (value) => value ? [{ type: "text", value }] : [];
|
|
111
|
+
return [
|
|
112
|
+
...text(before),
|
|
113
|
+
...(content.length ? [{ ...node, children: content }] : []),
|
|
114
|
+
...text(after)
|
|
115
|
+
];
|
|
116
|
+
}
|
|
47
117
|
// an org bracket-link path cannot contain [ or ]; percent-encoding keeps
|
|
48
118
|
// the url equivalent and, unlike org's backslash escaping, survives being
|
|
49
119
|
// parsed back (uniorg does not decode `\[`). A silent normalization, not
|
|
@@ -51,6 +121,36 @@ export function transformPhrasingChildren(ctx, children) {
|
|
|
51
121
|
function orgSafeUrl(url) {
|
|
52
122
|
return url.replaceAll("[", "%5B").replaceAll("]", "%5D");
|
|
53
123
|
}
|
|
124
|
+
// a url without a scheme is a relative path in markdown, but a bare org
|
|
125
|
+
// path is a fuzzy link (a heading search); org's file: type keeps it a
|
|
126
|
+
// file. A #anchor becomes org's search option, which finds a
|
|
127
|
+
// <<target>> or a headline of that name. `[[page]]` / `((uuid))` urls
|
|
128
|
+
// are dialect references (logseq), not paths
|
|
129
|
+
const SCHEME_RE = /^[a-z][a-z0-9+.-]*:/i;
|
|
130
|
+
function isRelativePath(url) {
|
|
131
|
+
return url !== "" && !SCHEME_RE.test(url) && !/^(#|\/\/|\[|\()/.test(url);
|
|
132
|
+
}
|
|
133
|
+
// md urls are percent-encoded, org paths are not; a malformed escape
|
|
134
|
+
// stays as written
|
|
135
|
+
function decodeUrlPart(part) {
|
|
136
|
+
try {
|
|
137
|
+
return decodeURIComponent(part);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return part;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function orgLinkTarget(url) {
|
|
144
|
+
if (!isRelativePath(url)) {
|
|
145
|
+
return { rawLink: orgSafeUrl(url), linkType: "url" };
|
|
146
|
+
}
|
|
147
|
+
const hash = url.indexOf("#");
|
|
148
|
+
const path = escapeOrgPath(decodeUrlPart(hash === -1 ? url : url.slice(0, hash)));
|
|
149
|
+
const search = hash === -1
|
|
150
|
+
? ""
|
|
151
|
+
: `::${escapeOrgPath(decodeUrlPart(url.slice(hash + 1)), true)}`;
|
|
152
|
+
return { rawLink: `file:${path}${search}`, linkType: "file" };
|
|
153
|
+
}
|
|
54
154
|
function transformMdastLink(ctx, linkNode) {
|
|
55
155
|
const [only] = linkNode.children;
|
|
56
156
|
// text equal to the url (autolinks) is no description; a plain
|
|
@@ -61,13 +161,13 @@ function transformMdastLink(ctx, linkNode) {
|
|
|
61
161
|
? []
|
|
62
162
|
: transformPhrasingChildren(ctx, linkNode.children);
|
|
63
163
|
// rawLink should just be the URL, uniorg-stringify adds the brackets
|
|
64
|
-
const
|
|
164
|
+
const { rawLink, linkType } = orgLinkTarget(linkNode.url);
|
|
65
165
|
return {
|
|
66
166
|
type: "link",
|
|
67
167
|
format: "bracket", // Assuming bracket format for Markdown links
|
|
68
|
-
linkType
|
|
69
|
-
rawLink
|
|
70
|
-
path:
|
|
168
|
+
linkType,
|
|
169
|
+
rawLink,
|
|
170
|
+
path: rawLink,
|
|
71
171
|
children: linkChildren
|
|
72
172
|
};
|
|
73
173
|
}
|
|
@@ -101,16 +201,51 @@ function transformMdastImage(ctx, node) {
|
|
|
101
201
|
if (node.title) {
|
|
102
202
|
warn(ctx, `dropped image title "${node.title}" (${node.url})`);
|
|
103
203
|
}
|
|
104
|
-
const
|
|
204
|
+
const { rawLink } = orgLinkTarget(node.url);
|
|
105
205
|
return {
|
|
106
206
|
type: "link",
|
|
107
207
|
format: "bracket",
|
|
108
208
|
linkType: "file",
|
|
109
|
-
rawLink
|
|
110
|
-
path:
|
|
209
|
+
rawLink,
|
|
210
|
+
path: rawLink,
|
|
111
211
|
children: node.alt ? [{ type: "text", value: node.alt }] : []
|
|
112
212
|
};
|
|
113
213
|
}
|
|
214
|
+
// org markup spans at most two lines; CommonMark renders a line ending
|
|
215
|
+
// inside a code span as a space, so nothing is lost. Org markup may not
|
|
216
|
+
// start or end with whitespace either: edge whitespace moves outside
|
|
217
|
+
// the markers, a one-space shift in the rendered output
|
|
218
|
+
function transformMdastInlineCode(ctx, value) {
|
|
219
|
+
const [, before = "", code = "", after = ""] = /^(\s*)(.*?)(\s*)$/s.exec(value.replace(/\r\n?|\n/g, " ")) ?? [];
|
|
220
|
+
if (!code) {
|
|
221
|
+
// org has no empty code markup
|
|
222
|
+
warn(ctx, "whitespace-only inline code kept as text");
|
|
223
|
+
return [{ type: "text", value: before }];
|
|
224
|
+
}
|
|
225
|
+
const type = codeType(code);
|
|
226
|
+
if (!type) {
|
|
227
|
+
warn(ctx, "inline code holding both ~ and = kept as text");
|
|
228
|
+
return [{ type: "text", value: before + code + after }];
|
|
229
|
+
}
|
|
230
|
+
return [
|
|
231
|
+
...(before ? [{ type: "text", value: before }] : []),
|
|
232
|
+
{ type, value: code },
|
|
233
|
+
...(after ? [{ type: "text", value: after }] : [])
|
|
234
|
+
];
|
|
235
|
+
}
|
|
236
|
+
// org ends code at the first `~` it may close on (`~a~ b~`); verbatim
|
|
237
|
+
// keeps such code whole, and comes back as md code too, unless a `=`
|
|
238
|
+
// ends it early in turn
|
|
239
|
+
function codeType(code) {
|
|
240
|
+
return ["code", "verbatim"].find(type => !code.includes(type === "code" ? "~" : "=") ||
|
|
241
|
+
readsWhole({ type, value: code }));
|
|
242
|
+
}
|
|
243
|
+
// whether org reads `node`, rendered, back as the same node
|
|
244
|
+
function readsWhole(node) {
|
|
245
|
+
const [paragraph] = orgParser.parse(renderInline(node)).children;
|
|
246
|
+
const [first, ...rest] = paragraph && "children" in paragraph ? paragraph.children : [];
|
|
247
|
+
return !rest.length && first?.type === node.type && first.value === node.value;
|
|
248
|
+
}
|
|
114
249
|
function transformMdastInlineMath(node) {
|
|
115
250
|
const value = node.value;
|
|
116
251
|
return {
|
|
@@ -144,8 +279,6 @@ function transformMdastPhrasingContentToUniorgObject(ctx, node) {
|
|
|
144
279
|
return transformMdastLinkReference(ctx, node);
|
|
145
280
|
case "imageReference":
|
|
146
281
|
return transformMdastImageReference(ctx, node);
|
|
147
|
-
case "inlineCode":
|
|
148
|
-
return { type: "code", value: node.value };
|
|
149
282
|
case "inlineMath":
|
|
150
283
|
return transformMdastInlineMath(node);
|
|
151
284
|
case "break":
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* md→org: a decoded file link path, or its search option, as org link
|
|
3
|
+
* text.
|
|
4
|
+
*/
|
|
5
|
+
export declare function escapeOrgPath(part: string, isSearch?: boolean): string;
|
|
6
|
+
/**
|
|
7
|
+
* org→md: the inverse of `escapeOrgPath`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function unescapeOrgPath(part: string): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// an org bracket-link path cannot contain [ or ], and `::` starts its
|
|
2
|
+
// search option. morg percent-encodes those (uniorg does not decode org's
|
|
3
|
+
// own backslash escaping); a literal % in front of such an escape gets
|
|
4
|
+
// one more `25`, so the encoding stays reversible (`%5B` ↔ `%255B`)
|
|
5
|
+
const ESCAPE_RE = /%((?:25)*)(5B|5D|3A)/g;
|
|
6
|
+
const ESCAPED = { "5B": "[", "5D": "]", "3A": ":" };
|
|
7
|
+
/**
|
|
8
|
+
* md→org: a decoded file link path, or its search option, as org link
|
|
9
|
+
* text.
|
|
10
|
+
*/
|
|
11
|
+
export function escapeOrgPath(part, isSearch = false) {
|
|
12
|
+
const escaped = part
|
|
13
|
+
.replace(ESCAPE_RE, "%25$1$2")
|
|
14
|
+
.replaceAll("[", "%5B")
|
|
15
|
+
.replaceAll("]", "%5D");
|
|
16
|
+
// a colon next to another, or before the `::` separator
|
|
17
|
+
return isSearch ? escaped : escaped.replace(/:(?=:|$)/g, "%3A");
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* org→md: the inverse of `escapeOrgPath`.
|
|
21
|
+
*/
|
|
22
|
+
export function unescapeOrgPath(part) {
|
|
23
|
+
return part.replace(ESCAPE_RE, (_, more, code) => more ? `%${more.slice(2)}${code}` : (ESCAPED[code] ?? code));
|
|
24
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Parent } from "unist";
|
|
2
|
+
export type Node = Parent["children"][number] & {
|
|
3
|
+
value?: string;
|
|
4
|
+
};
|
|
5
|
+
export declare const orgParser: import("unified").Processor<import("uniorg").OrgData, undefined, undefined, undefined, undefined>;
|
|
6
|
+
export declare const positionParser: import("unified").Processor<import("uniorg").OrgData, undefined, undefined, undefined, undefined>;
|
|
7
|
+
/**
|
|
8
|
+
* Parses `text` as an org document, or returns undefined where uniorg
|
|
9
|
+
* throws: it takes a line starting `_.` or `_)` for a bullet, then
|
|
10
|
+
* fails to read it (org has no such bullet; see underscoreBullets).
|
|
11
|
+
*/
|
|
12
|
+
export declare function tryParse(text: string, parser?: {
|
|
13
|
+
parse(text: string): unknown;
|
|
14
|
+
}): Parent | undefined;
|
|
15
|
+
export declare function isInline(node: Node | Parent): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* How org renders an inline node within its line.
|
|
18
|
+
*/
|
|
19
|
+
export declare function renderInline(node: Node): string;
|
|
20
|
+
/**
|
|
21
|
+
* An inline node's delimiters around its children, as org renders them
|
|
22
|
+
* (`*` and `*`, `[[url][` and `]]`).
|
|
23
|
+
*/
|
|
24
|
+
export declare function delimiters(node: Node): [string, string];
|
|
25
|
+
/**
|
|
26
|
+
* The org rendering of each child; a block element only ends a line.
|
|
27
|
+
*/
|
|
28
|
+
export declare function renderChildren(children: Node[]): string[];
|
|
29
|
+
/**
|
|
30
|
+
* The index of the rendered child `offset` (into the joined rendering)
|
|
31
|
+
* lies in, and the offset within that child.
|
|
32
|
+
*/
|
|
33
|
+
export declare function locate(rendered: string[], offset: number): [number, number];
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { unified } from "unified";
|
|
2
|
+
import uniorgParse from "uniorg-parse";
|
|
3
|
+
import { uniorgStringify } from "uniorg-stringify";
|
|
4
|
+
// built once: constructing a processor per parse dominates the cost
|
|
5
|
+
export const orgParser = unified().use(uniorgParse).freeze();
|
|
6
|
+
export const positionParser = unified()
|
|
7
|
+
.use(uniorgParse, { trackPosition: true })
|
|
8
|
+
.freeze();
|
|
9
|
+
const stringifier = unified().use(uniorgStringify).freeze();
|
|
10
|
+
/**
|
|
11
|
+
* Parses `text` as an org document, or returns undefined where uniorg
|
|
12
|
+
* throws: it takes a line starting `_.` or `_)` for a bullet, then
|
|
13
|
+
* fails to read it (org has no such bullet; see underscoreBullets).
|
|
14
|
+
*/
|
|
15
|
+
export function tryParse(text, parser = orgParser) {
|
|
16
|
+
try {
|
|
17
|
+
return parser.parse(text);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// uniorg's inline node types; anything else is a block element (in a
|
|
24
|
+
// list item's flattened content: a nested list or code block)
|
|
25
|
+
const INLINE_TYPES = new Set([
|
|
26
|
+
"text",
|
|
27
|
+
"bold",
|
|
28
|
+
"italic",
|
|
29
|
+
"underline",
|
|
30
|
+
"strike-through",
|
|
31
|
+
"code",
|
|
32
|
+
"verbatim",
|
|
33
|
+
"link",
|
|
34
|
+
"footnote-reference",
|
|
35
|
+
"latex-fragment",
|
|
36
|
+
"entity",
|
|
37
|
+
"timestamp",
|
|
38
|
+
"subscript",
|
|
39
|
+
"superscript",
|
|
40
|
+
"export-snippet",
|
|
41
|
+
"statistics-cookie",
|
|
42
|
+
"citation",
|
|
43
|
+
"line-break"
|
|
44
|
+
]);
|
|
45
|
+
export function isInline(node) {
|
|
46
|
+
return INLINE_TYPES.has(node.type);
|
|
47
|
+
}
|
|
48
|
+
// ends the rendering, so the paragraph's own trailing newline and
|
|
49
|
+
// whitespace trimming stay out of it
|
|
50
|
+
const SENTINEL = "\u0000";
|
|
51
|
+
/**
|
|
52
|
+
* How org renders an inline node within its line.
|
|
53
|
+
*/
|
|
54
|
+
export function renderInline(node) {
|
|
55
|
+
if (node.type === "text") {
|
|
56
|
+
return node.value ?? "";
|
|
57
|
+
}
|
|
58
|
+
const rendered = String(stringifier.stringify({
|
|
59
|
+
type: "org-data",
|
|
60
|
+
children: [
|
|
61
|
+
{
|
|
62
|
+
type: "paragraph",
|
|
63
|
+
children: [node, { type: "text", value: SENTINEL }]
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}));
|
|
67
|
+
return rendered.slice(0, rendered.lastIndexOf(SENTINEL));
|
|
68
|
+
}
|
|
69
|
+
// stands in for an inline node's content
|
|
70
|
+
const CONTENT = "\u0001";
|
|
71
|
+
/**
|
|
72
|
+
* An inline node's delimiters around its children, as org renders them
|
|
73
|
+
* (`*` and `*`, `[[url][` and `]]`).
|
|
74
|
+
*/
|
|
75
|
+
export function delimiters(node) {
|
|
76
|
+
const [open = "", close = ""] = renderInline({
|
|
77
|
+
...node,
|
|
78
|
+
children: [{ type: "text", value: CONTENT }]
|
|
79
|
+
}).split(CONTENT);
|
|
80
|
+
return [open, close];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The org rendering of each child; a block element only ends a line.
|
|
84
|
+
*/
|
|
85
|
+
export function renderChildren(children) {
|
|
86
|
+
return children.map(child => (isInline(child) ? renderInline(child) : "\n"));
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The index of the rendered child `offset` (into the joined rendering)
|
|
90
|
+
* lies in, and the offset within that child.
|
|
91
|
+
*/
|
|
92
|
+
export function locate(rendered, offset) {
|
|
93
|
+
let start = 0;
|
|
94
|
+
for (const [index, part] of rendered.entries()) {
|
|
95
|
+
if (offset < start + part.length) {
|
|
96
|
+
return [index, offset - start];
|
|
97
|
+
}
|
|
98
|
+
start += part.length;
|
|
99
|
+
}
|
|
100
|
+
return [-1, 0];
|
|
101
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Parent } from "unist";
|
|
2
|
+
/**
|
|
3
|
+
* md→org: a `|` in a table cell's text (md `\|`) would end the cell; org
|
|
4
|
+
* has no escaped `|`, but renders the `\vert` entity as one, `{}` ending
|
|
5
|
+
* its name before any letter. In code, where the entity would be
|
|
6
|
+
* literal, the lookalike stands in, which is reported. Runs after the
|
|
7
|
+
* preset, whose wikilink aliases (`[[Page|alias]]`) are no text.
|
|
8
|
+
* @param tree The document.
|
|
9
|
+
* @param onWarning Reports a lookalike written.
|
|
10
|
+
*/
|
|
11
|
+
export declare function escapeTablePipes(tree: Parent, onWarning?: (message: string) => void): void;
|
|
12
|
+
/**
|
|
13
|
+
* org→md: the lookalike in code in a table cell back to the `|` it
|
|
14
|
+
* stands in for.
|
|
15
|
+
* @param tree The document.
|
|
16
|
+
*/
|
|
17
|
+
export declare function unescapeTablePipes(tree: Parent): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { visit } from "unist-util-visit";
|
|
2
|
+
// org has no escape inside ~code~ or =verbatim=: a `|` there would end
|
|
3
|
+
// the cell, so a lookalike (U+2223 DIVIDES) stands in for it
|
|
4
|
+
const LOOKALIKE = "∣";
|
|
5
|
+
/**
|
|
6
|
+
* md→org: a `|` in a table cell's text (md `\|`) would end the cell; org
|
|
7
|
+
* has no escaped `|`, but renders the `\vert` entity as one, `{}` ending
|
|
8
|
+
* its name before any letter. In code, where the entity would be
|
|
9
|
+
* literal, the lookalike stands in, which is reported. Runs after the
|
|
10
|
+
* preset, whose wikilink aliases (`[[Page|alias]]`) are no text.
|
|
11
|
+
* @param tree The document.
|
|
12
|
+
* @param onWarning Reports a lookalike written.
|
|
13
|
+
*/
|
|
14
|
+
export function escapeTablePipes(tree, onWarning) {
|
|
15
|
+
let replaced = false;
|
|
16
|
+
visit(tree, "table-cell", (cell) => {
|
|
17
|
+
visit(cell, (node) => {
|
|
18
|
+
if (node.type === "text") {
|
|
19
|
+
node.value = node.value?.replaceAll("|", "\\vert{}");
|
|
20
|
+
}
|
|
21
|
+
else if (isCode(node) && node.value?.includes("|")) {
|
|
22
|
+
node.value = node.value.replaceAll("|", LOOKALIKE);
|
|
23
|
+
replaced = true;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
if (replaced) {
|
|
28
|
+
onWarning?.("a | in code in a table cell becomes ∣ (U+2223) in org");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* org→md: the lookalike in code in a table cell back to the `|` it
|
|
33
|
+
* stands in for.
|
|
34
|
+
* @param tree The document.
|
|
35
|
+
*/
|
|
36
|
+
export function unescapeTablePipes(tree) {
|
|
37
|
+
visit(tree, "table-cell", (cell) => {
|
|
38
|
+
visit(cell, (node) => {
|
|
39
|
+
if (isCode(node)) {
|
|
40
|
+
node.value = node.value?.replaceAll(LOOKALIKE, "|");
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function isCode(node) {
|
|
46
|
+
return node.type === "code" || node.type === "verbatim";
|
|
47
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Parent } from "unist";
|
|
2
|
+
/**
|
|
3
|
+
* org→md: guards the lines uniorg misreads before parsing.
|
|
4
|
+
*/
|
|
5
|
+
export declare function guardUnderscoreBullets(org: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* org→md: drops the guards again, also where uniorg reads no text (a
|
|
8
|
+
* src block's code). A zero-width space the author put there goes too.
|
|
9
|
+
*/
|
|
10
|
+
export declare function dropUnderscoreBulletGuards(tree: Parent): void;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { visit } from "unist-util-visit";
|
|
2
|
+
import { ZERO_WIDTH_SPACE } from "./markupBoundary.js";
|
|
3
|
+
// ====================================================================
|
|
4
|
+
// WORKAROUND for a bug in uniorg-parse 3.2.2 (upstream issue: not filed
|
|
5
|
+
// yet, see TODO.md). Drop this module once a fixed version is in; the
|
|
6
|
+
// canary in tests/uniorgWorkarounds.spec.ts fails then.
|
|
7
|
+
// ====================================================================
|
|
8
|
+
// Its `listItemRe` takes a line starting `_.` or `_)` for a list item
|
|
9
|
+
// (`\w` includes `_`), but its `fullListItemRe`, like org itself, has
|
|
10
|
+
// no such bullet. `parseListStructure` then throws (`match error`), or,
|
|
11
|
+
// when a real bullet line follows, reads that one instead and silently
|
|
12
|
+
// drops the line. Org reads the line as text, and so does uniorg with a
|
|
13
|
+
// zero-width space in front (morg's line-start escape, see lineSyntax)
|
|
14
|
+
const UNDERSCORE_BULLET_RE = /^([ \t]*)(?=_[.)](?:[ \t]|$))/gm;
|
|
15
|
+
/**
|
|
16
|
+
* org→md: guards the lines uniorg misreads before parsing.
|
|
17
|
+
*/
|
|
18
|
+
export function guardUnderscoreBullets(org) {
|
|
19
|
+
return org.replace(UNDERSCORE_BULLET_RE, `$1${ZERO_WIDTH_SPACE}`);
|
|
20
|
+
}
|
|
21
|
+
const GUARD_RE = new RegExp(`(^|\\n)([ \\t]*)${ZERO_WIDTH_SPACE}(?=_[.)](?:[ \\t\\n]|$))`, "g");
|
|
22
|
+
/**
|
|
23
|
+
* org→md: drops the guards again, also where uniorg reads no text (a
|
|
24
|
+
* src block's code). A zero-width space the author put there goes too.
|
|
25
|
+
*/
|
|
26
|
+
export function dropUnderscoreBulletGuards(tree) {
|
|
27
|
+
visit(tree, (node) => {
|
|
28
|
+
if (typeof node.value === "string") {
|
|
29
|
+
node.value = node.value.replace(GUARD_RE, "$1$2");
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -180,7 +180,7 @@ function transformPropertyDrawer(ctx, node) {
|
|
|
180
180
|
}
|
|
181
181
|
const isms = (node.children || [])
|
|
182
182
|
.filter(child => child.type === "node-property")
|
|
183
|
-
.map(
|
|
183
|
+
.map(({ key, value }) => (value ? `${key}:: ${value}` : `${key}::`));
|
|
184
184
|
return isms.length ? keyValueParagraph(isms) : null;
|
|
185
185
|
}
|
|
186
186
|
function transformParagraph(ctx, node) {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { Root as MdastRoot } from "mdast";
|
|
2
2
|
import type { OrgData } from "uniorg";
|
|
3
|
-
import type
|
|
3
|
+
import { type UniorgToMdastOptions } from "./shared.js";
|
|
4
4
|
export type { UniorgToMdastOptions } from "./shared.js";
|
|
5
5
|
/**
|
|
6
6
|
* Transforms a uniorg AST to a mdast (Markdown AST).
|
|
7
7
|
* @param uniorgAst The uniorg AST to transform.
|
|
8
|
-
* @param options Controls org-ism serialization (`key:: value` lines)
|
|
8
|
+
* @param options Controls org-ism serialization (`key:: value` lines);
|
|
9
|
+
* `org`, the text a tree was parsed from, finds its frontmatter block.
|
|
9
10
|
* @returns The transformed mdast.
|
|
10
11
|
*/
|
|
11
12
|
export declare function transformUniorgAstToMdast(uniorgAst: OrgData, options?: UniorgToMdastOptions): MdastRoot;
|