@remigius42/morg 0.4.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/LICENSE +674 -0
- package/README.md +267 -0
- package/dist/cli/args.d.ts +15 -0
- package/dist/cli/args.js +105 -0
- package/dist/cli/configFile.d.ts +2 -0
- package/dist/cli/configFile.js +18 -0
- package/dist/cli/conversion.d.ts +11 -0
- package/dist/cli/conversion.js +50 -0
- package/dist/cli/error.d.ts +2 -0
- package/dist/cli/error.js +4 -0
- package/dist/cli/formats.d.ts +3 -0
- package/dist/cli/formats.js +46 -0
- package/dist/cli/presets.d.ts +2 -0
- package/dist/cli/presets.js +10 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +54 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.js +22 -0
- package/dist/conversionOptions.d.ts +32 -0
- package/dist/conversionOptions.js +34 -0
- package/dist/core/markdownStyle.d.ts +27 -0
- package/dist/core/markdownStyle.js +108 -0
- package/dist/core/mdastToUniorg/blocks.d.ts +20 -0
- package/dist/core/mdastToUniorg/blocks.js +211 -0
- package/dist/core/mdastToUniorg/context.d.ts +15 -0
- package/dist/core/mdastToUniorg/context.js +7 -0
- package/dist/core/mdastToUniorg/index.d.ts +13 -0
- package/dist/core/mdastToUniorg/index.js +95 -0
- package/dist/core/mdastToUniorg/lists.d.ts +4 -0
- package/dist/core/mdastToUniorg/lists.js +45 -0
- package/dist/core/mdastToUniorg/phrasing.d.ts +4 -0
- package/dist/core/mdastToUniorg/phrasing.js +176 -0
- package/dist/core/uniorgToMdast/elements.d.ts +4 -0
- package/dist/core/uniorgToMdast/elements.js +278 -0
- package/dist/core/uniorgToMdast/footnotes.d.ts +10 -0
- package/dist/core/uniorgToMdast/footnotes.js +54 -0
- package/dist/core/uniorgToMdast/index.d.ts +11 -0
- package/dist/core/uniorgToMdast/index.js +84 -0
- package/dist/core/uniorgToMdast/lists.d.ts +4 -0
- package/dist/core/uniorgToMdast/lists.js +94 -0
- package/dist/core/uniorgToMdast/objects.d.ts +4 -0
- package/dist/core/uniorgToMdast/objects.js +152 -0
- package/dist/core/uniorgToMdast/shared.d.ts +25 -0
- package/dist/core/uniorgToMdast/shared.js +55 -0
- package/dist/core/uniorgToMdast/tables.d.ts +6 -0
- package/dist/core/uniorgToMdast/tables.js +66 -0
- package/dist/fileNames.d.ts +16 -0
- package/dist/fileNames.js +34 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +8 -0
- package/dist/markdownToOrg.d.ts +8 -0
- package/dist/markdownToOrg.js +194 -0
- package/dist/normalize.d.ts +26 -0
- package/dist/normalize.js +24 -0
- package/dist/options.d.ts +102 -0
- package/dist/options.js +9 -0
- package/dist/orgToMarkdown.d.ts +8 -0
- package/dist/orgToMarkdown.js +61 -0
- package/dist/presets/logseq.d.ts +25 -0
- package/dist/presets/logseq.js +298 -0
- package/dist/presets/obsidian.d.ts +6 -0
- package/dist/presets/obsidian.js +40 -0
- package/dist/presets/registry.d.ts +8 -0
- package/dist/presets/registry.js +25 -0
- package/dist/presets/types.d.ts +12 -0
- package/dist/presets/types.js +1 -0
- package/package.json +112 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { unified } from "unified";
|
|
2
|
+
import remarkParse from "remark-parse";
|
|
3
|
+
import remarkGfm from "remark-gfm";
|
|
4
|
+
import remarkFrontmatter from "remark-frontmatter";
|
|
5
|
+
import remarkMath from "remark-math";
|
|
6
|
+
import { uniorgStringify } from "uniorg-stringify";
|
|
7
|
+
import { visit } from "unist-util-visit";
|
|
8
|
+
import { transformMdastToUniorgAst } from "./core/mdastToUniorg/index.js";
|
|
9
|
+
import { detectMarkdownStyle, STYLE_KEYWORD } from "./core/markdownStyle.js";
|
|
10
|
+
/**
|
|
11
|
+
* Converts a Markdown string to an Org-mode string.
|
|
12
|
+
* @param markdown The Markdown string to convert.
|
|
13
|
+
* @param options Conversion options (md-ism preservation, dialect preset).
|
|
14
|
+
* @returns The converted Org-mode string.
|
|
15
|
+
*/
|
|
16
|
+
export function convertMarkdownToOrg(markdown, options = {}) {
|
|
17
|
+
// Phase 1: Parse Markdown to mdast
|
|
18
|
+
const mdast = unified()
|
|
19
|
+
.use(remarkParse)
|
|
20
|
+
.use(remarkGfm)
|
|
21
|
+
.use(remarkFrontmatter)
|
|
22
|
+
.use(remarkMath)
|
|
23
|
+
.parse(markdown);
|
|
24
|
+
// Phase 2: Generic mdast to uniorg-ast transformation
|
|
25
|
+
let uniorgAst = transformMdastToUniorgAst(mdast, {
|
|
26
|
+
...(options.preserveMdisms !== undefined && {
|
|
27
|
+
preserveMdisms: options.preserveMdisms
|
|
28
|
+
}),
|
|
29
|
+
...(options.interpretHtml !== undefined && {
|
|
30
|
+
interpretHtml: options.interpretHtml
|
|
31
|
+
}),
|
|
32
|
+
...(options.onWarning !== undefined && { onWarning: options.onWarning })
|
|
33
|
+
});
|
|
34
|
+
// Phase 2b: restore key:: value lines below headings to native org
|
|
35
|
+
// syntax (see ADR 0002): known keys become TODO keywords, priorities,
|
|
36
|
+
// tags and planning; unknown keys become property drawer entries.
|
|
37
|
+
// orgismKeys maps custom names back to canonical (per-config).
|
|
38
|
+
const canonicalKeys = new Map(Object.entries(options.orgismKeys ?? {}).map(([canonical, custom]) => [
|
|
39
|
+
custom,
|
|
40
|
+
canonical
|
|
41
|
+
]));
|
|
42
|
+
restoreOrgisms(uniorgAst, canonicalKeys);
|
|
43
|
+
// Phase 2c: formatting-as-structure — adjacent lists need two blank
|
|
44
|
+
// lines between them, or org's parser merges them into one list.
|
|
45
|
+
separateAdjacentLists(uniorgAst);
|
|
46
|
+
// Phase 2d: record the source's own style markers, so the return trip
|
|
47
|
+
// can reproduce them instead of morg's canonical ones (ADR 0004)
|
|
48
|
+
if (options.recordStyle) {
|
|
49
|
+
recordStyleKeyword(uniorgAst, detectMarkdownStyle(mdast, markdown, options.onWarning));
|
|
50
|
+
}
|
|
51
|
+
// Phase 3: Apply dialect preset, if any
|
|
52
|
+
if (options.preset?.applyToUniorg) {
|
|
53
|
+
uniorgAst = options.preset.applyToUniorg(uniorgAst);
|
|
54
|
+
}
|
|
55
|
+
// Phase 4: Render uniorg-ast to Org-mode string
|
|
56
|
+
const processor = unified().use(uniorgStringify);
|
|
57
|
+
const orgContent = processor.stringify(uniorgAst);
|
|
58
|
+
return orgContent;
|
|
59
|
+
}
|
|
60
|
+
// leads the document so it survives the frontmatter keywords following it
|
|
61
|
+
function recordStyleKeyword(uniorgAst, style) {
|
|
62
|
+
if (!Object.keys(style).length) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
uniorgAst.children.unshift({
|
|
66
|
+
type: "keyword",
|
|
67
|
+
key: STYLE_KEYWORD,
|
|
68
|
+
value: JSON.stringify(style)
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const KEY_VALUE_LINE_RE = /^([\w-]+):: (.*)$/;
|
|
72
|
+
function parseKeyValueParagraph(node) {
|
|
73
|
+
if (node?.type !== "paragraph" || !node.children?.length) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
if (!node.children.every(child => child.type === "text")) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const lines = node.children
|
|
80
|
+
.map(child => child.value ?? "")
|
|
81
|
+
.join("")
|
|
82
|
+
.split("\n");
|
|
83
|
+
const entries = [];
|
|
84
|
+
for (const line of lines) {
|
|
85
|
+
const match = KEY_VALUE_LINE_RE.exec(line);
|
|
86
|
+
if (!match) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
entries.push([match[1], match[2]]);
|
|
90
|
+
}
|
|
91
|
+
return entries;
|
|
92
|
+
}
|
|
93
|
+
function makeTimestamp(rawValue) {
|
|
94
|
+
return { type: "timestamp", rawValue };
|
|
95
|
+
}
|
|
96
|
+
// the headline slots have a syntax of their own: a value org could not
|
|
97
|
+
// carry there (a drawer property that merely shares the name, a
|
|
98
|
+
// hand-written line) stays a property instead of corrupting the title
|
|
99
|
+
const TODO_KEYWORD_RE = /^[A-Z][A-Z0-9_-]*$/;
|
|
100
|
+
const PRIORITY_RE = /^[A-Z]$/;
|
|
101
|
+
const TAG_LIST_RE = /^[\w@#%]+(?:,\s*[\w@#%]+)*$/;
|
|
102
|
+
const TIMESTAMP_RE = /^[<[].*[>\]]$/;
|
|
103
|
+
function applyOrgismEntry(headline, planning, properties, key, value) {
|
|
104
|
+
switch (key) {
|
|
105
|
+
case "todo":
|
|
106
|
+
if (!TODO_KEYWORD_RE.test(value)) {
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
headline.todoKeyword = value;
|
|
110
|
+
return;
|
|
111
|
+
case "priority":
|
|
112
|
+
if (!PRIORITY_RE.test(value)) {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
headline.priority = value;
|
|
116
|
+
return;
|
|
117
|
+
case "tags":
|
|
118
|
+
if (!TAG_LIST_RE.test(value)) {
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
headline.tags = value.split(/,\s*/);
|
|
122
|
+
return;
|
|
123
|
+
case "scheduled":
|
|
124
|
+
case "deadline":
|
|
125
|
+
case "closed":
|
|
126
|
+
if (!TIMESTAMP_RE.test(value)) {
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
planning[key] = makeTimestamp(value);
|
|
130
|
+
return;
|
|
131
|
+
default:
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
properties.push({ type: "node-property", key, value });
|
|
135
|
+
}
|
|
136
|
+
function consumeOrgismParagraphs(children, i, canonicalKeys) {
|
|
137
|
+
const headline = children[i];
|
|
138
|
+
const planning = {};
|
|
139
|
+
const properties = [];
|
|
140
|
+
let consumed = 0;
|
|
141
|
+
let entries;
|
|
142
|
+
while ((entries = parseKeyValueParagraph(children[i + 1 + consumed]))) {
|
|
143
|
+
for (const [rawKey, value] of entries) {
|
|
144
|
+
const key = canonicalKeys.get(rawKey) ?? rawKey;
|
|
145
|
+
applyOrgismEntry(headline, planning, properties, key, value);
|
|
146
|
+
}
|
|
147
|
+
consumed++;
|
|
148
|
+
}
|
|
149
|
+
return { planning, properties, consumed };
|
|
150
|
+
}
|
|
151
|
+
function buildOrgismReplacements(planning, properties) {
|
|
152
|
+
const replacements = [];
|
|
153
|
+
if (Object.keys(planning).length) {
|
|
154
|
+
replacements.push({
|
|
155
|
+
type: "planning",
|
|
156
|
+
scheduled: planning.scheduled ?? null,
|
|
157
|
+
deadline: planning.deadline ?? null,
|
|
158
|
+
closed: planning.closed ?? null
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
if (properties.length) {
|
|
162
|
+
replacements.push({
|
|
163
|
+
type: "property-drawer",
|
|
164
|
+
children: properties,
|
|
165
|
+
contentsBegin: 0,
|
|
166
|
+
contentsEnd: 0
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return replacements;
|
|
170
|
+
}
|
|
171
|
+
function restoreOrgisms(uniorgAst, canonicalKeys) {
|
|
172
|
+
const children = uniorgAst.children;
|
|
173
|
+
for (let i = 0; i < children.length; i++) {
|
|
174
|
+
if (children[i]?.type !== "headline") {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const { planning, properties, consumed } = consumeOrgismParagraphs(children, i, canonicalKeys);
|
|
178
|
+
if (!consumed) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const replacements = buildOrgismReplacements(planning, properties);
|
|
182
|
+
children.splice(i + 1, consumed, ...replacements);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function separateAdjacentLists(uniorgAst) {
|
|
186
|
+
visit(uniorgAst, "plain-list", (_node, index, parent) => {
|
|
187
|
+
if (parent.children[index + 1]?.type === "plain-list") {
|
|
188
|
+
const separator = { type: "text", value: "\n\n" };
|
|
189
|
+
parent.children.splice(index + 1, 0, separator);
|
|
190
|
+
return index + 2;
|
|
191
|
+
}
|
|
192
|
+
return undefined;
|
|
193
|
+
});
|
|
194
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { MarkdownToOrgOptions, OrgToMarkdownOptions } from "./options.js";
|
|
2
|
+
/**
|
|
3
|
+
* The full option set of both directions: normalization uses the same
|
|
4
|
+
* configuration as conversion — Convergence is per-config (see ADR
|
|
5
|
+
* 0002), so a file must be normalized with the exact config (preset,
|
|
6
|
+
* style, key names, toggles) it will be converted with.
|
|
7
|
+
*/
|
|
8
|
+
export type NormalizeOptions = MarkdownToOrgOptions & OrgToMarkdownOptions;
|
|
9
|
+
/**
|
|
10
|
+
* Normalizes a Markdown string to morg's canonical form: one full round
|
|
11
|
+
* trip (`md → org → md`), whose output is a fixed point (see ADR 0001).
|
|
12
|
+
* This canonicalizes, it does not just re-style — org-isms and md-isms
|
|
13
|
+
* are rewritten the same way a conversion would rewrite them.
|
|
14
|
+
* @param markdown The Markdown string to normalize.
|
|
15
|
+
* @param options Warning callback and dialect preset.
|
|
16
|
+
* @returns The canonical-form Markdown string.
|
|
17
|
+
*/
|
|
18
|
+
export declare function normalizeMarkdown(markdown: string, options?: NormalizeOptions): string;
|
|
19
|
+
/**
|
|
20
|
+
* Normalizes an Org string to morg's canonical form: one full round trip
|
|
21
|
+
* (`org → md → org`), whose output is a fixed point (see ADR 0001).
|
|
22
|
+
* @param org The Org string to normalize.
|
|
23
|
+
* @param options Warning callback and dialect preset.
|
|
24
|
+
* @returns The canonical-form Org string.
|
|
25
|
+
*/
|
|
26
|
+
export declare function normalizeOrg(org: string, options?: NormalizeOptions): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { convertMarkdownToOrg } from "./markdownToOrg.js";
|
|
2
|
+
import { convertOrgToMarkdown } from "./orgToMarkdown.js";
|
|
3
|
+
/**
|
|
4
|
+
* Normalizes a Markdown string to morg's canonical form: one full round
|
|
5
|
+
* trip (`md → org → md`), whose output is a fixed point (see ADR 0001).
|
|
6
|
+
* This canonicalizes, it does not just re-style — org-isms and md-isms
|
|
7
|
+
* are rewritten the same way a conversion would rewrite them.
|
|
8
|
+
* @param markdown The Markdown string to normalize.
|
|
9
|
+
* @param options Warning callback and dialect preset.
|
|
10
|
+
* @returns The canonical-form Markdown string.
|
|
11
|
+
*/
|
|
12
|
+
export function normalizeMarkdown(markdown, options = {}) {
|
|
13
|
+
return convertOrgToMarkdown(convertMarkdownToOrg(markdown, options), options);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Normalizes an Org string to morg's canonical form: one full round trip
|
|
17
|
+
* (`org → md → org`), whose output is a fixed point (see ADR 0001).
|
|
18
|
+
* @param org The Org string to normalize.
|
|
19
|
+
* @param options Warning callback and dialect preset.
|
|
20
|
+
* @returns The canonical-form Org string.
|
|
21
|
+
*/
|
|
22
|
+
export function normalizeOrg(org, options = {}) {
|
|
23
|
+
return convertMarkdownToOrg(convertOrgToMarkdown(org, options), options);
|
|
24
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { Preset } from "./presets/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* A feature toggle: `true`/`false` switches everything on/off, a record
|
|
4
|
+
* toggles individual constructs by name. Unlisted keys fall back to the
|
|
5
|
+
* flag's default. Mirrors the shape of the (planned) TOML config entries.
|
|
6
|
+
*/
|
|
7
|
+
export type Toggle = boolean | Record<string, boolean>;
|
|
8
|
+
export declare function toggleEnabled(toggle: Toggle | undefined, key: string, defaultValue?: boolean): boolean;
|
|
9
|
+
export interface MarkdownToOrgOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Preserve Markdown constructs without a native Org equivalent (e.g.
|
|
12
|
+
* raw HTML as export blocks/snippets, see ADR 0002). Default: `true`.
|
|
13
|
+
*/
|
|
14
|
+
preserveMdisms?: Toggle;
|
|
15
|
+
/**
|
|
16
|
+
* Interpret the HTML vocabulary morg itself emits under `useHtml`
|
|
17
|
+
* (bare `<u>`, `<sup>`, `<sub>`, `<dl>/<dt>/<dd>` without attributes)
|
|
18
|
+
* as native Org constructs instead of preserving it as an md-ism.
|
|
19
|
+
* The inverse of `useHtml`; with both enabled the round trip is
|
|
20
|
+
* lossless. Any other HTML still preserves per `preserveMdisms`.
|
|
21
|
+
* Default: `false`.
|
|
22
|
+
*/
|
|
23
|
+
interpretHtml?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Record the Markdown style knobs detected in the source as a
|
|
26
|
+
* `#+MORG_MARKDOWN_STYLE:` keyword, so the return trip restores the source's own
|
|
27
|
+
* markers instead of morg's canonical ones (ADR 0004). Widens the set
|
|
28
|
+
* of inputs the Round Trip leaves untouched; it does not replace
|
|
29
|
+
* Convergence. Knobs used inconsistently are not recorded and warn.
|
|
30
|
+
* Default: `false`.
|
|
31
|
+
*/
|
|
32
|
+
recordStyle?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Custom names for org-ism `key::` lines, canonical → custom (e.g.
|
|
35
|
+
* `{ todo: "state" }`). Must match the mapping the file was written
|
|
36
|
+
* with — Convergence is per-config (ADR 0002).
|
|
37
|
+
*/
|
|
38
|
+
orgismKeys?: Record<string, string>;
|
|
39
|
+
/** Called for each construct dropped without an equivalent. */
|
|
40
|
+
onWarning?: (message: string) => void;
|
|
41
|
+
/** Dialect preset applied on top of the generic conversion. */
|
|
42
|
+
preset?: Preset;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Markdown stringifier style knobs (subset of remark-stringify options).
|
|
46
|
+
* Canonical form is parameterized by these (see ADR 0001): round trips
|
|
47
|
+
* must use the same style, and files formatted under one style are not
|
|
48
|
+
* a fixed point under another. Defaults: `-` bullet, `*` emphasis /
|
|
49
|
+
* `*` strong (i.e. `**bold**`), backtick fences, `-` rule — prettier's
|
|
50
|
+
* choices except emphasis (`emphasis: "_"` aligns with prettier).
|
|
51
|
+
*/
|
|
52
|
+
export interface MarkdownStyleOptions {
|
|
53
|
+
/** Unordered list marker. Default: `"-"`. */
|
|
54
|
+
bullet?: "-" | "*" | "+";
|
|
55
|
+
/** Emphasis (italic) marker. Default: `"*"`. */
|
|
56
|
+
emphasis?: "*" | "_";
|
|
57
|
+
/** Strong (bold) marker, doubled in output. Default: `"*"`. */
|
|
58
|
+
strong?: "*" | "_";
|
|
59
|
+
/** Code fence marker. Default: `` "`" ``. */
|
|
60
|
+
fence?: "`" | "~";
|
|
61
|
+
/** Thematic break marker, tripled in output. Default: `"-"`. */
|
|
62
|
+
rule?: "-" | "*" | "_";
|
|
63
|
+
/**
|
|
64
|
+
* How often to repeat the thematic break marker (minimum 3).
|
|
65
|
+
* Default: `3`. mdformat writes 70 underscores (`rule: "_"`,
|
|
66
|
+
* `ruleRepetition: 70`).
|
|
67
|
+
*/
|
|
68
|
+
ruleRepetition?: number;
|
|
69
|
+
}
|
|
70
|
+
export interface OrgToMarkdownOptions {
|
|
71
|
+
/**
|
|
72
|
+
* Serialize Org constructs without a native Markdown equivalent as
|
|
73
|
+
* `key:: value` lines and verbatim passthroughs (see ADR 0002).
|
|
74
|
+
* Default: `true`.
|
|
75
|
+
*/
|
|
76
|
+
preserveOrgisms?: Toggle;
|
|
77
|
+
/**
|
|
78
|
+
* Render Org constructs without a Markdown equivalent as raw HTML
|
|
79
|
+
* (`<u>`, `<sup>`, `<sub>`, `<dl>`) instead of keeping their org markup
|
|
80
|
+
* verbatim. HTML round-trips as a preserved md-ism (export blocks and
|
|
81
|
+
* snippets), not back to native org markup — a one-way door unless
|
|
82
|
+
* the return trip enables its inverse, `interpretHtml`.
|
|
83
|
+
* Default: `false`.
|
|
84
|
+
*/
|
|
85
|
+
useHtml?: Toggle;
|
|
86
|
+
/**
|
|
87
|
+
* Map bare `TODO`/`DONE` leaf headlines (no priority, tags or content)
|
|
88
|
+
* to GFM task items (`- [ ]` / `- [x]`). Documented lossy export mode:
|
|
89
|
+
* headings become list items and do not restore to TODO headlines on
|
|
90
|
+
* the return trip; unmappable states keep the heading and warn.
|
|
91
|
+
* Default: `false`.
|
|
92
|
+
*/
|
|
93
|
+
taskCheckboxes?: boolean;
|
|
94
|
+
/** Markdown output style; canonical form is per-config (ADR 0001). */
|
|
95
|
+
markdownStyle?: MarkdownStyleOptions;
|
|
96
|
+
/** Custom names for org-ism `key::` lines, canonical → custom. */
|
|
97
|
+
orgismKeys?: Record<string, string>;
|
|
98
|
+
/** Called for each construct dropped without an equivalent. */
|
|
99
|
+
onWarning?: (message: string) => void;
|
|
100
|
+
/** Dialect preset extracted before the generic conversion. */
|
|
101
|
+
preset?: Preset;
|
|
102
|
+
}
|
package/dist/options.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { OrgToMarkdownOptions } from "./options.js";
|
|
2
|
+
/**
|
|
3
|
+
* Converts an Org-mode string to a Markdown string.
|
|
4
|
+
* @param org The Org-mode string to convert.
|
|
5
|
+
* @param options Conversion options (org-ism serialization, dialect preset).
|
|
6
|
+
* @returns The converted Markdown string.
|
|
7
|
+
*/
|
|
8
|
+
export declare function convertOrgToMarkdown(org: string, options?: OrgToMarkdownOptions): string;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { unified } from "unified";
|
|
2
|
+
import uniorgParse from "uniorg-parse";
|
|
3
|
+
import remarkStringify from "remark-stringify";
|
|
4
|
+
import remarkGfm from "remark-gfm";
|
|
5
|
+
import remarkFrontmatter from "remark-frontmatter";
|
|
6
|
+
import remarkMath from "remark-math";
|
|
7
|
+
import { transformUniorgAstToMdast } from "./core/uniorgToMdast/index.js";
|
|
8
|
+
import { takeRecordedStyle } from "./core/markdownStyle.js";
|
|
9
|
+
/**
|
|
10
|
+
* Converts an Org-mode string to a Markdown string.
|
|
11
|
+
* @param org The Org-mode string to convert.
|
|
12
|
+
* @param options Conversion options (org-ism serialization, dialect preset).
|
|
13
|
+
* @returns The converted Markdown string.
|
|
14
|
+
*/
|
|
15
|
+
export function convertOrgToMarkdown(org, options = {}) {
|
|
16
|
+
// Phase 1: Parse Org-mode to uniorg-ast
|
|
17
|
+
let uniorgAst = unified().use(uniorgParse).parse(org);
|
|
18
|
+
// Phase 1b: a recorded style is morg's own (ADR 0004) — consume it so
|
|
19
|
+
// it does not travel on as frontmatter; explicit options still win
|
|
20
|
+
const recordedStyle = takeRecordedStyle(uniorgAst);
|
|
21
|
+
// Phase 2: Extract dialect preset conventions, if any
|
|
22
|
+
if (options.preset?.extractFromUniorg) {
|
|
23
|
+
uniorgAst = options.preset.extractFromUniorg(uniorgAst);
|
|
24
|
+
}
|
|
25
|
+
// Phase 3: Generic uniorg-ast to mdast transformation
|
|
26
|
+
const mdast = transformUniorgAstToMdast(uniorgAst, {
|
|
27
|
+
...(options.preserveOrgisms !== undefined && {
|
|
28
|
+
preserveOrgisms: options.preserveOrgisms
|
|
29
|
+
}),
|
|
30
|
+
...(options.useHtml !== undefined && { useHtml: options.useHtml }),
|
|
31
|
+
...(options.taskCheckboxes !== undefined && {
|
|
32
|
+
taskCheckboxes: options.taskCheckboxes
|
|
33
|
+
}),
|
|
34
|
+
...(options.orgismKeys !== undefined && {
|
|
35
|
+
orgismKeys: options.orgismKeys
|
|
36
|
+
}),
|
|
37
|
+
...(options.onWarning !== undefined && { onWarning: options.onWarning })
|
|
38
|
+
});
|
|
39
|
+
// Phase 4: Render mdast to Markdown string
|
|
40
|
+
// bullet and rule "-" (not remark's default "*") are morg's canonical
|
|
41
|
+
// Markdown form; markdownStyle knobs override it (canonical form is
|
|
42
|
+
// then per-config, see ADR 0001)
|
|
43
|
+
const markdownContent = unified()
|
|
44
|
+
.use(remarkStringify, {
|
|
45
|
+
bullet: "-",
|
|
46
|
+
rule: "-",
|
|
47
|
+
...recordedStyle,
|
|
48
|
+
...options.markdownStyle,
|
|
49
|
+
handlers: {
|
|
50
|
+
// key:: value blocks and preset inline passthroughs (e.g.
|
|
51
|
+
// wikilinks) are emitted verbatim, unescaped
|
|
52
|
+
keyValue: (node) => node.value,
|
|
53
|
+
verbatimInline: (node) => node.value
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
.use(remarkGfm)
|
|
57
|
+
.use(remarkFrontmatter)
|
|
58
|
+
.use(remarkMath)
|
|
59
|
+
.stringify(mdast);
|
|
60
|
+
return markdownContent;
|
|
61
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { OrgData } from "uniorg";
|
|
2
|
+
import type { Preset } from "./types.js";
|
|
3
|
+
export interface LogseqPresetOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Content following a heading becomes children of that heading's block
|
|
6
|
+
* in Logseq's outline: paragraphs turn into child block headlines one
|
|
7
|
+
* level deeper; other constructs stay in the preceding block's body.
|
|
8
|
+
* Default: `true`.
|
|
9
|
+
*/
|
|
10
|
+
nestUnderHeadings?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Logseq dialect preset: `heading::` properties and outline nesting.
|
|
14
|
+
*/
|
|
15
|
+
export declare function logseq(options?: LogseqPresetOptions): Preset;
|
|
16
|
+
/**
|
|
17
|
+
* Applies Logseq-specific conventions to a uniorg AST: every heading gets
|
|
18
|
+
* a `:heading:` property drawer, and with `nestUnderHeadings` following
|
|
19
|
+
* paragraphs become child block headlines (in Logseq's org format every
|
|
20
|
+
* outline block is a headline).
|
|
21
|
+
* @param uniorgAst The uniorg AST to transform.
|
|
22
|
+
* @param nestUnderHeadings Nest content as child blocks. Default: `true`.
|
|
23
|
+
* @returns The Logseq-flavored uniorg AST.
|
|
24
|
+
*/
|
|
25
|
+
export declare function applyLogseqSpecificsToUniorgAst(uniorgAst: OrgData, nestUnderHeadings?: boolean): OrgData;
|