slackmark 0.4.0 → 0.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
@@ -14,7 +14,8 @@ renders any Block Kit JSON and pairs perfectly with `slackmark`.
14
14
  - Capability profiles (`latest`, `conservative`, dated presets)
15
15
  - Ordered fallback chains with `Degradation` records (never silent)
16
16
  - Message budgets (50 blocks, table/markdown/chart caps)
17
- - Mermaid `pie` / `xychart-beta` → native charts; other diagrams via injectable renderers
17
+ - Mermaid `pie` / `xychart-beta` → native charts; other mermaid diagrams configured `renderers` or preformatted fallback
18
+ - Math and non-mermaid diagram fences → preformatted/code text (renderers are not consulted)
18
19
  - OOP + constructor DI; `convert()` convenience for simple use
19
20
 
20
21
  ## Quickstart
@@ -60,7 +61,7 @@ Result shape: `{ blocks, text, degradations }` — total on user markdown (never
60
61
  | Preset | Flags |
61
62
  | --- | --- |
62
63
  | `latest` (default) | all |
63
- | `conservative` | `richText` only |
64
+ | `conservative` | all flags off |
64
65
  | `2025-02` | + `markdownBlock` |
65
66
  | `2025-08` | + `tableBlock` |
66
67
  | `2026-03` | + `preformattedLanguage`, `headerLevel` |
@@ -73,36 +74,32 @@ Pass a preset name, a flags object, and/or `capabilityOverrides`.
73
74
 
74
75
  Built-in adapters cover the mapping matrix. Prepend custom `NodeAdapter`s via `createConverter({ adapters })`.
75
76
 
76
- No renderer or uploader is enabled by default, so default conversion performs no network I/O. Hosted renderers only compose durable public PNG URLs; they return `null` when Slack's 3,000-character `image_url` limit would be exceeded. An absent or declining renderer falls back to preformatted source and records a degradation.
77
+ No renderer or uploader is enabled by default, so default conversion performs no network I/O. **Only the mermaid fence adapter reads `config.renderers`** and only after native `pie` / `xychart-beta` charts are unavailable. Math always becomes preformatted/code text; non-mermaid diagram fences (PlantUML, D2, Chart.js, …) always become code blocks. Image-rendered math and non-mermaid diagrams are planned as future Node-only packages.
78
+
79
+ Hosted URL renderers compose durable public PNG URLs (no network I/O in core); they return `null` when Slack's 3,000-character `image_url` limit would be exceeded. An absent or declining renderer falls back to preformatted mermaid source and records a degradation.
77
80
 
78
81
  Import first-party URL adapters from the dedicated subpath:
79
82
 
80
83
  ```ts
81
84
  import { createConverter } from "slackmark";
82
- import {
83
- CodeCogsRenderer,
84
- KrokiRenderer,
85
- MermaidInkRenderer,
86
- QuickChartRenderer,
87
- } from "slackmark/renderers";
85
+ import { KrokiRenderer, MermaidInkRenderer } from "slackmark/renderers";
88
86
 
89
87
  const converter = createConverter({
90
- renderers: [new KrokiRenderer()],
88
+ renderers: [new MermaidInkRenderer()],
89
+ // or: renderers: [new KrokiRenderer()], // consulted for lang=mermaid only
91
90
  });
92
91
  ```
93
92
 
94
- - `KrokiRenderer` covers Mermaid, PlantUML, GraphViz/Dot, D2, Vega-Lite, and other Kroki diagram types.
95
- - `MermaidInkRenderer` emits pako-state PNG URLs with a non-transparent background by default.
96
- - `CodeCogsRenderer` emits `png.image` URLs for math/LaTeX requests.
97
- - `QuickChartRenderer` emits full Chart.js GET URLs and never uses expiring `/chart/create` short URLs.
93
+ - `MermaidInkRenderer` emits pako-state PNG URLs for mermaid fences with a non-transparent background by default.
94
+ - `KrokiRenderer` can render mermaid fences the same way (its other diagram-type aliases are unused by conversion today).
95
+ - `CodeCogsRenderer` and `QuickChartRenderer` are also exported from `slackmark/renderers` as `ImageRenderer` implementations, but no adapter calls them during conversion.
98
96
 
99
- Hosted services receive the diagram, formula, or chart configuration encoded in the URL when Slack fetches the image. Treat them as explicit data-egress opt-ins. Each adapter accepts `baseUrl` for a self-hosted deployment:
97
+ Hosted services receive the diagram source encoded in the URL when Slack fetches the image. Treat them as explicit data-egress opt-ins. Each adapter accepts `baseUrl` for a self-hosted deployment:
100
98
 
101
99
  ```ts
102
100
  const privateRenderers = [
103
101
  new KrokiRenderer({ baseUrl: "https://kroki.internal.example" }),
104
102
  new MermaidInkRenderer({ baseUrl: "https://mermaid.internal.example" }),
105
- new QuickChartRenderer({ baseUrl: "https://charts.internal.example" }),
106
103
  ];
107
104
  ```
108
105
 
@@ -125,9 +122,17 @@ const converter = createConverter({
125
122
 
126
123
  Inject `fetch`, sleep, and clock functions for deterministic tests. The Slack token needs `files:write` and `files:read`; supplying `channelId` shares the upload to the target channel for reliable `slack_file` rendering.
127
124
 
125
+ ## Raw Slack entities
126
+
127
+ Markdown that already contains Slack mrkdwn tokens — `<@U…>`, `<#C…>`, `<!here>`,
128
+ `<!subteam^S…>`, `<!date^…>` — converts to native rich text elements (no resolvers
129
+ required). HTML-entity-escaped forms such as `&lt;@U123&gt;` convert the same way:
130
+ remark decodes character references before slackmark sees the text. To keep the
131
+ syntax literal, wrap it in a code span (`` `<@U123>` ``).
132
+
128
133
  ## Optional remark plugins
129
134
 
130
- `remark-math` and `remark-frontmatter` are dependencies; enable with `enableMath` / `enableFrontmatter`.
135
+ `remark-math` and `remark-frontmatter` are dependencies; enable with `enableMath` / `enableFrontmatter`. Math is parsed when enabled, then emitted as preformatted/code text.
131
136
 
132
137
  ## License
133
138
 
@@ -1 +1 @@
1
- {"version":3,"file":"inline.d.ts","sourceRoot":"","sources":["../../src/adapters/inline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiC,eAAe,EAAgB,MAAM,OAAO,CAAC;AAG1F,OAAO,KAAK,EACV,UAAU,EACV,aAAa,EAEb,cAAc,EAEd,aAAa,EAEd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAmBzE,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC,CAAC;AAEF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACjD,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;CACrD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,EACrC,GAAG,EAAE,iBAAiB,EACtB,SAAS,GAAE,aAAkB,GAC5B,gBAAgB,CAiQlB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,GAAG,MAAM,CAE7E;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAerD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,EACrC,GAAG,EAAE,iBAAiB,GACrB,MAAM,CAuCR;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAK9F;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,iBAAiB,GAAG,UAAU,CAUrF;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAY/F;AAED,gFAAgF;AAChF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,CAY9F;AAED,mFAAmF;AACnF,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,UAAU,GAAG,OAAO,GAC1B,MAAM,CAYR;AAqHD;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,aAAa,GACnB,aAAa,CAAC,cAAc,CAAC,GAAG,SAAS,CAE3C;AASD,gGAAgG;AAChG,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,EACrC,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAQT;AA+MD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAKnD"}
1
+ {"version":3,"file":"inline.d.ts","sourceRoot":"","sources":["../../src/adapters/inline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiC,eAAe,EAAgB,MAAM,OAAO,CAAC;AAG1F,OAAO,KAAK,EACV,UAAU,EACV,aAAa,EAEb,cAAc,EAEd,aAAa,EAEd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAYzE,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC,CAAC;AAEF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACjD,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;CACrD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,EACrC,GAAG,EAAE,iBAAiB,EACtB,SAAS,GAAE,aAAkB,GAC5B,gBAAgB,CAiQlB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,GAAG,MAAM,CAE7E;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAerD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,EACrC,GAAG,EAAE,iBAAiB,GACrB,MAAM,CAuCR;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAK9F;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,iBAAiB,GAAG,UAAU,CAUrF;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAY/F;AAED,gFAAgF;AAChF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,CAY9F;AAED,mFAAmF;AACnF,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,UAAU,GAAG,OAAO,GAC1B,MAAM,CAYR;AA0LD;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,aAAa,GACnB,aAAa,CAAC,cAAc,CAAC,GAAG,SAAS,CAI3C;AAED,gGAAgG;AAChG,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,EACrC,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAQT;AA4HD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAKnD"}
@@ -2,7 +2,7 @@ import type { RootContent } from "mdast";
2
2
  import type { ConversionContext } from "../budget/conversion-context.js";
3
3
  import type { Candidate, NodeAdapter } from "./node-adapter.js";
4
4
  /**
5
- * Display math → preformatted TeX (renderer path is phase 2).
5
+ * Display math → preformatted TeX (`config.renderers` is not consulted).
6
6
  */
7
7
  export declare class MathBlockAdapter implements NodeAdapter {
8
8
  match(node: RootContent, _ctx: ConversionContext): boolean;
@@ -1,5 +1,4 @@
1
1
  export interface CapabilityFlags {
2
- readonly richText: boolean;
3
2
  readonly markdownBlock: boolean;
4
3
  readonly tableBlock: boolean;
5
4
  readonly preformattedLanguage: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../src/capabilities/capabilities.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,eAAe,CAAC;AAEnD,eAAO,MAAM,oBAAoB;aAC/B,MAAM,EAAE,QAAQ;aAChB,YAAY,EAAE,cAAc;aAC5B,QAAQ,EAAE,SAAS;aACnB,QAAQ,EAAE,SAAS;aACnB,QAAQ,EAAE,SAAS;aACnB,QAAQ,EAAE,SAAS;aACnB,QAAQ,EAAE,SAAS;CACX,CAAC;AAEX,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,OAAO,oBAAoB,CAAC,CAAC;AA+CpG,eAAO,MAAM,kBAAkB,EAAE;IAC/B,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,YAAY,EAAE,eAAe,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;CASrC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,CAAC,IAAI,cAAc,CAAC,CAAC,EAAE,OAAO;CACzC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,oBAAoB,GAAG,eAAe,GAAG,sBAAsB,GAAG,SAAS,EAClF,SAAS,CAAC,EAAE,sBAAsB,GACjC,eAAe,CAqBjB"}
1
+ {"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../src/capabilities/capabilities.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,eAAe,CAAC;AAEnD,eAAO,MAAM,oBAAoB;aAC/B,MAAM,EAAE,QAAQ;aAChB,YAAY,EAAE,cAAc;aAC5B,QAAQ,EAAE,SAAS;aACnB,QAAQ,EAAE,SAAS;aACnB,QAAQ,EAAE,SAAS;aACnB,QAAQ,EAAE,SAAS;aACnB,QAAQ,EAAE,SAAS;CACX,CAAC;AAEX,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,OAAO,oBAAoB,CAAC,CAAC;AA4CpG,eAAO,MAAM,kBAAkB,EAAE;IAC/B,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,YAAY,EAAE,eAAe,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;CASrC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,CAAC,IAAI,cAAc,CAAC,CAAC,EAAE,OAAO;CACzC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,oBAAoB,GAAG,eAAe,GAAG,sBAAsB,GAAG,SAAS,EAClF,SAAS,CAAC,EAAE,sBAAsB,GACjC,eAAe,CAqBjB"}
package/dist/index.js CHANGED
@@ -18,7 +18,6 @@ var CapabilityPresetName = {
18
18
  D2026_06: "2026-06"
19
19
  };
20
20
  var ALL_OFF = {
21
- richText: false,
22
21
  markdownBlock: false,
23
22
  tableBlock: false,
24
23
  preformattedLanguage: false,
@@ -27,10 +26,7 @@ var ALL_OFF = {
27
26
  dataVisualization: false,
28
27
  containerBlock: false
29
28
  };
30
- var CONSERVATIVE = {
31
- ...ALL_OFF,
32
- richText: true
33
- };
29
+ var CONSERVATIVE = { ...ALL_OFF };
34
30
  var P_2025_02 = {
35
31
  ...CONSERVATIVE,
36
32
  markdownBlock: true
@@ -89,7 +85,7 @@ function isPresetName(value) {
89
85
  return Object.hasOwn(CAPABILITY_PRESETS, value);
90
86
  }
91
87
  function isFullFlags(value) {
92
- return typeof value.richText === "boolean" && typeof value.markdownBlock === "boolean" && typeof value.tableBlock === "boolean" && typeof value.preformattedLanguage === "boolean" && typeof value.headerLevel === "boolean" && typeof value.dataTable === "boolean" && typeof value.dataVisualization === "boolean" && typeof value.containerBlock === "boolean";
88
+ return typeof value.markdownBlock === "boolean" && typeof value.tableBlock === "boolean" && typeof value.preformattedLanguage === "boolean" && typeof value.headerLevel === "boolean" && typeof value.dataTable === "boolean" && typeof value.dataVisualization === "boolean" && typeof value.containerBlock === "boolean";
93
89
  }
94
90
 
95
91
  // src/constants.ts
@@ -1361,7 +1357,6 @@ import { toString as toString3 } from "mdast-util-to-string";
1361
1357
 
1362
1358
  // src/adapters/inline.ts
1363
1359
  import { toString as toString2 } from "mdast-util-to-string";
1364
- var EMOJI_SHORTCODE_RE = /(?<![A-Za-z0-9]):([a-z0-9_+-]{1,64}):(?![A-Za-z0-9])/g;
1365
1360
  var HTTP_URL_RE = /^(https?:|mailto:)/i;
1366
1361
  var IMAGE_EXT_RE = /\.(png|jpe?g|gif)(?:\?|#|$)/i;
1367
1362
  function emitPhrasing(nodes, ctx, baseStyle = {}) {
@@ -1585,7 +1580,7 @@ function emitPhrasing(nodes, ctx, baseStyle = {}) {
1585
1580
  nodeType: "inlineMath",
1586
1581
  from: "inlineMath",
1587
1582
  to: "style.code",
1588
- reason: "Math rendering not configured; emitted as inline code",
1583
+ reason: "Math is emitted as preformatted/code text",
1589
1584
  path: ctx.path
1590
1585
  });
1591
1586
  break;
@@ -1717,13 +1712,21 @@ function styleOrUndefined(style) {
1717
1712
  }
1718
1713
  return void 0;
1719
1714
  }
1720
- var SLACK_ENTITY_CANDIDATE_RE = /<([@#!][^<>]{1,340}|(?:https?:|mailto:)[^<>|]{1,3000}\|[^<>]{1,300})>/gi;
1715
+ var SLACK_ENTITY_AT_RE = /<([@#!][^<>]{1,340}|(?:https?:|mailto:)[^<>|]{1,3000}\|[^<>]{1,300})>/iy;
1716
+ var CHAR_LT = 60;
1717
+ var CHAR_HASH = 35;
1718
+ var CHAR_AT = 64;
1719
+ var CHAR_COLON = 58;
1721
1720
  var SLACK_USER_RE = /^@([UW][A-Z0-9]{1,60})(?:\|[^>]{0,300})?$/;
1722
1721
  var SLACK_CHANNEL_RE = /^#([CDG][A-Z0-9]{1,60})(?:\|[^>]{0,300})?$/;
1723
1722
  var SLACK_USERGROUP_RE = /^!subteam\^(S[A-Z0-9]{1,60})(?:\|[^>]{0,300})?$/;
1724
1723
  var SLACK_BROADCAST_RE = /^!(here|channel|everyone)(?:\|[^>]{0,300})?$/;
1725
1724
  var SLACK_DATE_RE = /^!date\^(\d{1,13})\^([^^|]{1,300})(?:\^([^^|]{1,3000}))?(?:\|([^]{0,300}))?$/;
1726
1725
  var SLACK_LINK_RE = /^((?:https?:|mailto:)[^|]{1,3000})\|([^]{1,300})$/i;
1726
+ var MENTION_PATTERN = String.raw`(?<![A-Za-z0-9._-])@([a-zA-Z0-9_-](?:[a-zA-Z0-9._-]{0,62}[a-zA-Z0-9_-])?)|(?<![A-Za-z0-9_-])#([a-zA-Z0-9_-]{1,80})`;
1727
+ var MENTION_RE = new RegExp(MENTION_PATTERN, "g");
1728
+ var MENTION_AT_RE = new RegExp(MENTION_PATTERN, "y");
1729
+ var EMOJI_AT_RE = /(?<![A-Za-z0-9]):([a-z0-9_+-]{1,64}):(?![A-Za-z0-9])/y;
1727
1730
  function slackEntityElement(inner, style) {
1728
1731
  const labeledLink = SLACK_LINK_RE.exec(inner);
1729
1732
  if (labeledLink?.[1] !== void 0 && labeledLink[2] !== void 0) {
@@ -1772,39 +1775,79 @@ function slackEntityElement(inner, style) {
1772
1775
  }
1773
1776
  return void 0;
1774
1777
  }
1775
- function slackEntitySplit(value, style, emit) {
1776
- SLACK_ENTITY_CANDIDATE_RE.lastIndex = 0;
1777
- let match = SLACK_ENTITY_CANDIDATE_RE.exec(value);
1778
- if (match === null) {
1779
- return void 0;
1780
- }
1778
+ function scanTextRun(value, style, resolveUser, resolveChannel) {
1779
+ const scanMentions = resolveUser !== void 0 || resolveChannel !== void 0;
1780
+ const styleOpt = styleOrUndefined(style);
1781
1781
  const out = [];
1782
- let last = 0;
1783
1782
  let sawEntity = false;
1784
- while (match !== null) {
1785
- const element = slackEntityElement(match[1] ?? "", style);
1783
+ let i = 0;
1784
+ let textStart = 0;
1785
+ while (i < value.length) {
1786
+ const ch = value.charCodeAt(i);
1787
+ let element;
1788
+ let tokenEnd = 0;
1789
+ if (ch === CHAR_LT) {
1790
+ SLACK_ENTITY_AT_RE.lastIndex = i;
1791
+ const match = SLACK_ENTITY_AT_RE.exec(value);
1792
+ if (match !== null) {
1793
+ element = slackEntityElement(match[1] ?? "", style);
1794
+ if (element !== void 0) {
1795
+ tokenEnd = SLACK_ENTITY_AT_RE.lastIndex;
1796
+ sawEntity = true;
1797
+ }
1798
+ }
1799
+ } else if (scanMentions && (ch === CHAR_AT || ch === CHAR_HASH)) {
1800
+ MENTION_AT_RE.lastIndex = i;
1801
+ const match = MENTION_AT_RE.exec(value);
1802
+ if (match !== null) {
1803
+ const userHandle = match[1];
1804
+ const channelName = match[2];
1805
+ if (userHandle !== void 0 && resolveUser !== void 0) {
1806
+ const userId = resolveUser(userHandle);
1807
+ if (userId !== void 0 && userId.length > 0) {
1808
+ element = { type: "user", user_id: userId, style: styleOpt };
1809
+ tokenEnd = MENTION_AT_RE.lastIndex;
1810
+ }
1811
+ } else if (channelName !== void 0 && resolveChannel !== void 0) {
1812
+ const channelId = resolveChannel(channelName);
1813
+ if (channelId !== void 0 && channelId.length > 0) {
1814
+ element = { type: "channel", channel_id: channelId, style: styleOpt };
1815
+ tokenEnd = MENTION_AT_RE.lastIndex;
1816
+ }
1817
+ }
1818
+ }
1819
+ } else if (ch === CHAR_COLON) {
1820
+ EMOJI_AT_RE.lastIndex = i;
1821
+ const match = EMOJI_AT_RE.exec(value);
1822
+ if (match !== null) {
1823
+ const emoji = { type: "emoji", name: match[1] ?? "" };
1824
+ element = emoji;
1825
+ tokenEnd = EMOJI_AT_RE.lastIndex;
1826
+ }
1827
+ }
1786
1828
  if (element !== void 0) {
1787
- sawEntity = true;
1788
- if (match.index > last) {
1789
- out.push(...emit(value.slice(last, match.index)));
1829
+ if (i > textStart) {
1830
+ out.push({ type: "text", text: value.slice(textStart, i), style: styleOpt });
1790
1831
  }
1791
1832
  out.push(element);
1792
- last = match.index + match[0].length;
1833
+ i = tokenEnd;
1834
+ textStart = i;
1835
+ continue;
1793
1836
  }
1794
- match = SLACK_ENTITY_CANDIDATE_RE.exec(value);
1837
+ i += 1;
1795
1838
  }
1796
- if (!sawEntity) {
1797
- return void 0;
1839
+ if (textStart < value.length) {
1840
+ out.push({ type: "text", text: value.slice(textStart), style: styleOpt });
1798
1841
  }
1799
- if (last < value.length) {
1800
- out.push(...emit(value.slice(last)));
1842
+ if (out.length === 0) {
1843
+ out.push({ type: "text", text: value, style: styleOpt });
1801
1844
  }
1802
- return out;
1845
+ return { elements: out, sawEntity };
1803
1846
  }
1804
1847
  function slackEntityRichTextInlines(value, style) {
1805
- return slackEntitySplit(value, style, (slice) => emitTextWithEmoji(slice, style));
1848
+ const { elements, sawEntity } = scanTextRun(value, style);
1849
+ return sawEntity ? elements : void 0;
1806
1850
  }
1807
- var MENTION_RE = /(?<![A-Za-z0-9._-])@([a-zA-Z0-9_-](?:[a-zA-Z0-9._-]{0,62}[a-zA-Z0-9_-])?)|(?<![A-Za-z0-9_-])#([a-zA-Z0-9_-]{1,80})/g;
1808
1851
  function phrasingNeedsMentionResolution(nodes, ctx) {
1809
1852
  const resolvers = ctx.config.mentionResolvers;
1810
1853
  if (resolvers?.resolveUser === void 0 && resolvers?.resolveChannel === void 0) {
@@ -1815,84 +1858,8 @@ function phrasingNeedsMentionResolution(nodes, ctx) {
1815
1858
  return MENTION_RE.test(plain);
1816
1859
  }
1817
1860
  function emitTextWithMentionsAndEmoji(node, style, ctx) {
1818
- const viaEntities = slackEntitySplit(
1819
- node.value,
1820
- style,
1821
- (slice) => emitMentionsAndEmoji(slice, style, ctx)
1822
- );
1823
- if (viaEntities !== void 0) {
1824
- return viaEntities;
1825
- }
1826
- return emitMentionsAndEmoji(node.value, style, ctx);
1827
- }
1828
- function emitMentionsAndEmoji(value, style, ctx) {
1829
1861
  const resolvers = ctx.config.mentionResolvers;
1830
- const canUser = resolvers?.resolveUser !== void 0;
1831
- const canChannel = resolvers?.resolveChannel !== void 0;
1832
- if (!canUser && !canChannel) {
1833
- return emitTextWithEmoji(value, style);
1834
- }
1835
- const out = [];
1836
- MENTION_RE.lastIndex = 0;
1837
- let last = 0;
1838
- let match = MENTION_RE.exec(value);
1839
- while (match !== null) {
1840
- const start = match.index;
1841
- if (start > last) {
1842
- out.push(...emitTextWithEmoji(value.slice(last, start), style));
1843
- }
1844
- const userHandle = match[1];
1845
- const channelName = match[2];
1846
- if (userHandle !== void 0 && canUser) {
1847
- const userId = resolvers.resolveUser?.(userHandle);
1848
- if (userId !== void 0 && userId.length > 0) {
1849
- out.push({ type: "user", user_id: userId, style: styleOrUndefined(style) });
1850
- } else {
1851
- out.push(...emitTextWithEmoji(match[0], style));
1852
- }
1853
- } else if (channelName !== void 0 && canChannel) {
1854
- const channelId = resolvers.resolveChannel?.(channelName);
1855
- if (channelId !== void 0 && channelId.length > 0) {
1856
- out.push({ type: "channel", channel_id: channelId, style: styleOrUndefined(style) });
1857
- } else {
1858
- out.push(...emitTextWithEmoji(match[0], style));
1859
- }
1860
- } else {
1861
- out.push(...emitTextWithEmoji(match[0], style));
1862
- }
1863
- last = start + match[0].length;
1864
- match = MENTION_RE.exec(value);
1865
- }
1866
- if (last < value.length) {
1867
- out.push(...emitTextWithEmoji(value.slice(last), style));
1868
- }
1869
- if (out.length === 0) {
1870
- out.push(...emitTextWithEmoji(value, style));
1871
- }
1872
- return out;
1873
- }
1874
- function emitTextWithEmoji(value, style) {
1875
- const out = [];
1876
- EMOJI_SHORTCODE_RE.lastIndex = 0;
1877
- let last = 0;
1878
- let match = EMOJI_SHORTCODE_RE.exec(value);
1879
- while (match !== null) {
1880
- const start = match.index;
1881
- if (start > last) {
1882
- out.push({ type: "text", text: value.slice(last, start), style: styleOrUndefined(style) });
1883
- }
1884
- const emoji = { type: "emoji", name: match[1] ?? "" };
1885
- out.push(emoji);
1886
- last = start + match[0].length;
1887
- match = EMOJI_SHORTCODE_RE.exec(value);
1888
- }
1889
- if (last < value.length) {
1890
- out.push({ type: "text", text: value.slice(last), style: styleOrUndefined(style) });
1891
- }
1892
- if (out.length === 0) {
1893
- out.push({ type: "text", text: value, style: styleOrUndefined(style) });
1894
- }
1895
- return out;
1862
+ return scanTextRun(node.value, style, resolvers?.resolveUser, resolvers?.resolveChannel).elements;
1896
1863
  }
1897
1864
  function reassembleSlackLabeledLink(work, i, style) {
1898
1865
  const lead = work[i];
@@ -2712,7 +2679,7 @@ function preformattedMath(value, withLang, ctx) {
2712
2679
  nodeType: "math",
2713
2680
  from: "math",
2714
2681
  to: "rich_text_preformatted",
2715
- reason: "No math renderer configured; emitting TeX source",
2682
+ reason: "Math is emitted as preformatted/code text",
2716
2683
  path: ctx.path
2717
2684
  });
2718
2685
  return preformattedRichText(value, withLang ? "latex" : void 0);
@@ -3612,117 +3579,37 @@ function validateBlocks(blocks) {
3612
3579
  message: `Too many blocks: ${String(blocks.length)} > ${String(MAX_BLOCKS_PER_MESSAGE)}`
3613
3580
  });
3614
3581
  }
3615
- let markdownChars = 0;
3616
- let tableChars = 0;
3617
- let dataTableChars = 0;
3618
- let charts = 0;
3582
+ const counters = {
3583
+ markdownChars: 0,
3584
+ tableChars: 0,
3585
+ dataTableChars: 0,
3586
+ charts: 0
3587
+ };
3619
3588
  blocks.forEach((block, i) => {
3620
- const path = `blocks[${String(i)}]`;
3621
- switch (block.type) {
3622
- case "header":
3623
- if (block.text.text.length > HEADER_TEXT_MAX_CHARS) {
3624
- issues.push({ path, message: "header text > 150" });
3625
- }
3626
- if (block.level !== void 0 && (block.level < 1 || block.level > 4)) {
3627
- issues.push({ path, message: "header level out of 1\u20134" });
3628
- }
3629
- break;
3630
- case "section":
3631
- if (block.text !== void 0 && block.text.text.length > SECTION_TEXT_MAX_CHARS) {
3632
- issues.push({ path, message: "section text > 3000" });
3633
- }
3634
- break;
3635
- case "image":
3636
- if (block.alt_text.length > IMAGE_ALT_MAX_CHARS) {
3637
- issues.push({ path, message: "image alt > 2000" });
3638
- }
3639
- if (block.image_url !== void 0 && block.image_url.length > IMAGE_URL_MAX_CHARS) {
3640
- issues.push({ path, message: "image_url > 3000" });
3641
- }
3642
- break;
3643
- case "context":
3644
- if (block.elements.length > CONTEXT_ELEMENTS_MAX) {
3645
- issues.push({ path, message: "context elements > 10" });
3646
- }
3647
- break;
3648
- case "markdown":
3649
- markdownChars += block.text.length;
3650
- break;
3651
- case "table": {
3652
- if (block.rows.length > TABLE_MAX_ROWS) {
3653
- issues.push({ path, message: "table rows > 100" });
3654
- }
3655
- for (const row of block.rows) {
3656
- if (row.length > TABLE_MAX_COLS) {
3657
- issues.push({ path, message: "table cols > 20" });
3658
- }
3659
- for (const cell of row) {
3660
- tableChars += cellCharCount(cell);
3661
- }
3662
- }
3663
- break;
3664
- }
3665
- case "data_table": {
3666
- if (block.rows.length > DATA_TABLE_MAX_DATA_ROWS + 1) {
3667
- issues.push({ path, message: "data_table rows > 201" });
3668
- }
3669
- for (const row of block.rows) {
3670
- if (row.length > TABLE_MAX_COLS) {
3671
- issues.push({ path, message: "data_table cols > 20" });
3672
- }
3673
- for (const cell of row) {
3674
- dataTableChars += cellCharCount(cell);
3675
- }
3676
- }
3677
- break;
3678
- }
3679
- case "data_visualization":
3680
- charts += 1;
3681
- if (block.title.length > CHART_TITLE_MAX_CHARS) {
3682
- issues.push({ path, message: "chart title > 50" });
3683
- }
3684
- validateChart(block, path, issues);
3685
- break;
3686
- case "container":
3687
- if (block.child_blocks.length > CONTAINER_CHILDREN_MAX) {
3688
- issues.push({ path, message: "container children > 10" });
3689
- }
3690
- if (block.title !== void 0 && block.title.text.length > CONTAINER_TITLE_MAX_CHARS) {
3691
- issues.push({ path, message: "container title > 150" });
3692
- }
3693
- break;
3694
- case "divider":
3695
- case "rich_text":
3696
- break;
3697
- default: {
3698
- const exhaustive = block;
3699
- void exhaustive;
3700
- break;
3701
- }
3702
- }
3589
+ validateBlock(block, `blocks[${String(i)}]`, issues, counters, 0);
3703
3590
  });
3704
- if (markdownChars > MARKDOWN_CUMULATIVE_MAX_CHARS) {
3591
+ if (counters.markdownChars > MARKDOWN_CUMULATIVE_MAX_CHARS) {
3705
3592
  issues.push({
3706
3593
  path: "$",
3707
- message: `markdown cumulative ${String(markdownChars)} > 12000`
3594
+ message: `markdown cumulative ${String(counters.markdownChars)} > 12000`
3708
3595
  });
3709
3596
  }
3710
- if (tableChars > TABLE_CELL_CHARS_MAX) {
3597
+ if (counters.tableChars > TABLE_CELL_CHARS_MAX) {
3711
3598
  issues.push({
3712
3599
  path: "$",
3713
- message: `table chars ${String(tableChars)} > 10000`
3600
+ message: `table chars ${String(counters.tableChars)} > 10000`
3714
3601
  });
3715
3602
  }
3716
- if (dataTableChars > DATA_TABLE_CELL_CHARS_MAX) {
3603
+ if (counters.dataTableChars > DATA_TABLE_CELL_CHARS_MAX) {
3717
3604
  issues.push({
3718
3605
  path: "$",
3719
- message: `data_table chars ${String(dataTableChars)} > 20000`
3606
+ message: `data_table chars ${String(counters.dataTableChars)} > 20000`
3720
3607
  });
3721
3608
  }
3722
- if (charts > DATA_VISUALIZATION_MAX_PER_MESSAGE) {
3609
+ if (counters.charts > DATA_VISUALIZATION_MAX_PER_MESSAGE) {
3723
3610
  issues.push({
3724
3611
  path: "$",
3725
- message: `charts ${String(charts)} > ${String(DATA_VISUALIZATION_MAX_PER_MESSAGE)}`
3612
+ message: `charts ${String(counters.charts)} > ${String(DATA_VISUALIZATION_MAX_PER_MESSAGE)}`
3726
3613
  });
3727
3614
  }
3728
3615
  if (issues.length > 0) {
@@ -3731,6 +3618,158 @@ function validateBlocks(blocks) {
3731
3618
  }
3732
3619
  return blocks;
3733
3620
  }
3621
+ function validateBlock(block, path, issues, counters, depth) {
3622
+ if (depth > MAX_BLOCKS_PER_MESSAGE) {
3623
+ issues.push({ path, message: "container nesting too deep" });
3624
+ return;
3625
+ }
3626
+ switch (block.type) {
3627
+ case "header":
3628
+ if (block.text.text.length > HEADER_TEXT_MAX_CHARS) {
3629
+ issues.push({ path, message: "header text > 150" });
3630
+ }
3631
+ if (block.level !== void 0 && (block.level < 1 || block.level > 4)) {
3632
+ issues.push({ path, message: "header level out of 1\u20134" });
3633
+ }
3634
+ break;
3635
+ case "section":
3636
+ if (block.text !== void 0 && block.text.text.length > SECTION_TEXT_MAX_CHARS) {
3637
+ issues.push({ path, message: "section text > 3000" });
3638
+ }
3639
+ break;
3640
+ case "image":
3641
+ if (block.alt_text.length > IMAGE_ALT_MAX_CHARS) {
3642
+ issues.push({ path, message: "image alt > 2000" });
3643
+ }
3644
+ if (block.image_url !== void 0 && block.image_url.length > IMAGE_URL_MAX_CHARS) {
3645
+ issues.push({ path, message: "image_url > 3000" });
3646
+ }
3647
+ break;
3648
+ case "context":
3649
+ if (block.elements.length === 0) {
3650
+ issues.push({ path, message: "context elements empty" });
3651
+ } else if (block.elements.length > CONTEXT_ELEMENTS_MAX) {
3652
+ issues.push({ path, message: "context elements > 10" });
3653
+ }
3654
+ break;
3655
+ case "markdown":
3656
+ counters.markdownChars += block.text.length;
3657
+ break;
3658
+ case "table":
3659
+ if (block.rows.length > TABLE_MAX_ROWS) {
3660
+ issues.push({ path, message: "table rows > 100" });
3661
+ }
3662
+ counters.tableChars += validateTableCells(block.rows, path, "table cols > 20", issues);
3663
+ break;
3664
+ case "data_table":
3665
+ if (block.rows.length > DATA_TABLE_MAX_DATA_ROWS + 1) {
3666
+ issues.push({ path, message: "data_table rows > 201" });
3667
+ }
3668
+ counters.dataTableChars += validateTableCells(
3669
+ block.rows,
3670
+ path,
3671
+ "data_table cols > 20",
3672
+ issues
3673
+ );
3674
+ break;
3675
+ case "data_visualization":
3676
+ counters.charts += 1;
3677
+ if (block.title.length > CHART_TITLE_MAX_CHARS) {
3678
+ issues.push({ path, message: "chart title > 50" });
3679
+ }
3680
+ validateChart(block, path, issues);
3681
+ break;
3682
+ case "container":
3683
+ if (block.child_blocks.length > CONTAINER_CHILDREN_MAX) {
3684
+ issues.push({ path, message: "container children > 10" });
3685
+ }
3686
+ if (block.title !== void 0 && block.title.text.length > CONTAINER_TITLE_MAX_CHARS) {
3687
+ issues.push({ path, message: "container title > 150" });
3688
+ }
3689
+ if (block.rich_text_title !== void 0) {
3690
+ validateRichTextBlock(block.rich_text_title, `${path}.rich_text_title`, issues);
3691
+ }
3692
+ for (const [i, child] of block.child_blocks.entries()) {
3693
+ validateBlock(child, `${path}.child_blocks[${String(i)}]`, issues, counters, depth + 1);
3694
+ }
3695
+ break;
3696
+ case "divider":
3697
+ break;
3698
+ case "rich_text":
3699
+ validateRichTextBlock(block, path, issues);
3700
+ break;
3701
+ default: {
3702
+ const exhaustive = block;
3703
+ void exhaustive;
3704
+ break;
3705
+ }
3706
+ }
3707
+ }
3708
+ function validateRichTextBlock(block, path, issues) {
3709
+ if (block.elements.length === 0) {
3710
+ issues.push({ path, message: "rich_text elements empty" });
3711
+ return;
3712
+ }
3713
+ for (const [i, element] of block.elements.entries()) {
3714
+ validateRichTextElement(element, `${path}.elements[${String(i)}]`, issues);
3715
+ }
3716
+ }
3717
+ function validateRichTextElement(element, path, issues) {
3718
+ switch (element.type) {
3719
+ case "rich_text_section":
3720
+ case "rich_text_quote":
3721
+ case "rich_text_preformatted":
3722
+ if (element.elements.length === 0) {
3723
+ issues.push({ path, message: `${element.type} elements empty` });
3724
+ return;
3725
+ }
3726
+ for (const [i, inline] of element.elements.entries()) {
3727
+ if (inline.type === "link" && !isValidRichTextLinkUrl(inline.url)) {
3728
+ issues.push({
3729
+ path: `${path}.elements[${String(i)}]`,
3730
+ message: "invalid rich text link URL"
3731
+ });
3732
+ }
3733
+ }
3734
+ break;
3735
+ case "rich_text_list":
3736
+ if (element.elements.length === 0) {
3737
+ issues.push({ path, message: "rich_text_list elements empty" });
3738
+ return;
3739
+ }
3740
+ for (const [i, section] of element.elements.entries()) {
3741
+ validateRichTextElement(section, `${path}.elements[${String(i)}]`, issues);
3742
+ }
3743
+ break;
3744
+ default: {
3745
+ const exhaustive = element;
3746
+ void exhaustive;
3747
+ break;
3748
+ }
3749
+ }
3750
+ }
3751
+ function validateTableCells(rows, path, colsMessage, issues) {
3752
+ let chars = 0;
3753
+ for (const [r, row] of rows.entries()) {
3754
+ if (row.length > TABLE_MAX_COLS) {
3755
+ issues.push({ path, message: colsMessage });
3756
+ }
3757
+ for (const [c, cell] of row.entries()) {
3758
+ chars += cellCharCount(cell);
3759
+ if (cell.type === "rich_text") {
3760
+ validateRichTextBlock(cell, `${path}.rows[${String(r)}][${String(c)}]`, issues);
3761
+ }
3762
+ }
3763
+ }
3764
+ return chars;
3765
+ }
3766
+ var HTTP_OR_MAILTO_URL_RE = /^(https?:|mailto:)/i;
3767
+ function isValidRichTextLinkUrl(url) {
3768
+ if (url.length === 0 || url.length > IMAGE_URL_MAX_CHARS) {
3769
+ return false;
3770
+ }
3771
+ return HTTP_OR_MAILTO_URL_RE.test(url);
3772
+ }
3734
3773
  function cellCharCount(cell) {
3735
3774
  if (cell.type === "raw_text") {
3736
3775
  return cell.text.length;