markdown-to-slack-blocks 1.4.0 → 1.5.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 CHANGED
@@ -181,3 +181,31 @@ const text = blocksToPlainText(blocks);
181
181
  ```
182
182
 
183
183
  The function walks the rendered blocks and returns a joined string that keeps list markers, quotes, tables (as `cell | cell` rows), mentions, dates (using the provided fallback or ISO string), and basic formatting markers where possible. The output is best-effort and is intended for concise fallbacks rather than full fidelity rendering.
184
+
185
+ ### Markdown rendering
186
+
187
+ If you need a best-effort Markdown representation of an existing block array, use `blocksToMarkdown`:
188
+
189
+ ```typescript
190
+ import { blocksToMarkdown } from 'markdown-to-slack-blocks';
191
+
192
+ const markdown = blocksToMarkdown(blocks);
193
+ // -> "# Title\n\nParagraph with **bold** text" or similar
194
+ ```
195
+
196
+ If you want Slack IDs rendered back to visible mention names (which might be useful for LLMs), pass the inverse mention map:
197
+
198
+ ```typescript
199
+ const markdown = blocksToMarkdown(blocks, {
200
+ mentions: {
201
+ users: { 'U123456': 'username' },
202
+ channels: { 'C123456': 'general' },
203
+ userGroups: { 'S123456': 'engineers' },
204
+ teams: { 'T123456': 'myteam' }
205
+ }
206
+ });
207
+ ```
208
+
209
+ With these options, mentions like `<@U123456>` or `<#C123456>` are rendered as `@username` and `#general` where possible. The same ID format validation applies to these reverse mention maps.
210
+
211
+ The renderer converts `rich_text`, section `mrkdwn`, code blocks, quotes, lists, tables, images, and Slack entities back into normalized Markdown. Because Slack blocks do not preserve every detail of the original source, the output is canonicalized rather than byte-for-byte identical to the input, but it round-trips cleanly for this library's generated blocks.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { MarkdownToBlocksOptions } from "./types";
2
2
  export * from "./types";
3
- export { blocksToPlainText, splitBlocks, splitBlocksWithText, SplitBlocksOptions, SplitBlocksResult, } from "./splitter";
3
+ export { blocksToMarkdown, blocksToPlainText, splitBlocks, splitBlocksWithText, SplitBlocksOptions, SplitBlocksResult, } from "./splitter";
4
4
  export declare function markdownToBlocks(markdown: string, options?: MarkdownToBlocksOptions): import("./types").Block[];
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAIvD,cAAc,SAAS,CAAC;AACxB,OAAO,EACN,iBAAiB,EACjB,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GACjB,MAAM,YAAY,CAAC;AAEpB,wBAAgB,gBAAgB,CAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,uBAAuB,6BAIjC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAIvD,cAAc,SAAS,CAAC;AACxB,OAAO,EACN,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GACjB,MAAM,YAAY,CAAC;AAEpB,wBAAgB,gBAAgB,CAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,uBAAuB,6BAIjC"}
package/dist/index.js CHANGED
@@ -14,13 +14,14 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.splitBlocksWithText = exports.splitBlocks = exports.blocksToPlainText = void 0;
17
+ exports.splitBlocksWithText = exports.splitBlocks = exports.blocksToPlainText = exports.blocksToMarkdown = void 0;
18
18
  exports.markdownToBlocks = markdownToBlocks;
19
19
  const parser_1 = require("./parser");
20
20
  const validator_1 = require("./validator");
21
21
  // Re-export types for consumers
22
22
  __exportStar(require("./types"), exports);
23
23
  var splitter_1 = require("./splitter");
24
+ Object.defineProperty(exports, "blocksToMarkdown", { enumerable: true, get: function () { return splitter_1.blocksToMarkdown; } });
24
25
  Object.defineProperty(exports, "blocksToPlainText", { enumerable: true, get: function () { return splitter_1.blocksToPlainText; } });
25
26
  Object.defineProperty(exports, "splitBlocks", { enumerable: true, get: function () { return splitter_1.splitBlocks; } });
