markdown-to-slack-blocks 1.2.2 → 1.3.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.
package/README.md CHANGED
@@ -141,22 +141,30 @@ All IDs must be alphanumeric.
141
141
 
142
142
  ### Handling Large Messages
143
143
 
144
- Slack limits messages to **~45 blocks** and **~12KB** of JSON. Use `splitBlocks` to split large outputs into several messages:
144
+ Slack limits messages to **~45 blocks** and **~12KB** of JSON. Use `splitBlocks` to split large outputs into several messages, or `splitBlocksWithText` if you also need a plain-text fallback for `postMessage`:
145
145
 
146
146
  ```typescript
147
- import { markdownToBlocks, splitBlocks } from 'markdown-to-slack-blocks';
147
+ import { markdownToBlocks, splitBlocks, splitBlocksWithText } from 'markdown-to-slack-blocks';
148
148
 
149
149
  const blocks = markdownToBlocks(veryLongMarkdown);
150
- const batches = splitBlocks(blocks);
151
150
 
151
+ // Blocks-only batches
152
+ const batches = splitBlocks(blocks);
152
153
  for (const batch of batches) {
153
154
  await slack.postMessage({ channel, blocks: batch });
154
155
  }
156
+
157
+ // Batches with text fallback
158
+ const batchesWithText = splitBlocksWithText(blocks);
159
+ for (const batch of batchesWithText) {
160
+ await slack.postMessage({ channel, text: batch.text, blocks: batch.blocks });
161
+ }
155
162
  ```
156
163
 
157
164
  **Options:**
158
165
  ```typescript
159
166
  splitBlocks(blocks, { maxBlocks: 40, maxCharacters: 12000 });
167
+ splitBlocksWithText(blocks, { maxBlocks: 40, maxCharacters: 12000 });
160
168
  ```
161
169
 
162
- The function splits at natural boundaries: between blocks first, then within `rich_text` elements, and finally within large code blocks by line.
170
+ Splitting happens at natural boundaries: between blocks first, then within `rich_text` elements, and finally within large code blocks by line. `splitBlocksWithText` additionally generates a concise plaintext summary per batch (headers, sections, rich text, tables, etc.) suitable for Slack's `text` field.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { MarkdownToBlocksOptions } from './types';
2
2
  export * from './types';
3
- export { splitBlocks, SplitBlocksOptions } from './splitter';
3
+ export { 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,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAIlD,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE7D,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB,6BAGnF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAIlD,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAErG,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB,6BAGnF"}
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ 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.splitBlocks = void 0;
17
+ exports.splitBlocksWithText = exports.splitBlocks = void 0;
18
18
  exports.markdownToBlocks = markdownToBlocks;
19
19
  const parser_1 = require("./parser");
20
20
  const validator_1 = require("./validator");
@@ -22,6 +22,7 @@ const validator_1 = require("./validator");
22
22
  __exportStar(require("./types"), exports);
23
23
  var splitter_1 = require("./splitter");
24
24
  Object.defineProperty(exports, "splitBlocks", { enumerable: true, get: function () { return splitter_1.splitBlocks; } });
25
+ Object.defineProperty(exports, "splitBlocksWithText", { enumerable: true, get: function () { return splitter_1.splitBlocksWithText; } });
25
26
  function markdownToBlocks(markdown, options) {
26
27
  (0, validator_1.validateOptions)(options);
27
28
  return (0, parser_1.parseMarkdown)(markdown, options);
package/dist/parser.js CHANGED
@@ -254,6 +254,14 @@ function flattenStyles(children, style, options) {
254
254
  });
255
255
  }
