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 +12 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/parser.js +8 -0
- package/dist/splitter.d.ts +8 -0
- package/dist/splitter.d.ts.map +1 -1
- package/dist/splitter.js +97 -0
- package/package.json +1 -1
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
|
-
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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...>
|
package/dist/splitter.d.ts
CHANGED
|
@@ -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
|
package/dist/splitter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"splitter.d.ts","sourceRoot":"","sources":["../src/splitter.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,KAAK,
|
|
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
|
+
}
|