@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
package/dist/presets/logseq.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
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";
|
|
3
6
|
/**
|
|
4
7
|
* Logseq dialect preset: `heading::` properties and outline nesting.
|
|
5
8
|
*/
|
|
@@ -7,6 +10,7 @@ export function logseq(options = {}) {
|
|
|
7
10
|
const nestUnderHeadings = options.nestUnderHeadings ?? true;
|
|
8
11
|
return {
|
|
9
12
|
name: "logseq",
|
|
13
|
+
applyToMdast: keepPagePropertySource,
|
|
10
14
|
applyToUniorg: uniorgAst => applyLogseqSpecificsToUniorgAst(uniorgAst, nestUnderHeadings),
|
|
11
15
|
extractFromUniorg: extractLogseqSpecificsFromUniorgAst
|
|
12
16
|
};
|
|
@@ -87,6 +91,7 @@ function toBlockHeadline(node, level) {
|
|
|
87
91
|
*/
|
|
88
92
|
export function applyLogseqSpecificsToUniorgAst(uniorgAst, nestUnderHeadings = true) {
|
|
89
93
|
rewriteLabeledPageRefs(uniorgAst);
|
|
94
|
+
takePageProperties(uniorgAst);
|
|
90
95
|
const children = uniorgAst.children;
|
|
91
96
|
const result = [];
|
|
92
97
|
let currentLevel = 0;
|
|
@@ -135,6 +140,130 @@ export function applyLogseqSpecificsToUniorgAst(uniorgAst, nestUnderHeadings = t
|
|
|
135
140
|
uniorgAst.children = result;
|
|
136
141
|
return uniorgAst;
|
|
137
142
|
}
|
|
143
|
+
// Logseq reads a page's first block of `key:: value` lines (md) and its
|
|
144
|
+
// leading `#+key: value` lines (org) as page properties (ADR 0005)
|
|
145
|
+
function pagePropertyEntries(text) {
|
|
146
|
+
const entries = keyValueEntries(text);
|
|
147
|
+
// a key org reads as no keyword name (begin_src) would not come back
|
|
148
|
+
return entries?.every(entry => fitsKeywordLine(...entry)) ? entries : null;
|
|
149
|
+
}
|
|
150
|
+
// md→org: the page property block's values are Logseq's, not Markdown;
|
|
151
|
+
// its source text replaces the parse, so urls and markup stay verbatim
|
|
152
|
+
function keepPagePropertySource(mdast, markdown) {
|
|
153
|
+
// below the frontmatter and an Emacs mode line
|
|
154
|
+
const node = mdast.children.find(child => child.type !== "yaml" &&
|
|
155
|
+
!(child.type === "html" &&
|
|
156
|
+
child.value.startsWith("<!--") &&
|
|
157
|
+
isModeLine(child.value)));
|
|
158
|
+
const start = node?.position?.start.offset;
|
|
159
|
+
const end = node?.position?.end.offset;
|
|
160
|
+
if (node?.type !== "paragraph" || start === undefined || end === undefined) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const source = markdown.slice(start, end);
|
|
164
|
+
if (pagePropertyEntries(source)) {
|
|
165
|
+
node.children = [{ type: "text", value: source }];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function takePageProperties(uniorgAst) {
|
|
169
|
+
const children = uniorgAst.children;
|
|
170
|
+
const keywords = takeFrontmatterKeywords(children);
|
|
171
|
+
// below the keywords, the file-level drawer and a mode line
|
|
172
|
+
const index = children.findIndex(node => !["keyword", "property-drawer"].includes(node.type) &&
|
|
173
|
+
!isModeLineComment(node) &&
|
|
174
|
+
!isFrontmatterNode(node));
|
|
175
|
+
const lines = pagePropertyLines(children[index]);
|
|
176
|
+
if (lines) {
|
|
177
|
+
children.splice(index, 1);
|
|
178
|
+
keywords.push(...lines);
|
|
179
|
+
}
|
|
180
|
+
// ahead of the block, so all of them lead the page
|
|
181
|
+
const at = children.findIndex(node => node.type !== "keyword");
|
|
182
|
+
children.splice(at === -1 ? children.length : at, 0, ...keywords.map(([key, value]) => ({ type: "keyword", key, value })));
|
|
183
|
+
}
|
|
184
|
+
function takeFrontmatterKeywords(children) {
|
|
185
|
+
const frontmatter = children.find(isFrontmatterNode);
|
|
186
|
+
if (!frontmatter) {
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
const keywords = takeFlatEntries(frontmatter);
|
|
190
|
+
// an emptied block goes
|
|
191
|
+
if (!frontmatter.yaml && keywords.length) {
|
|
192
|
+
children.splice(children.indexOf(frontmatter), 1);
|
|
193
|
+
}
|
|
194
|
+
return keywords;
|
|
195
|
+
}
|
|
196
|
+
function pagePropertyLines(node) {
|
|
197
|
+
// plain text only: markup in a key means the source was no key:: line
|
|
198
|
+
const children = node?.children;
|
|
199
|
+
if (node?.type !== "paragraph" ||
|
|
200
|
+
!children?.every(child => child.type === "text")) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
return pagePropertyEntries(toString(node));
|
|
204
|
+
}
|
|
205
|
+
// keywords that act in Emacs or in export (TODO states, startup and
|
|
206
|
+
// export options, file inclusion, babel calls, a dynamic block's begin
|
|
207
|
+
// and end lines, citations, a table of
|
|
208
|
+
// contents, index entries, raw export lines and exporter options, of
|
|
209
|
+
// org's own exporters, org-info.js and KOMA letter class files among
|
|
210
|
+
// them, and of ox-hugo and org-re-reveal): as frontmatter they were
|
|
211
|
+
// passive data, so they stay in the block. A denylist: other
|
|
212
|
+
// third-party exporters' keys pass. Export metadata (`title`, `author`)
|
|
213
|
+
// passes too, and `tags`, Logseq's page tags property
|
|
214
|
+
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;
|
|
215
|
+
// flat frontmatter entries are page properties too, a sequence written
|
|
216
|
+
// as Logseq writes it (`a, b`); what a keyword line cannot hold stays
|
|
217
|
+
function takeFlatEntries(frontmatter) {
|
|
218
|
+
const { keywords, yaml } = takeFrontmatterEntries(frontmatter.yaml, (key, value, text) => {
|
|
219
|
+
const flat = flatValue(value, text);
|
|
220
|
+
// a key that comes back as a key:: line as written: uniorg
|
|
221
|
+
// upper-cases it, Logseq reads it lower-cased
|
|
222
|
+
return flat !== null &&
|
|
223
|
+
/^[a-z0-9_-]+$/.test(text(key)) &&
|
|
224
|
+
fitsKeywordLine(text(key), flat) &&
|
|
225
|
+
!ACTING_KEYWORD_RE.test(text(key))
|
|
226
|
+
? [[text(key), flat]]
|
|
227
|
+
: null;
|
|
228
|
+
});
|
|
229
|
+
frontmatter.yaml = yaml;
|
|
230
|
+
return keywords;
|
|
231
|
+
}
|
|
232
|
+
function flatValue(value, text) {
|
|
233
|
+
const single = (item) => isScalar(item) && item.value !== null ? text(item) : null;
|
|
234
|
+
if (!isSeq(value)) {
|
|
235
|
+
return single(value);
|
|
236
|
+
}
|
|
237
|
+
const items = value.items.map(single);
|
|
238
|
+
return items.length &&
|
|
239
|
+
items.every(item => item !== null && !item.includes(","))
|
|
240
|
+
? items.join(", ")
|
|
241
|
+
: null;
|
|
242
|
+
}
|
|
243
|
+
// the reverse: leading keywords become the first block, verbatim, keys
|
|
244
|
+
// lower-cased (uniorg upper-cases them, Logseq reads only lower case)
|
|
245
|
+
function pageProperties(uniorgAst) {
|
|
246
|
+
const children = uniorgAst.children;
|
|
247
|
+
// below a mode line, as on the way in
|
|
248
|
+
const first = children.findIndex(node => !isModeLineComment(node));
|
|
249
|
+
let count = 0;
|
|
250
|
+
while (first !== -1 && children[first + count]?.type === "keyword") {
|
|
251
|
+
count++;
|
|
252
|
+
}
|
|
253
|
+
const leading = children.slice(first, first + count);
|
|
254
|
+
// a key no `key::` line holds (`CAPTION[short]`) stays a keyword
|
|
255
|
+
const properties = leading.filter(keyword => /^[\w-]+$/.test(keyword.key));
|
|
256
|
+
if (!properties.length) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const lines = properties.map(keyword => `${keyword.key.toLowerCase()}::${keyword.value ? ` ${keyword.value}` : ""}`);
|
|
260
|
+
children.splice(first, count, ...leading.filter(keyword => !properties.includes(keyword)), {
|
|
261
|
+
type: "paragraph",
|
|
262
|
+
children: [{ type: "verbatim-inline", value: lines.join("\n") }],
|
|
263
|
+
contentsBegin: 0,
|
|
264
|
+
contentsEnd: 0
|
|
265
|
+
});
|
|
266
|
+
}
|
|
138
267
|
// Logseq md keeps TODO/DONE as leading text markers and priorities as
|
|
139
268
|
// [#A] text; in org they are the headline's TODO keyword and priority
|
|
140
269
|
// (other Logseq markers like DOING are not org keywords and simply
|
|
@@ -162,6 +291,7 @@ function takeTaskMarker(headline) {
|
|
|
162
291
|
* @returns The generic uniorg AST.
|
|
163
292
|
*/
|
|
164
293
|
function extractLogseqSpecificsFromUniorgAst(uniorgAst) {
|
|
294
|
+
pageProperties(uniorgAst);
|
|
165
295
|
extractInParent(uniorgAst);
|
|
166
296
|
repairHighlights(uniorgAst);
|
|
167
297
|
fuzzyLinksToPageRefs(uniorgAst);
|
package/dist/presets/types.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import type { Root as MdastRoot } from "mdast";
|
|
1
2
|
import type { OrgData } from "uniorg";
|
|
2
3
|
/**
|
|
3
4
|
* A dialect preset: transforms applied on top of the dialect-agnostic core.
|
|
4
|
-
* `
|
|
5
|
+
* `applyToMdast` runs in the md→org pipeline before the generic
|
|
6
|
+
* transform, with the Markdown source at hand; `applyToUniorg` runs in the md→org pipeline after the generic transform;
|
|
5
7
|
* `extractFromUniorg` runs in the org→md pipeline before the generic
|
|
6
8
|
* transform, normalizing dialect conventions back to generic uniorg.
|
|
7
9
|
*/
|
|
8
10
|
export interface Preset {
|
|
9
11
|
name: string;
|
|
12
|
+
applyToMdast?: (mdast: MdastRoot, markdown: string) => void;
|
|
10
13
|
applyToUniorg?: (uniorgAst: OrgData) => OrgData;
|
|
11
14
|
extractFromUniorg?: (uniorgAst: OrgData) => OrgData;
|
|
12
15
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remigius42/morg",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Bidirectional Markdown ↔ Org-mode converter with round-trip convergence",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"org-mode",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"prepare": "husky",
|
|
47
47
|
"prepack": "npm run build",
|
|
48
48
|
"version": "node scripts/release-changelog.mjs && git add CHANGELOG.md",
|
|
49
|
+
"postversion": "printf '\\nNext:\\n git push origin v%s\\n wait for the Release run to pass\\n git push\\n' \"$npm_package_version\"",
|
|
49
50
|
"build": "tsc -p tsconfig.build.json",
|
|
50
51
|
"build:web": "vite build web",
|
|
51
52
|
"dev:web": "vite web",
|
|
@@ -61,6 +62,8 @@
|
|
|
61
62
|
"lint:fix": "npm run lint:fix:prettier && npm run lint:fix:eslint",
|
|
62
63
|
"lint:fix:prettier": "prettier --write '**/*'",
|
|
63
64
|
"lint:fix:eslint": "eslint --fix '**/*.{ts,mts,js}'",
|
|
65
|
+
"perf": "npm run build && node scripts/perf/perf.mjs",
|
|
66
|
+
"perf:record": "node scripts/perf/perf.mjs --record",
|
|
64
67
|
"test:unit": "vitest",
|
|
65
68
|
"test:unit:ci": "vitest run --reporter default --coverage",
|
|
66
69
|
"test:coverage": "vitest run --coverage",
|