26
27
  Object.defineProperty(exports, "splitBlocksWithText", { enumerable: true, get: function () { return splitter_1.splitBlocksWithText; } });
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACX,KAAK,EAKL,uBAAuB,EAGvB,MAAM,SAAS,CAAC;AAmFjB,wBAAgB,aAAa,CAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,uBAA4B,GACnC,KAAK,EAAE,CAiNT"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACX,KAAK,EAKL,uBAAuB,EAGvB,MAAM,SAAS,CAAC;AAyGjB,wBAAgB,aAAa,CAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,uBAA4B,GACnC,KAAK,EAAE,CAiNT"}
package/dist/parser.js CHANGED
@@ -24,12 +24,25 @@ function isListNode(node) {
24
24
  /**
25
25
  * Preprocess markdown to "unwrap" lists that are entirely wrapped in formatting.
26
26
  * For example: "**1. Item**" becomes "1. **Item**"
27
+ * Also handles heading-prefixed format-wrapped lists:
28
+ * "### **1. Item**" becomes "###\n1. **Item**"
27
29
  * This allows the markdown parser to recognize them as list items.
28
30
  */
29
31
  function preprocessMarkdown(markdown) {
30
32
  const lines = markdown.split("\n");
31
- const processedLines = lines.map((line) => {
33
+ const processedLines = [];
34
+ for (const line of lines) {
32
35
  const trimmed = line.trim();
36
+ // Match heading-prefixed format-wrapped lists
37
+ // e.g. "### **1. Item**" or "## *- Item*"
38
+ const headingFormatWrappedListRegex = /^(#{1,6})\s+(\*\*|\*|_|~)(\d+\.|\*|-|\+)(\s+)(.*?)\2$/;
39
+ const headingMatch = trimmed.match(headingFormatWrappedListRegex);
40
+ if (headingMatch) {
41
+ const [_, _heading, format, marker, spaces, content] = headingMatch;
42
+ // Split into an empty line (to end the heading context) and a format-unwrapped list line
43
+ processedLines.push(`${marker}${spaces}${format}${content}${format}`);
44
+ continue;
45
+ }
33
46
  // Match patterns like **1. item**, *1. item*, ~1. item~, **- item**, etc.
34
47
  // Group 1: opening format (** or * or _ or ~)
35
48
  // Group 2: list marker (1. or - or * or +)
@@ -42,10 +55,11 @@ function preprocessMarkdown(markdown) {
42
55
  const [_, format, marker, spaces, content] = match;
43
56
  // Preserve leading whitespace of the original line
44
57
  const leadingWhitespace = line.match(/^\s*/)?.[0] || "";
45
- return `${leadingWhitespace}${marker}${spaces}${format}${content}${format}`;
58
+ processedLines.push(`${leadingWhitespace}${marker}${spaces}${format}${content}${format}`);
59
+ continue;
46
60
  }
47
- return line;
48
- });
61
+ processedLines.push(line);
62
+ }
49
63
  return processedLines.join("\n");
50
64
  }
51
65
  function parseMarkdown(markdown, options = {}) {
@@ -382,9 +396,14 @@ function processTextNode(text, style, options) {
382
396
  elements.push(withStyle({ type: "channel", channel_id: channelId }));
383
397
  }
384
398
  else if (match[8]) {
385
- // Team: <!subteam^ID>
386
- const teamId = match[8];
387
- elements.push(withStyle({ type: "team", team_id: teamId }));
399
+ // User Group / Team: <!subteam^ID>
400
+ const subteamId = match[8];
401
+ if (subteamId.startsWith("S")) {
402
+ elements.push(withStyle({ type: "usergroup", usergroup_id: subteamId }));
403
+ }
404
+ else {
405
+ elements.push(withStyle({ type: "team", team_id: subteamId }));
406
+ }
388
407
  }
389
408
  else if (match[10]) {
390
409
  // Date: <!date^...|...>
@@ -1,4 +1,4 @@
1
- import type { Block } from "./types";
1
+ import type { Block, BlocksToMarkdownOptions } from "./types";
2
2
  export interface SplitBlocksOptions {
3
3
  /** Maximum number of blocks per message. Default: 40 */
4
4
  maxBlocks?: number;
@@ -22,6 +22,10 @@ export declare function splitBlocks(blocks: Block[], options?: SplitBlocksOption
22
22
  * Splits blocks and also returns a plain-text fallback for each batch, suitable for postMessage `text`.
23
23
  */
24
24
  export declare function splitBlocksWithText(blocks: Block[], options?: SplitBlocksOptions): SplitBlocksResult[];
25
+ /**
26
+ * Renders a block array back into best-effort Markdown.
27
+ */
28
+ export declare function blocksToMarkdown(blocks: Block[], options?: BlocksToMarkdownOptions): string;
25
29
  /**
26
30
  * Generates a lightweight plain-text fallback from a block batch.
27
31
  */
@@ -1 +1 @@
1
- {"version":3,"file":"splitter.d.ts","sourceRoot":"","sources":["../src/splitter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,KAAK,EASL,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,kBAAkB;IAClC,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,KAAK,EAAE,CAAC;CAChB;AAMD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAC1B,MAAM,EAAE,KAAK,EAAE,EACf,OAAO,CAAC,EAAE,kBAAkB,GAC1B,KAAK,EAAE,EAAE,CAqFX;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAClC,MAAM,EAAE,KAAK,EAAE,EACf,OAAO,CAAC,EAAE,kBAAkB,GAC1B,iBAAiB,EAAE,CAMrB;AAmSD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CA4GzD"}
1
+ {"version":3,"file":"splitter.d.ts","sourceRoot":"","sources":["../src/splitter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,KAAK,EACL,uBAAuB,EAcvB,MAAM,SAAS,CAAC;AAGjB,MAAM,WAAW,kBAAkB;IAClC,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,KAAK,EAAE,CAAC;CAChB;AAMD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAC1B,MAAM,EAAE,KAAK,EAAE,EACf,OAAO,CAAC,EAAE,kBAAkB,GAC1B,KAAK,EAAE,EAAE,CAqFX;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAClC,MAAM,EAAE,KAAK,EAAE,EACf,OAAO,CAAC,EAAE,kBAAkB,GAC1B,iBAAiB,EAAE,CAMrB;AAmSD;;GAEG;AACH,wBAAgB,gBAAgB,CAC/B,MAAM,EAAE,KAAK,EAAE,EACf,OAAO,CAAC,EAAE,uBAAuB,GAC/B,MAAM,CAWR;AAkjBD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CA4GzD"}
package/dist/splitter.js CHANGED
@@ -2,7 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.splitBlocks = splitBlocks;
4
4
  exports.splitBlocksWithText = splitBlocksWithText;
5
+ exports.blocksToMarkdown = blocksToMarkdown;
5
6
  exports.blocksToPlainText = blocksToPlainText;
7
+ const validator_1 = require("./validator");
6
8
  const DEFAULT_MAX_BLOCKS = 40;
7
9
  const DEFAULT_MAX_CHARACTERS = 12000;
8
10
  const DEFAULT_MAX_TEXT_SECTION_CHARACTERS = 3000;
@@ -332,6 +334,400 @@ function chunkString(str, limit) {
332
334
  }
333
335
  return chunks;
334
336
  }
337
+ /**
338
+ * Renders a block array back into best-effort Markdown.
339
+ */
340
+ function blocksToMarkdown(blocks, options) {
341
+ (0, validator_1.validateBlocksToMarkdownOptions)(options);
342
+ const parts = [];
343
+ for (const block of blocks) {
344
+ const rendered = renderBlockAsMarkdown(block, options).trim();
345
+ if (rendered)
346
+ parts.push(rendered);
347
+ }
348
+ return parts.join("\n\n");
349
+ }
350
+ function renderBlockAsMarkdown(block, options) {
351
+ switch (block.type) {
352
+ case "section":
353
+ return renderSectionBlockAsMarkdown(block, options);
354
+ case "header":
355
+ return `# ${block.text.text}`;
356
+ case "context":
357
+ return block.elements
358
+ .map((element) => element.type === "image"
359
+ ? renderImageElementAsMarkdown(element)
360
+ : renderTextObjectAsMarkdown(element, options))
361
+ .filter(Boolean)
362
+ .join(" ");
363
+ case "rich_text":
364
+ return renderRichTextBlockAsMarkdown(block, options);
365
+ case "divider":
366
+ return "---";
367
+ case "image":
368
+ return renderImageBlockAsMarkdown(block);
369
+ case "table":
370
+ return renderTableBlockAsMarkdown(block, options);
371
+ default:
372
+ return "";
373
+ }
374
+ }
375
+ function renderSectionBlockAsMarkdown(block, options) {
376
+ const parts = [];
377
+ const text = renderTextObjectAsMarkdown(block.text, options);
378
+ if (text)
379
+ parts.push(text);
380
+ if (block.fields?.length) {
381
+ const renderedFields = block.fields
382
+ .map((field) => renderTextObjectAsMarkdown(field, options))
383
+ .filter(Boolean)
384
+ .join("\n");
385
+ if (renderedFields)
386
+ parts.push(renderedFields);
387
+ }
388
+ if (isImageElement(block.accessory)) {
389
+ parts.push(renderImageElementAsMarkdown(block.accessory));
390
+ }
391
+ return parts.join("\n\n");
392
+ }
393
+ function renderTextObjectAsMarkdown(text, options) {
394
+ if (!text)
395
+ return "";
396
+ return text.type === "mrkdwn"
397
+ ? convertMrkdwnToMarkdown(text.text, options)
398
+ : text.text;
399
+ }
400
+ function renderRichTextBlockAsMarkdown(block, options) {
401
+ const heading = renderRichTextHeadingAsMarkdown(block);
402
+ if (heading)
403
+ return heading;
404
+ const rendered = block.elements
405
+ .map((element) => renderRichTextElementAsMarkdown(element, options))
406
+ .filter((part) => part.markdown.length > 0);
407
+ if (rendered.length === 0)
408
+ return "";
409
+ let markdown = rendered[0].markdown;
410
+ for (let index = 1; index < rendered.length; index++) {
411
+ const previous = rendered[index - 1];
412
+ const current = rendered[index];
413
+ const separator = previous.kind === "list" && current.kind === "list" ? "\n" : "\n\n";
414
+ markdown += separator + current.markdown;
415
+ }
416
+ return markdown;
417
+ }
418
+ function renderRichTextHeadingAsMarkdown(block) {
419
+ if (block.elements.length !== 1)
420
+ return "";
421
+ const [element] = block.elements;
422
+ if (element.type !== "rich_text_section" || element.elements.length !== 1) {
423
+ return "";
424
+ }
425
+ const [textElement] = element.elements;
426
+ if (textElement.type !== "text" ||
427
+ !textElement.style?.bold ||
428
+ textElement.style.italic ||
429
+ textElement.style.strike ||
430
+ textElement.style.code) {
431
+ return "";
432
+ }
433
+ return `### ${textElement.text}`;
434
+ }
435
+ function renderRichTextElementAsMarkdown(element, options) {
436
+ switch (element.type) {
437
+ case "rich_text_section":
438
+ return {
439
+ kind: "section",
440
+ markdown: renderRichTextSectionAsMarkdown(element, options),
441
+ };
442
+ case "rich_text_list":
443
+ return {
444
+ kind: "list",
445
+ markdown: renderRichTextListAsMarkdown(element, options),
446
+ };
447
+ case "rich_text_preformatted":
448
+ return {
449
+ kind: "preformatted",
450
+ markdown: renderRichTextPreformattedAsMarkdown(element, options),
451
+ };
452
+ case "rich_text_quote":
453
+ return {
454
+ kind: "quote",
455
+ markdown: renderRichTextQuoteAsMarkdown(element, options),
456
+ };
457
+ default:
458
+ return {
459
+ kind: "section",
460
+ markdown: "",
461
+ };
462
+ }
463
+ }
464
+ function renderRichTextSectionAsMarkdown(section, options) {
465
+ return section.elements
466
+ .map((element) => renderRichTextSectionElementAsMarkdown(element, options))
467
+ .join("");
468
+ }
469
+ function renderRichTextListAsMarkdown(list, options) {
470
+ const indentUnit = list.style === "ordered" ? " " : " ";
471
+ const indent = indentUnit.repeat(list.indent ?? 0);
472
+ const start = list.offset ?? 1;
473
+ return list.elements
474
+ .map((item, index) => {
475
+ const marker = list.style === "ordered" ? `${start + index}. ` : "- ";
476
+ const content = renderRichTextSectionAsMarkdown(item, options);
477
+ return indentMultilineMarkdown(`${indent}${marker}`, content);
478
+ })
479
+ .join("\n");
480
+ }
481
+ function renderRichTextPreformattedAsMarkdown(element, options) {
482
+ const text = element.elements
483
+ .map((item) => renderRichTextSectionElementAsMarkdown(item, options))
484
+ .join("");
485
+ return wrapFencedCodeBlock(text);
486
+ }
487
+ function renderRichTextQuoteAsMarkdown(element, options) {
488
+ return renderRichTextSectionAsMarkdown({
489
+ type: "rich_text_section",
490
+ elements: element.elements,
491
+ }, options)
492
+ .split("\n")
493
+ .map((line) => `> ${line}`)
494
+ .join("\n");
495
+ }
496
+ function renderRichTextSectionElementAsMarkdown(element, options) {
497
+ switch (element.type) {
498
+ case "text":
499
+ return applyMarkdownStyle(element.text, element.style);
500
+ case "link": {
501
+ const content = element.text && element.text.length > 0
502
+ ? `[${element.text}](<${element.url}>)`
503
+ : element.url;
504
+ return applyMarkdownStyle(content, element.style);
505
+ }
506
+ case "emoji":
507
+ return applyMarkdownStyle(`:${element.name}:`, element.style);
508
+ case "date": {
509
+ const fallback = element.fallback ?? new Date(element.timestamp * 1000).toISOString();
510
+ const content = `<!date^${element.timestamp}^${element.format}|${fallback}>`;
511
+ return applyMarkdownStyle(content, element.style);
512
+ }
513
+ case "user":
514
+ return applyMarkdownStyle(renderUserMentionAsMarkdown(element.user_id, options), element.style);
515
+ case "usergroup":
516
+ return applyMarkdownStyle(renderUserGroupMentionAsMarkdown(element.usergroup_id, options), element.style);
517
+ case "team":
518
+ return applyMarkdownStyle(renderTeamMentionAsMarkdown(element.team_id, options), element.style);
519
+ case "channel":
520
+ return applyMarkdownStyle(renderChannelMentionAsMarkdown(element.channel_id, options), element.style);
521
+ case "broadcast":
522
+ return applyMarkdownStyle(`<!${element.range}>`, element.style);
523
+ case "color":
524
+ return applyMarkdownStyle(element.value, element.style);
525
+ default:
526
+ return "";
527
+ }
528
+ }
529
+ function renderImageBlockAsMarkdown(block) {
530
+ const title = block.title?.text
531
+ ? ` "${block.title.text.replace(/"/g, '\\"')}"`
532
+ : "";
533
+ return `![${block.alt_text}](<${block.image_url}>${title})`;
534
+ }
535
+ function renderImageElementAsMarkdown(element) {
536
+ return `![${element.alt_text}](<${element.image_url}>)`;
537
+ }
538
+ function renderTableBlockAsMarkdown(block, options) {
539
+ if (block.rows.length === 0 || block.rows[0].length === 0) {
540
+ return "";
541
+ }
542
+ const rows = block.rows.map((row) => `| ${row.map((cell) => renderTableCellAsMarkdown(cell, options)).join(" | ")} |`);
543
+ const separator = `| ${block.rows[0].map(() => "---").join(" | ")} |`;
544
+ return [rows[0], separator, ...rows.slice(1)].join("\n");
545
+ }
546
+ function renderTableCellAsMarkdown(cell, options) {
547
+ return renderRichTextBlockAsMarkdown(cell, options)
548
+ .replace(/\|/g, "\\|")
549
+ .replace(/\n/g, "\\n");
550
+ }
551
+ function applyMarkdownStyle(text, style) {
552
+ if (!style)
553
+ return text;
554
+ let result = text;
555
+ if (style.code) {
556
+ result = wrapInlineCode(result);
557
+ }
558
+ if (style.bold) {
559
+ result = `**${result}**`;
560
+ }
561
+ if (style.italic) {
562
+ result = `*${result}*`;
563
+ }
564
+ if (style.strike) {
565
+ result = `~${result}~`;
566
+ }
567
+ return result;
568
+ }
569
+ function wrapInlineCode(text) {
570
+ const fence = getBacktickFence(text, 1);
571
+ return `${fence}${text}${fence}`;
572
+ }
573
+ function wrapFencedCodeBlock(text) {
574
+ const fence = getBacktickFence(text, 3);
575
+ return `${fence}\n${text}\n${fence}`;
576
+ }
577
+ function getBacktickFence(text, minimumLength) {
578
+ const runs = text.match(/`+/g);
579
+ const longestRun = runs?.reduce((max, run) => Math.max(max, run.length), 0) ?? 0;
580
+ return "`".repeat(Math.max(minimumLength, longestRun + 1));
581
+ }
582
+ function indentMultilineMarkdown(prefix, text) {
583
+ const lines = text.split("\n");
584
+ if (lines.length === 0)
585
+ return prefix.trimEnd();
586
+ const continuation = " ".repeat(prefix.length);
587
+ return lines
588
+ .map((line, index) => index === 0 ? `${prefix}${line}` : `${continuation}${line}`)
589
+ .join("\n");
590
+ }
591
+ function convertMrkdwnToMarkdown(text, options) {
592
+ let result = "";
593
+ for (let index = 0; index < text.length; index++) {
594
+ const character = text[index];
595
+ if (character === "`") {
596
+ const closingIndex = text.indexOf("`", index + 1);
597
+ if (closingIndex !== -1) {
598
+ result += wrapInlineCode(text.slice(index + 1, closingIndex));
599
+ index = closingIndex;
600
+ continue;
601
+ }
602
+ }
603
+ if (character === "<") {
604
+ const closingIndex = text.indexOf(">", index + 1);
605
+ if (closingIndex !== -1) {
606
+ result += convertAngleBracketTokenToMarkdown(text.slice(index, closingIndex + 1), options);
607
+ index = closingIndex;
608
+ continue;
609
+ }
610
+ }
611
+ if (isMrkdwnStyleMarker(character) && isValidMrkdwnStyleOpen(text, index)) {
612
+ const closingIndex = findClosingMrkdwnStyleMarker(text, index);
613
+ if (closingIndex !== -1) {
614
+ const inner = convertMrkdwnToMarkdown(text.slice(index + 1, closingIndex), options);
615
+ result += wrapConvertedMrkdwnStyle(character, inner);
616
+ index = closingIndex;
617
+ continue;
618
+ }
619
+ }
620
+ result += character;
621
+ }
622
+ return result;
623
+ }
624
+ function convertAngleBracketTokenToMarkdown(token, options) {
625
+ const userMatch = token.match(/^<@([\w.-]+)>$/);
626
+ if (userMatch) {
627
+ return renderUserMentionAsMarkdown(userMatch[1], options);
628
+ }
629
+ const channelMatch = token.match(/^<#([\w.-]+)>$/);
630
+ if (channelMatch) {
631
+ return renderChannelMentionAsMarkdown(channelMatch[1], options);
632
+ }
633
+ const subteamMatch = token.match(/^<!subteam\^([\w.-]+)>$/);
634
+ if (subteamMatch) {
635
+ const subteamId = subteamMatch[1];
636
+ return subteamId.startsWith("S")
637
+ ? renderUserGroupMentionAsMarkdown(subteamId, options)
638
+ : renderTeamMentionAsMarkdown(subteamId, options);
639
+ }
640
+ if (token.startsWith("<!")) {
641
+ return token;
642
+ }
643
+ const formattedLinkMatch = token.match(/^<([^|>]+)\|(.+)>$/);
644
+ if (formattedLinkMatch) {
645
+ const [, url, label] = formattedLinkMatch;
646
+ return `[${convertMrkdwnToMarkdown(label, options)}](<${url}>)`;
647
+ }
648
+ const autoLinkMatch = token.match(/^<([^>]+)>$/);
649
+ if (autoLinkMatch) {
650
+ return autoLinkMatch[1];
651
+ }
652
+ return token;
653
+ }
654
+ function isMrkdwnStyleMarker(character) {
655
+ return character === "*" || character === "_" || character === "~";
656
+ }
657
+ function isValidMrkdwnStyleOpen(text, index) {
658
+ const previous = text[index - 1];
659
+ const next = text[index + 1];
660
+ return isMrkdwnBoundary(previous) && !isWhitespace(next);
661
+ }
662
+ function isValidMrkdwnStyleClose(text, index) {
663
+ const previous = text[index - 1];
664
+ const next = text[index + 1];
665
+ return !isWhitespace(previous) && isMrkdwnBoundary(next);
666
+ }
667
+ function findClosingMrkdwnStyleMarker(text, openingIndex) {
668
+ const marker = text[openingIndex];
669
+ for (let index = openingIndex + 1; index < text.length; index++) {
670
+ const character = text[index];
671
+ if (character === "`") {
672
+ const closingIndex = text.indexOf("`", index + 1);
673
+ if (closingIndex === -1)
674
+ return -1;
675
+ index = closingIndex;
676
+ continue;
677
+ }
678
+ if (character === "<") {
679
+ const closingIndex = text.indexOf(">", index + 1);
680
+ if (closingIndex === -1)
681
+ return -1;
682
+ index = closingIndex;
683
+ continue;
684
+ }
685
+ if (character === marker && isValidMrkdwnStyleClose(text, index)) {
686
+ return index;
687
+ }
688
+ }
689
+ return -1;
690
+ }
691
+ function wrapConvertedMrkdwnStyle(marker, text) {
692
+ if (marker === "*")
693
+ return `**${text}**`;
694
+ if (marker === "_")
695
+ return `*${text}*`;
696
+ return `~${text}~`;
697
+ }
698
+ function isMrkdwnBoundary(character) {
699
+ if (character === undefined)
700
+ return true;
701
+ return /[\s.,!?;:()[\]{}"'<>/-]/.test(character);
702
+ }
703
+ function isWhitespace(character) {
704
+ return character !== undefined && /\s/.test(character);
705
+ }
706
+ function renderUserMentionAsMarkdown(userId, options) {
707
+ return renderNamedMention(options?.mentions?.users?.[userId] ??
708
+ options?.mentions?.userGroups?.[userId] ??
709
+ options?.mentions?.teams?.[userId], `<@${userId}>`);
710
+ }
711
+ function renderChannelMentionAsMarkdown(channelId, options) {
712
+ return renderNamedMention(options?.mentions?.channels?.[channelId], `<#${channelId}>`, "#");
713
+ }
714
+ function renderUserGroupMentionAsMarkdown(userGroupId, options) {
715
+ return renderNamedMention(options?.mentions?.userGroups?.[userGroupId], `<!subteam^${userGroupId}>`);
716
+ }
717
+ function renderTeamMentionAsMarkdown(teamId, options) {
718
+ return renderNamedMention(options?.mentions?.teams?.[teamId], `<!subteam^${teamId}>`);
719
+ }
720
+ function renderNamedMention(name, fallback, prefix = "@") {
721
+ return name ? `${prefix}${name}` : fallback;
722
+ }
723
+ function isImageElement(element) {
724
+ if (!element || typeof element !== "object")
725
+ return false;
726
+ const accessory = element;
727
+ return (accessory.type === "image" &&
728
+ typeof accessory.image_url === "string" &&
729
+ typeof accessory.alt_text === "string");
730
+ }
335
731
  /**
336
732
  * Generates a lightweight plain-text fallback from a block batch.
337
733
  */
package/dist/types.d.ts CHANGED
@@ -156,4 +156,12 @@ export interface MarkdownToBlocksOptions {
156
156
  detectColors?: boolean;
157
157
  preferSectionBlocks?: boolean;
158
158
  }
159
+ export interface BlocksToMarkdownOptions {
160
+ mentions?: {
161
+ users?: Record<string, string>;
162
+ channels?: Record<string, string>;
163
+ userGroups?: Record<string, string>;
164
+ teams?: Record<string, string>;
165
+ };
166
+ }
159
167
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
- import type { MarkdownToBlocksOptions } from "./types";
1
+ import type { BlocksToMarkdownOptions, MarkdownToBlocksOptions } from "./types";
2
2
  export declare function validateOptions(options?: MarkdownToBlocksOptions): void;
3
+ export declare function validateBlocksToMarkdownOptions(options?: BlocksToMarkdownOptions): void;
3
4
  //# sourceMappingURL=validator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAEvD,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,uBAAuB,GAAG,IAAI,CA8CvE"}
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAEhF,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,uBAAuB,GAAG,IAAI,CA8CvE;AAED,wBAAgB,+BAA+B,CAC9C,OAAO,CAAC,EAAE,uBAAuB,GAC/B,IAAI,CA0CN"}
package/dist/validator.js CHANGED
@@ -1,37 +1,54 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validateOptions = validateOptions;
4
+ exports.validateBlocksToMarkdownOptions = validateBlocksToMarkdownOptions;
4
5
  function validateOptions(options) {
5
6
  if (!options?.mentions) {
6
7
  return;
7
8
  }
8
9
  const { users, channels, userGroups, teams } = options.mentions;
9
10
  if (users) {
10
- for (const [name, id] of Object.entries(users)) {
11
- if (!/^[UW][A-Z0-9]+$/.test(id)) {
12
- throw new Error(`Invalid User ID for '${name}': '${id}'. Must start with U or W and contain only alphanumeric characters.`);
13
- }
14
- }
11
+ validateIdValues(users, /^[UW][A-Z0-9]+$/, "User", (name, id) => `Invalid User ID for '${name}': '${id}'. Must start with U or W and contain only alphanumeric characters.`);
15
12
  }
16
13
  if (channels) {
17
- for (const [name, id] of Object.entries(channels)) {
18
- if (!/^C[A-Z0-9]+$/.test(id)) {
19
- throw new Error(`Invalid Channel ID for '${name}': '${id}'. Must start with C and contain only alphanumeric characters.`);
20
- }
21
- }
14
+ validateIdValues(channels, /^C[A-Z0-9]+$/, "Channel", (name, id) => `Invalid Channel ID for '${name}': '${id}'. Must start with C and contain only alphanumeric characters.`);
22
15
  }
23
16
  if (userGroups) {
24
- for (const [name, id] of Object.entries(userGroups)) {
25
- if (!/^S[A-Z0-9]+$/.test(id)) {
26
- throw new Error(`Invalid User Group ID for '${name}': '${id}'. Must start with S and contain only alphanumeric characters.`);
27
- }
28
- }
17
+ validateIdValues(userGroups, /^S[A-Z0-9]+$/, "User Group", (name, id) => `Invalid User Group ID for '${name}': '${id}'. Must start with S and contain only alphanumeric characters.`);
29
18
  }
30
19
  if (teams) {
31
- for (const [name, id] of Object.entries(teams)) {
32
- if (!/^T[A-Z0-9]+$/.test(id)) {
33
- throw new Error(`Invalid Team ID for '${name}': '${id}'. Must start with T and contain only alphanumeric characters.`);
34
- }
20
+ validateIdValues(teams, /^T[A-Z0-9]+$/, "Team", (name, id) => `Invalid Team ID for '${name}': '${id}'. Must start with T and contain only alphanumeric characters.`);
21
+ }
22
+ }
23
+ function validateBlocksToMarkdownOptions(options) {
24
+ if (!options?.mentions) {
25
+ return;
26
+ }
27
+ const { users, channels, userGroups, teams } = options.mentions;
28
+ if (users) {
29
+ validateIdKeys(users, /^[UW][A-Z0-9]+$/, (id) => `Invalid User ID key '${id}'. Must start with U or W and contain only alphanumeric characters.`);
30
+ }
31
+ if (channels) {
32
+ validateIdKeys(channels, /^C[A-Z0-9]+$/, (id) => `Invalid Channel ID key '${id}'. Must start with C and contain only alphanumeric characters.`);
33
+ }
34
+ if (userGroups) {
35
+ validateIdKeys(userGroups, /^S[A-Z0-9]+$/, (id) => `Invalid User Group ID key '${id}'. Must start with S and contain only alphanumeric characters.`);
36
+ }
37
+ if (teams) {
38
+ validateIdKeys(teams, /^T[A-Z0-9]+$/, (id) => `Invalid Team ID key '${id}'. Must start with T and contain only alphanumeric characters.`);
39
+ }
40
+ }
41
+ function validateIdValues(entries, pattern, _kind, buildMessage) {
42
+ for (const [name, id] of Object.entries(entries)) {
43
+ if (!pattern.test(id)) {
44
+ throw new Error(buildMessage(name, id));
45
+ }
46
+ }
47
+ }
48
+ function validateIdKeys(entries, pattern, buildMessage) {
49
+ for (const id of Object.keys(entries)) {
50
+ if (!pattern.test(id)) {
51
+ throw new Error(buildMessage(id));
35
52
  }
36
53
  }
37
54
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "markdown-to-slack-blocks",
3
- "version": "1.4.0",
4
- "description": "Convert Markdown to Slack Block Kit JSON format",
3
+ "version": "1.5.0",
4
+ "description": "Convert Markdown to Slack Block Kit JSON format and vice versa.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "exports": {
@@ -48,7 +48,7 @@
48
48
  "node": ">=18.0.0"
49
49
  },
50
50
  "dependencies": {
51
- "mdast-util-from-markdown": "2.0.2",
51
+ "mdast-util-from-markdown": "2.0.3",
52
52
  "mdast-util-gfm": "3.1.0",
53
53
  "mdast-util-to-string": "4.0.0",
54
54
  "micromark-extension-gfm": "3.0.0"
@@ -58,6 +58,6 @@
58
58
  "@types/node": "24.10.1",
59
59
  "eslint": "9.39.1",
60
60
  "typescript": "5.9.3",
61
- "vitest": "4.0.15"
61
+ "vitest": "4.1.2"
62
62
  }
63
- }
63
+ }