256
256
  function processTextNode(text, style, options) {
257
+ // Do not parse mentions/colors inside code-styled text; treat as literal.
258
+ if (style.code) {
259
+ const literal = { type: 'text', text };
260
+ if (Object.keys(style).length > 0) {
261
+ literal.style = style;
262
+ }
263
+ return [literal];
264
+ }
257
265
  // Regex for:
258
266
  // 1. Broadcast: <!here> | <!channel> | <!everyone>
259
267
  // 2. Mention: <@U...>
@@ -5,6 +5,10 @@ export interface SplitBlocksOptions {
5
5
  /** Maximum JSON character count per message. Default: 12000 */
6
6
  maxCharacters?: number;
7
7
  }
8
+ export interface SplitBlocksResult {
9
+ text: string;
10
+ blocks: Block[];
11
+ }
8
12
  /**
9
13
  * Splits an array of blocks into multiple arrays that fit within Slack's limits.
10
14
  * Attempts to split at natural boundaries (between top-level blocks, rich_text elements, etc.)
@@ -14,4 +18,8 @@ export interface SplitBlocksOptions {
14
18
  * @returns Array of block arrays, each fitting within the limits
15
19
  */
16
20
  export declare function splitBlocks(blocks: Block[], options?: SplitBlocksOptions): Block[][];
21
+ /**
22
+ * Splits blocks and also returns a plain-text fallback for each batch, suitable for postMessage `text`.
23
+ */
24
+ export declare function splitBlocksWithText(blocks: Block[], options?: SplitBlocksOptions): SplitBlocksResult[];
17
25
  //# sourceMappingURL=splitter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"splitter.d.ts","sourceRoot":"","sources":["../src/splitter.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,KAAK,EAQR,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,kBAAkB;IAC/B,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAMD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,KAAK,EAAE,EAAE,CA6EpF"}
1
+ {"version":3,"file":"splitter.d.ts","sourceRoot":"","sources":["../src/splitter.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,KAAK,EASR,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,kBAAkB;IAC/B,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,KAAK,EAAE,CAAC;CACnB;AAMD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,KAAK,EAAE,EAAE,CA6EpF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,iBAAiB,EAAE,CAMtG"}
package/dist/splitter.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.splitBlocks = splitBlocks;
4
+ exports.splitBlocksWithText = splitBlocksWithText;
4
5
  const DEFAULT_MAX_BLOCKS = 40;
5
6
  const DEFAULT_MAX_CHARACTERS = 12000;
6
7
  const DEFAULT_MAX_TEXT_SECTION_CHARACTERS = 3000;
@@ -83,6 +84,16 @@ function splitBlocks(blocks, options) {
83
84
  flushBatch();
84
85
  return result.length > 0 ? result : [[]];
85
86
  }
87
+ /**
88
+ * Splits blocks and also returns a plain-text fallback for each batch, suitable for postMessage `text`.
89
+ */
90
+ function splitBlocksWithText(blocks, options) {
91
+ const batches = splitBlocks(blocks, options);
92
+ return batches.map(batch => ({
93
+ text: blocksToPlainText(batch),
94
+ blocks: batch,
95
+ }));
96
+ }
86
97
  /**
87
98
  * Splits a large RichTextBlock into smaller RichTextBlocks by splitting its elements
88
99
  */
@@ -315,3 +326,89 @@ function chunkString(str, limit) {
315
326
  }
316
327
  return chunks;
317
328
  }
329
+ /**
330
+ * Generates a lightweight plain-text fallback from a block batch.
331
+ */
332
+ function blocksToPlainText(blocks) {
333
+ const parts = [];
334
+ const renderTextObject = (text) => text?.text ?? '';
335
+ const renderRichTextSectionElement = (element) => {
336
+ switch (element.type) {
337
+ case 'text':
338
+ return element.text;
339
+ case 'link':
340
+ return element.text ?? element.url;
341
+ case 'emoji':
342
+ return `:${element.name}:`;
343
+ case 'date':
344
+ return element.fallback ?? new Date(element.timestamp * 1000).toISOString();
345
+ case 'user':
346
+ return `<@${element.user_id}>`;
347
+ case 'usergroup':
348
+ return `<!subteam^${element.usergroup_id}>`;
349
+ case 'team':
350
+ return `<team:${element.team_id}>`;
351
+ case 'channel':
352
+ return `<#${element.channel_id}>`;
353
+ case 'broadcast':
354
+ return element.range === 'here' ? `<!here>` : element.range === 'channel' ? `<!channel>` : `<!everyone>`;
355
+ case 'color':
356
+ return element.value;
357
+ default:
358
+ return '';
359
+ }
360
+ };
361
+ const renderRichTextElement = (element) => {
362
+ switch (element.type) {
363
+ case 'rich_text_section':
364
+ return element.elements.map(renderRichTextSectionElement).filter(Boolean).join('');
365
+ case 'rich_text_list':
366
+ return element.elements
367
+ .map((item, idx) => {
368
+ const marker = element.style === 'ordered' ? `${(element.offset ?? 1) + idx}. ` : '- ';
369
+ return marker + item.elements.map(renderRichTextSectionElement).join('');
370
+ })
371
+ .join('\n');
372
+ case 'rich_text_preformatted':
373
+ return element.elements.map(renderRichTextSectionElement).join('');
374
+ case 'rich_text_quote':
375
+ return element.elements.map(renderRichTextSectionElement).map(line => `> ${line}`).join('\n');
376
+ default:
377
+ return '';
378
+ }
379
+ };
380
+ const renderRichTextBlock = (block) => {
381
+ return block.elements.map(renderRichTextElement).filter(Boolean).join('\n');
382
+ };
383
+ const renderBlock = (block) => {
384
+ switch (block.type) {
385
+ case 'section':
386
+ return renderTextObject(block.text);
387
+ case 'header':
388
+ return renderTextObject(block.text);
389
+ case 'context':
390
+ return block.elements
391
+ .map(el => el.text ?? '')
392
+ .filter(Boolean)
393
+ .join(' ');
394
+ case 'rich_text':
395
+ return renderRichTextBlock(block);
396
+ case 'divider':
397
+ return '---';
398
+ case 'image':
399
+ return block.title?.text ?? block.alt_text ?? 'Image';
400
+ case 'table':
401
+ return block.rows
402
+ .map(row => row.map(renderRichTextBlock).join(' | '))
403
+ .join('\n');
404
+ default:
405
+ return '';
406
+ }
407
+ };
408
+ for (const block of blocks) {
409
+ const rendered = renderBlock(block).trim();
410
+ if (rendered)
411
+ parts.push(rendered);
412
+ }
413
+ return parts.join('\n\n');
414
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markdown-to-slack-blocks",
3
- "version": "1.2.2",
3
+ "version": "1.3.1",
4
4
  "description": "Convert Markdown to Slack Block Kit JSON format",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",