@remigius42/morg 0.10.0 → 0.10.1

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.
@@ -4,7 +4,7 @@ import { positionParser, tryParse } from "../core/render.js";
4
4
  import { ZERO_WIDTH_SPACE } from "../core/markupBoundary.js";
5
5
  import { mapOutsideCode } from "../core/outsideCode.js";
6
6
  import { isDrawerStart, isOrgBlockStart, orgElementEnd } from "../core/passthroughSource.js";
7
- import { readVanillaMarkdownOutline } from "./logseqVanillaMarkdown.js";
7
+ import { columns, dedent as dedentColumns, readVanillaMarkdownOutline } from "./logseqVanillaMarkdown.js";
8
8
  // Logseq stores a page as an outline of blocks, each block a content
9
9
  // string it parses on its own: org writes a block as its level's stars,
10
10
  // a space and the content (an empty block as the bare stars), Markdown
@@ -710,14 +710,14 @@ function queryCodeToBlocks(content) {
710
710
  }
711
711
  return result;
712
712
  }
713
- // Logseq Markdown: spaces after a bullet the whole content shares, which
714
- // a Vanilla list item would read as its content's column
713
+ // Logseq Markdown: indentation after a bullet the whole content shares,
714
+ // which a Vanilla list item would read as its content's column
715
715
  function dedentCommon(content) {
716
716
  const indent = Math.min(...content
717
717
  .filter(line => line.trim())
718
- .map(line => /^ */.exec(line)?.[0].length ?? 0));
718
+ .map(line => columns(/^[ \t]*/.exec(line)?.[0] ?? "")));
719
719
  return Number.isFinite(indent) && indent
720
- ? content.map(line => line.slice(Math.min(indent, line.length)))
720
+ ? content.map(line => dedentColumns(line, indent))
721
721
  : content;
722
722
  }
723
723
  // a translation's page links, but in code
@@ -1,5 +1,7 @@
1
1
  import type { Outline } from "./logseqOutline.js";
2
2
  import type { ConversionContext } from "./types.js";
3
+ export declare function dedent(line: string, column: number): string;
4
+ export declare function columns(text: string, start?: number): number;
3
5
  /**
4
6
  * Reads a Vanilla Markdown page into Logseq's outline.
5
7
  * @param markdown The Markdown page.
@@ -70,10 +70,29 @@ function readMeta(lines, context) {
70
70
  }
71
71
  return meta;
72
72
  }
73
- // a lazy continuation line sits anywhere left of the column
74
- function dedent(line, column) {
75
- const indent = /^ */.exec(line)?.[0].length ?? 0;
76
- return line.slice(Math.min(indent, column));
73
+ // a lazy continuation line sits anywhere left of the column; a tab
74
+ // reaches the next tab stop (4), the part of it right of the column
75
+ // stays as spaces
76
+ export function dedent(line, column) {
77
+ let width = 0;
78
+ let i = 0;
79
+ while (width < column && (line[i] === " " || line[i] === "\t")) {
80
+ width = columns(line[i++] ?? "", width);
81
+ }
82
+ return " ".repeat(Math.max(0, width - column)) + line.slice(i);
83
+ }
84
+ // the column text ends at, from a start column, a tab reaching the next
85
+ // tab stop
86
+ export function columns(text, start = 0) {
87
+ let width = start;
88
+ for (const char of text) {
89
+ width += char === "\t" ? 4 - (width % 4) : 1;
90
+ }
91
+ return width;
92
+ }
93
+ // the column a list item's content starts at
94
+ function contentColumn(line) {
95
+ return columns(BULLET_RE.exec(line)?.[0] ?? "");
77
96
  }
