github-markdown-adf 1.1.0 → 1.3.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/dist/index.mjs CHANGED
@@ -1,10 +1,13 @@
1
- import { unified } from "unified";
2
- import remarkParse from "remark-parse";
3
- import remarkGfm from "remark-gfm";
1
+ import { fromMarkdown } from "mdast-util-from-markdown";
2
+ import { gfmFromMarkdown } from "mdast-util-gfm";
3
+ import { gfm } from "micromark-extension-gfm";
4
4
  //#region src/md-to-adf/parser.ts
5
- const processor = unified().use(remarkParse).use(remarkGfm);
5
+ const markdownParseOptions = {
6
+ extensions: [gfm()],
7
+ mdastExtensions: [gfmFromMarkdown()]
8
+ };
6
9
  function parseMarkdown(markdown) {
7
- return processor.parse(markdown);
10
+ return fromMarkdown(markdown, markdownParseOptions);
8
11
  }
9
12
  //#endregion
10
13
  //#region src/utils/language.ts
@@ -35,6 +38,7 @@ function normalizeLanguage(lang) {
35
38
  //#region src/md-to-adf/transform/marks.ts
36
39
  const OPEN_HTML_TAG = /^<(ins|sub|sup)>$/i;
37
40
  const CLOSE_HTML_TAG = /^<\/(ins|sub|sup)>$/i;
41
+ const MENTION_PATTERN = /(^|[^A-Za-z0-9_@])@([A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?)(?=$|[^A-Za-z0-9._@-])/g;
38
42
  function htmlTagToMark(tag) {
39
43
  const t = tag.toLowerCase();
40
44
  if (t === "ins") return { type: "underline" };
@@ -48,7 +52,7 @@ function htmlTagToMark(tag) {
48
52
  };
49
53
  return null;
50
54
  }
51
- function phrasingToInlineNodes(nodes) {
55
+ function phrasingToInlineNodes(nodes, options) {
52
56
  const markStack = [];
53
57
  const result = [];
54
58
  for (const node of nodes) {
@@ -64,22 +68,22 @@ function phrasingToInlineNodes(nodes) {
64
68
  continue;
65
69
  }
66
70
  }
67
- result.push(...phrasingToInline(node, markStack.slice()));
71
+ result.push(...phrasingToInline(node, markStack.slice(), options));
68
72
  }
69
73
  return result;
70
74
  }
71
- function phrasingToInline(node, inheritedMarks) {
75
+ function phrasingToInline(node, inheritedMarks, options) {
72
76
  switch (node.type) {
73
77
  case "text":
74
78
  if (!node.value) return [];
75
- return [makeText(node.value, inheritedMarks)];
79
+ return textToInlineNodes(node.value, inheritedMarks, options);
76
80
  case "inlineCode": {
77
81
  const marks = [...inheritedMarks.filter((m) => m.type === "link"), { type: "code" }];
78
82
  return [makeText(node.value, marks)];
79
83
  }
80
- case "strong": return node.children.flatMap((c) => phrasingToInline(c, [...inheritedMarks, { type: "strong" }]));
81
- case "emphasis": return node.children.flatMap((c) => phrasingToInline(c, [...inheritedMarks, { type: "em" }]));
82
- case "delete": return node.children.flatMap((c) => phrasingToInline(c, [...inheritedMarks, { type: "strike" }]));
84
+ case "strong": return node.children.flatMap((c) => phrasingToInline(c, [...inheritedMarks, { type: "strong" }], options));
85
+ case "emphasis": return node.children.flatMap((c) => phrasingToInline(c, [...inheritedMarks, { type: "em" }], options));
86
+ case "delete": return node.children.flatMap((c) => phrasingToInline(c, [...inheritedMarks, { type: "strike" }], options));
83
87
  case "link": {
84
88
  const linkMark = {
85
89
  type: "link",
@@ -88,7 +92,7 @@ function phrasingToInline(node, inheritedMarks) {
88
92
  ...node.title ? { title: node.title } : {}
89
93
  }
90
94
  };
91
- return node.children.flatMap((c) => phrasingToInline(c, [...inheritedMarks, linkMark]));
95
+ return node.children.flatMap((c) => phrasingToInline(c, [...inheritedMarks, linkMark], options));
92
96
  }
93
97
  case "image": return [{
94
98
  type: "inlineCard",
@@ -101,6 +105,27 @@ function phrasingToInline(node, inheritedMarks) {
101
105
  default: return [];
102
106
  }
103
107
  }
108
+ function textToInlineNodes(value, marks, options) {
109
+ if (!options?.mentions || marks.length > 0) return [makeText(value, marks)];
110
+ const result = [];
111
+ let lastIndex = 0;
112
+ for (const match of value.matchAll(MENTION_PATTERN)) {
113
+ const matchIndex = match.index ?? 0;
114
+ const prefix = match[1] ?? "";
115
+ const username = match[2];
116
+ if (!username) continue;
117
+ const mentionStart = matchIndex + prefix.length;
118
+ const mentionEnd = matchIndex + match[0].length;
119
+ appendTextNode(result, value.slice(lastIndex, mentionStart), marks);
120
+ const mentionNode = makeMentionNode(username, options.mentions);
121
+ if (mentionNode) result.push(mentionNode);
122
+ else appendTextNode(result, value.slice(mentionStart, mentionEnd), marks);
123
+ lastIndex = mentionEnd;
124
+ }
125
+ if (result.length === 0) return [makeText(value, marks)];
126
+ appendTextNode(result, value.slice(lastIndex), marks);
127
+ return result;
128
+ }
104
129
  function makeText(value, marks) {
105
130
  const node = {
106
131
  type: "text",
@@ -109,6 +134,31 @@ function makeText(value, marks) {
109
134
  if (marks.length > 0) node.marks = marks;
110
135
  return node;
111
136
  }
137
+ function appendTextNode(result, value, marks) {
138
+ if (!value) return;
139
+ const previousNode = result[result.length - 1];
140
+ if (previousNode?.type === "text" && sameMarks(previousNode.marks, marks)) {
141
+ previousNode.text += value;
142
+ return;
143
+ }
144
+ result.push(makeText(value, marks));
145
+ }
146
+ function makeMentionNode(username, mentions) {
147
+ const attrs = typeof mentions === "function" ? mentions(username) : {
148
+ id: username,
149
+ text: `@${username}`
150
+ };
151
+ if (!attrs) return null;
152
+ return {
153
+ type: "mention",
154
+ attrs
155
+ };
156
+ }
157
+ function sameMarks(leftMarks, rightMarks) {
158
+ const normalizedLeft = leftMarks ?? [];
159
+ if (normalizedLeft.length !== rightMarks.length) return false;
160
+ return normalizedLeft.every((mark, index) => JSON.stringify(mark) === JSON.stringify(rightMarks[index]));
161
+ }
112
162
  function parseHtmlInline(html, inheritedMarks) {
113
163
  const stripped = html.replace(/<[^>]+>/g, "");
114
164
  if (stripped) return [makeText(stripped, inheritedMarks)];
@@ -116,17 +166,17 @@ function parseHtmlInline(html, inheritedMarks) {
116
166
  }
117
167
  //#endregion
118
168
  //#region src/md-to-adf/transform/blocks.ts
119
- function transformHeading(node) {
169
+ function transformHeading(node, options) {
120
170
  return {
121
171
  type: "heading",
122
172
  attrs: { level: node.depth },
123
- content: phrasingToInlineNodes(node.children)
173
+ content: phrasingToInlineNodes(node.children, options)
124
174
  };
125
175
  }
126
- function transformParagraph(node) {
176
+ function transformParagraph(node, options) {
127
177
  return {
128
178
  type: "paragraph",
129
- content: phrasingToInlineNodes(node.children)
179
+ content: phrasingToInlineNodes(node.children, options)
130
180
  };
131
181
  }
132
182
  function transformCode(node) {
@@ -150,7 +200,7 @@ const ALERT_TO_PANEL = {
150
200
  WARNING: "warning",
151
201
  CAUTION: "error"
152
202
  };
153
- function buildPanelContent(firstChild, restChildren, transformChildren) {
203
+ function buildPanelContent(firstChild, restChildren, transformChildren, options) {
154
204
  const remainingText = firstChild.children[0].value.replace(ALERT_PATTERN, "").trim();
155
205
  const remainingInlines = firstChild.children.slice(1);
156
206
  const contentNodes = [];
@@ -162,7 +212,7 @@ function buildPanelContent(firstChild, restChildren, transformChildren) {
162
212
  value: remainingText
163
213
  }] : [], ...remainingInlines]
164
214
  };
165
- if (paragraph.children.length > 0) contentNodes.push(transformParagraph(paragraph));
215
+ if (paragraph.children.length > 0) contentNodes.push(transformParagraph(paragraph, options));
166
216
  }
167
217
  contentNodes.push(...transformChildren(restChildren));
168
218
  if (contentNodes.length === 0) contentNodes.push({
@@ -171,7 +221,7 @@ function buildPanelContent(firstChild, restChildren, transformChildren) {
171
221
  });
172
222
  return contentNodes;
173
223
  }
174
- function tryTransformAlert(node, transformChildren) {
224
+ function tryTransformAlert(node, transformChildren, options) {
175
225
  const firstChild = node.children[0];
176
226
  if (firstChild?.type !== "paragraph") return null;
177
227
  const firstText = firstChild.children[0];
@@ -179,15 +229,15 @@ function tryTransformAlert(node, transformChildren) {
179
229
  const match = ALERT_PATTERN.exec(firstText.value);
180
230
  if (!match) return null;
181
231
  const panelType = ALERT_TO_PANEL[(match[1] ?? "NOTE").toUpperCase()] ?? "info";
182
- const content = buildPanelContent(firstChild, node.children.slice(1), transformChildren);
232
+ const content = buildPanelContent(firstChild, node.children.slice(1), transformChildren, options);
183
233
  return {
184
234
  type: "panel",
185
235
  attrs: { panelType },
186
236
  content
187
237
  };
188
238
  }
189
- function transformBlockquote(node, transformChildren) {
190
- const panel = tryTransformAlert(node, transformChildren);
239
+ function transformBlockquote(node, transformChildren, options) {
240
+ const panel = tryTransformAlert(node, transformChildren, options);
191
241
  if (panel) return panel;
192
242
  const content = transformChildren(node.children);
193
243
  return {
@@ -351,33 +401,33 @@ function generateId() {
351
401
  }
352
402
  //#endregion
353
403
  //#region src/md-to-adf/transform/lists.ts
354
- function transformList(node, transformBlock) {
355
- if (node.children.some((item) => item.checked !== null && item.checked !== void 0)) return transformTaskList(node);
356
- if (node.ordered) return transformOrderedList(node, transformBlock);
357
- return transformBulletList(node, transformBlock);
404
+ function transformList(node, transformBlock, options) {
405
+ if (node.children.some((item) => item.checked !== null && item.checked !== void 0)) return transformTaskList(node, options);
406
+ if (node.ordered) return transformOrderedList(node, transformBlock, options);
407
+ return transformBulletList(node, transformBlock, options);
358
408
  }
359
- function transformBulletList(node, transformBlock) {
409
+ function transformBulletList(node, transformBlock, options) {
360
410
  return {
361
411
  type: "bulletList",
362
- content: node.children.map((item) => transformListItem(item, transformBlock))
412
+ content: node.children.map((item) => transformListItem(item, transformBlock, options))
363
413
  };
364
414
  }
365
- function transformOrderedList(node, transformBlock) {
415
+ function transformOrderedList(node, transformBlock, options) {
366
416
  const result = {
367
417
  type: "orderedList",
368
- content: node.children.map((item) => transformListItem(item, transformBlock))
418
+ content: node.children.map((item) => transformListItem(item, transformBlock, options))
369
419
  };
370
420
  if (node.start !== null && node.start !== void 0 && node.start !== 1) result.attrs = { order: node.start };
371
421
  return result;
372
422
  }
373
- function transformListItem(node, transformBlock) {
423
+ function transformListItem(node, transformBlock, options) {
374
424
  const content = [];
375
425
  for (const child of node.children) if (child.type === "paragraph") content.push({
376
426
  type: "paragraph",
377
- content: phrasingToInlineNodes(child.children)
427
+ content: phrasingToInlineNodes(child.children, options)
378
428
  });
379
429
  else if (child.type === "list") {
380
- const nestedList = transformList(child, transformBlock);
430
+ const nestedList = transformList(child, transformBlock, options);
381
431
  if (nestedList.type === "taskList") {
382
432
  const para = {
383
433
  type: "paragraph",
@@ -414,11 +464,11 @@ function transformListItem(node, transformBlock) {
414
464
  content
415
465
  };
416
466
  }
417
- function transformTaskList(node) {
467
+ function transformTaskList(node, options) {
418
468
  const listId = generateId();
419
469
  const items = node.children.map((listItem) => {
420
470
  const state = listItem.checked === true ? "DONE" : "TODO";
421
- const inlineContent = listItem.children.filter((c) => c.type === "paragraph").flatMap((c) => phrasingToInlineNodes(c.children));
471
+ const inlineContent = listItem.children.filter((c) => c.type === "paragraph").flatMap((c) => phrasingToInlineNodes(c.children, options));
422
472
  const taskItem = {
423
473
  type: "taskItem",
424
474
  attrs: {
@@ -437,7 +487,7 @@ function transformTaskList(node) {
437
487
  }
438
488
  //#endregion
439
489
  //#region src/md-to-adf/transform/tables.ts
440
- function transformTable(node) {
490
+ function transformTable(node, options) {
441
491
  const rows = node.children;
442
492
  if (rows.length === 0) return {
443
493
  type: "table",
@@ -448,19 +498,19 @@ function transformTable(node) {
448
498
  const adfRows = [];
449
499
  if (headerRow) adfRows.push({
450
500
  type: "tableRow",
451
- content: headerRow.children.map((cell) => transformTableHeader(cell))
501
+ content: headerRow.children.map((cell) => transformTableHeader(cell, options))
452
502
  });
453
503
  for (const row of bodyRows) adfRows.push({
454
504
  type: "tableRow",
455
- content: row.children.map((cell) => transformTableCell(cell))
505
+ content: row.children.map((cell) => transformTableCell(cell, options))
456
506
  });
457
507
  return {
458
508
  type: "table",
459
509
  content: adfRows
460
510
  };
461
511
  }
462
- function transformTableHeader(cell) {
463
- const inlines = phrasingToInlineNodes(cell.children);
512
+ function transformTableHeader(cell, options) {
513
+ const inlines = phrasingToInlineNodes(cell.children, options);
464
514
  return {
465
515
  type: "tableHeader",
466
516
  attrs: {},
@@ -470,8 +520,8 @@ function transformTableHeader(cell) {
470
520
  }]
471
521
  };
472
522
  }
473
- function transformTableCell(cell) {
474
- const inlines = phrasingToInlineNodes(cell.children);
523
+ function transformTableCell(cell, options) {
524
+ const inlines = phrasingToInlineNodes(cell.children, options);
475
525
  return {
476
526
  type: "tableCell",
477
527
  attrs: {},
@@ -483,42 +533,42 @@ function transformTableCell(cell) {
483
533
  }
484
534
  //#endregion
485
535
  //#region src/md-to-adf/transform/index.ts
486
- function transformRoot(root) {
536
+ function transformRoot(root, options) {
487
537
  return {
488
538
  version: 1,
489
539
  type: "doc",
490
- content: transformNodes(root.children)
540
+ content: transformNodes(root.children, options)
491
541
  };
492
542
  }
493
- function transformNodes(nodes) {
543
+ function transformNodes(nodes, options) {
494
544
  const result = [];
495
545
  const detailsPairs = buildDetailsPairs(nodes);
496
546
  let i = 0;
497
547
  while (i < nodes.length) {
498
- const expand = tryTransformExpand(nodes, i, detailsPairs, transformBlock);
548
+ const expand = tryTransformExpand(nodes, i, detailsPairs, (node) => transformBlock(node, options));
499
549
  if (expand) {
500
550
  result.push(expand.node);
501
551
  i = expand.next;
502
552
  continue;
503
553
  }
504
- const transformed = transformBlock(nodes[i]);
554
+ const transformed = transformBlock(nodes[i], options);
505
555
  if (transformed) result.push(transformed);
506
556
  i++;
507
557
  }
508
558
  return result;
509
559
  }
510
- function transformBlock(node) {
560
+ function transformBlock(node, options) {
511
561
  switch (node.type) {
512
- case "heading": return transformHeading(node);
513
- case "paragraph": return transformParagraph(node);
562
+ case "heading": return transformHeading(node, options);
563
+ case "paragraph": return transformParagraph(node, options);
514
564
  case "code": return transformCode(node);
515
565
  case "thematicBreak": return transformThematicBreak(node);
516
566
  case "blockquote": return transformBlockquote(node, (children) => children.flatMap((c) => {
517
- const r = transformBlock(c);
567
+ const r = transformBlock(c, options);
518
568
  return r ? [r] : [];
519
- }));
520
- case "list": return transformList(node, transformBlock);
521
- case "table": return transformTable(node);
569
+ }), options);
570
+ case "list": return transformList(node, (blockNode) => transformBlock(blockNode, options), options);
571
+ case "table": return transformTable(node, options);
522
572
  case "html": return transformHtmlBlock(node);
523
573
  default: return null;
524
574
  }
@@ -545,8 +595,8 @@ function sanitizeText(text) {
545
595
  }
546
596
  //#endregion
547
597
  //#region src/md-to-adf/index.ts
548
- function mdToAdf(markdown) {
549
- return transformRoot(parseMarkdown(sanitizeText(markdown)));
598
+ function mdToAdf(markdown, options) {
599
+ return transformRoot(parseMarkdown(sanitizeText(markdown)), options);
550
600
  }
551
601
  //#endregion
552
602
  //#region src/utils/escape.ts
@@ -613,6 +663,7 @@ function serializeInline(node, options) {
613
663
  case "hardBreak": return "\\\n";
614
664
  case "mention":
615
665
  if (options?.mentions === false) return node.attrs.text ?? node.attrs.id;
666
+ if (typeof options?.mentions === "function") return options.mentions(node.attrs);
616
667
  return node.attrs.text ?? `@${node.attrs.id}`;
617
668
  case "emoji": return node.attrs.text ?? `:${node.attrs.shortName}:`;
618
669
  case "date": return node.attrs.timestamp;