78
97
  function leadingProperties(lines) {
79
98
  const count = lines.findIndex(line => !PROPERTY_RE.test(line));
@@ -83,9 +102,9 @@ function leadingProperties(lines) {
83
102
  // its checkbox read, continuation lines moved left past the bullet
84
103
  function itemSource(item, end, lines) {
85
104
  const [first = "", ...rest] = lines.slice(span(item)[0], end + 1);
86
- const column = BULLET_RE.exec(first)?.[0].length ?? 0;
105
+ const column = contentColumn(first);
87
106
  return [
88
- taskTitle(item, first.slice(column)),
107
+ taskTitle(item, first.slice(BULLET_RE.exec(first)?.[0].length ?? 0)),
89
108
  ...rest.map(line => dedent(line, column))
90
109
  ];
91
110
  }
@@ -123,7 +142,7 @@ function readItem(item, level, ordered, reader) {
123
142
  reader.blocks.push(block);
124
143
  // what follows the first nested list: more lists, and text, which a
125
144
  // block's content cannot hold after its children, as child blocks
126
- const column = BULLET_RE.exec(reader.lines[span(item)[0]] ?? "")?.[0].length;
145
+ const column = contentColumn(reader.lines[span(item)[0]] ?? "");
127
146
  const rest = nested[0]
128
147
  ? item.children.slice(item.children.indexOf(nested[0]))
129
148
  : [];
@@ -140,7 +159,7 @@ function readItem(item, level, ordered, reader) {
140
159
  meta: [],
141
160
  content: reader.lines
142
161
  .slice(start, end + 1)
143
- .map(line => dedent(line, column ?? 0))
162
+ .map(line => dedent(line, column))
144
163
  });
145
164
  }
146
165
  }
@@ -279,7 +298,7 @@ function readText(node, level, body, reader) {
279
298
  const [start, end] = span(node);
280
299
  // a rule's source may be bulleted (`- ---`), a list in a block
281
300
  // read from its own column, as an item's content is
282
- const column = (node.position?.start.column ?? 1) - 1;
301
+ const column = columns(reader.lines[start]?.slice(0, (node.position?.start.column ?? 1) - 1) ?? "");
283
302
  const source = node.type === "thematicBreak"
284
303
  ? ["---"]
285
304
  : reader.lines.slice(start, end + 1).map(line => dedent(line, column));
@@ -23,7 +23,8 @@ export function obsidian() {
23
23
  translateMarkdown: (markdown, context) => context.side === "input" ? toVanilla(markdown) : markdown
24
24
  };
25
25
  }
26
- const ALIASED_WIKILINK_RE = /\[\[([^\][|]+)\|([^\][]+)\]\]/g;
26
+ // not an embed, whose `|300` is a size
27
+ const ALIASED_WIKILINK_RE = /(?<!!)\[\[([^\][|]+)\|([^\][]+)\]\]/g;
27
28
  // in Markdown text: not an embed, whose `|300` is a size, and with a
28
29
  // table cell's escaped pipe (`[[Page\|alias]]`)
29
30
  const ALIASED_PAGE_LINK_RE = /(?<!!)\[\[([^\][|\\]+)\\?\|([^\][]+)\]\]/g;
@@ -44,11 +45,17 @@ function fuzzyLinksToWikilinks(uniorgAst) {
44
45
  return undefined;
45
46
  }
46
47
  const description = node.children.length ? toString(node) : "";
48
+ // an embed's `!` goes with it, else Markdown escapes it before `[`
49
+ const previous = parent.children[index - 1];
50
+ const embed = previous?.type === "text" && previous.value.endsWith("!");
51
+ if (embed) {
52
+ previous.value = previous.value.slice(0, -1);
53
+ }
47
54
  parent.children[index] = {
48
55
  type: "verbatim-inline",
49
- value: description
56
+ value: `${embed ? "!" : ""}${description
50
57
  ? `[[${node.rawLink}|${description}]]`
51
- : `[[${node.rawLink}]]`
58
+ : `[[${node.rawLink}]]`}`
52
59
  };
53
60
  return undefined;
54
61
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remigius42/morg",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Bidirectional Markdown ↔ Org-mode converter with round-trip convergence",
5
5
  "keywords": [
6
6
  "org-mode",