@assistant-ui/react-generative-ui 0.0.15 → 0.0.17
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/convert/boundSpec.d.ts +51 -0
- package/dist/convert/boundSpec.d.ts.map +1 -0
- package/dist/convert/boundSpec.js +108 -0
- package/dist/convert/boundSpec.js.map +1 -0
- package/dist/convert/copyBounded.d.ts +28 -0
- package/dist/convert/copyBounded.d.ts.map +1 -0
- package/dist/convert/copyBounded.js +38 -0
- package/dist/convert/copyBounded.js.map +1 -0
- package/dist/convert/isElement.d.ts +6 -0
- package/dist/convert/isElement.d.ts.map +1 -0
- package/dist/convert/isElement.js +6 -0
- package/dist/convert/isElement.js.map +1 -0
- package/dist/convert/takeRun.d.ts +9 -0
- package/dist/convert/takeRun.d.ts.map +1 -0
- package/dist/convert/takeRun.js +20 -0
- package/dist/convert/takeRun.js.map +1 -0
- package/dist/slack/constants.d.ts +1 -26
- package/dist/slack/constants.d.ts.map +1 -1
- package/dist/slack/constants.js +1 -26
- package/dist/slack/constants.js.map +1 -1
- package/dist/slack/fromSlackBlocks.js +6 -10
- package/dist/slack/fromSlackBlocks.js.map +1 -1
- package/dist/slack/toSlackBlocks.d.ts.map +1 -1
- package/dist/slack/toSlackBlocks.js +28 -95
- package/dist/slack/toSlackBlocks.js.map +1 -1
- package/dist/teams/constants.d.ts +1 -28
- package/dist/teams/constants.d.ts.map +1 -1
- package/dist/teams/constants.js +1 -32
- package/dist/teams/constants.js.map +1 -1
- package/dist/teams/toAdaptiveCard.d.ts.map +1 -1
- package/dist/teams/toAdaptiveCard.js +12 -23
- package/dist/teams/toAdaptiveCard.js.map +1 -1
- package/dist/teams/toTeamsAttachments.d.ts.map +1 -1
- package/dist/teams/toTeamsAttachments.js +5 -9
- package/dist/teams/toTeamsAttachments.js.map +1 -1
- package/dist/vocabulary/list.js +4 -2
- package/dist/vocabulary/list.js.map +1 -1
- package/package.json +6 -6
- package/src/convert/boundSpec.test.ts +192 -0
- package/src/convert/boundSpec.ts +134 -0
- package/src/convert/copyBounded.test.ts +131 -0
- package/src/convert/copyBounded.ts +38 -0
- package/src/convert/isElement.ts +6 -0
- package/src/convert/takeRun.test.ts +64 -0
- package/src/convert/takeRun.ts +19 -0
- package/src/slack/constants.ts +0 -29
- package/src/slack/fromSlackBlocks.test.ts +105 -7
- package/src/slack/fromSlackBlocks.ts +9 -9
- package/src/slack/toSlackBlocks.test.ts +82 -2
- package/src/slack/toSlackBlocks.ts +68 -164
- package/src/teams/constants.ts +0 -42
- package/src/teams/toAdaptiveCard.test.ts +74 -2
- package/src/teams/toAdaptiveCard.ts +29 -36
- package/src/teams/toTeamsAttachments.ts +4 -17
- package/src/vocabulary/list.test.tsx +35 -9
- package/src/vocabulary/list.tsx +4 -2
- package/dist/teams/boundSpec.d.ts +0 -20
- package/dist/teams/boundSpec.d.ts.map +0 -1
- package/dist/teams/boundSpec.js +0 -67
- package/dist/teams/boundSpec.js.map +0 -1
- package/src/teams/boundSpec.ts +0 -102
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fromSlackBlocks.js","names":[],"sources":["../../src/slack/fromSlackBlocks.ts"],"sourcesContent":["import type { Action, UIElement } from \"../ir\";\nimport {\n ACTIONS_ELEMENT_CAP,\n CARD_ACTIONS_CAP,\n CAROUSEL_CARD_CAP,\n CONTEXT_ELEMENT_CAP,\n DATA_TABLE_COLUMN_CAP,\n DATA_TABLE_ROW_CAP,\n FACT_FIELD_CAP,\n INBOUND_BLOCK_CAP,\n RADIO_OPTION_CAP,\n SELECT_OPTION_CAP,\n} from \"./constants\";\nimport type { FromSlackBlocksResult, SlackConversionWarning } from \"./types\";\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst isDefined = <T>(value: T | undefined): value is T => value !== undefined;\n\nconst asString = (value: unknown): string =>\n typeof value === \"string\" ? value : \"\";\n\nconst textOf = (value: unknown): string =>\n isRecord(value) && typeof value[\"text\"] === \"string\" ? value[\"text\"] : \"\";\n\nconst warn = (\n warnings: SlackConversionWarning[],\n code: SlackConversionWarning[\"code\"],\n component: string,\n detail: string,\n) => {\n warnings.push({ code, component, detail });\n};\n\n/**\n * The shared bounded-iteration primitive: slices `value` to `cap` entries\n * without ever reading past that many indices, so a hostile array (sparse or\n * proxied with a fabricated `length`) cannot stall the event loop. `slice`\n * itself performs the bounded read; only its own reported length is\n * inspected to detect truncation.\n */\nconst clampArray = (\n value: unknown,\n cap: number,\n): { readonly items: unknown[]; readonly truncated: boolean } => {\n if (!Array.isArray(value)) return { items: [], truncated: false };\n return { items: value.slice(0, cap), truncated: value.length > cap };\n};\n\nconst boundedArray = (\n value: unknown,\n cap: number,\n warnings: SlackConversionWarning[],\n component: string,\n detail: string,\n): unknown[] => {\n const { items, truncated } = clampArray(value, cap);\n if (truncated) warn(warnings, \"clamped\", component, detail);\n return items;\n};\n\nconst parseValuePayload = (value: unknown): Record<string, unknown> => {\n if (typeof value !== \"string\") return {};\n try {\n const parsed: unknown = JSON.parse(value);\n return isRecord(parsed) ? parsed : {};\n } catch {\n return {};\n }\n};\n\nconst decodeAction = (actionId: unknown, value: unknown): Action => ({\n ...parseValuePayload(value),\n type: typeof actionId === \"string\" ? actionId : \"action\",\n});\n\nconst headerFrom = (block: Record<string, unknown>): UIElement => ({\n $type: \"Header\",\n text: textOf(block[\"text\"]),\n});\n\nconst imageFrom = (block: Record<string, unknown>): UIElement => ({\n $type: \"Image\",\n src: asString(block[\"image_url\"]),\n alt: asString(block[\"alt_text\"]),\n});\n\nconst markdownFrom = (block: Record<string, unknown>): UIElement => ({\n $type: \"Markdown\",\n value: asString(block[\"text\"]),\n});\n\nconst FACT_FIELD_PATTERN = /^\\*([\\s\\S]*?)\\*\\n([\\s\\S]*)$/;\n\nconst factFrom = (\n field: unknown,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const text = textOf(field);\n const match = FACT_FIELD_PATTERN.exec(text);\n if (match) {\n const [, label, value] = match;\n return { $type: \"Fact\", label, value };\n }\n warn(\n warnings,\n \"fallback\",\n \"Fact\",\n \"A section field that did not match the label/value format was kept as a value-only fact.\",\n );\n return { $type: \"Fact\", label: \"\", value: text };\n};\n\nconst listViewItemFrom = (\n text: string,\n button: Record<string, unknown>,\n): UIElement => ({\n $type: \"ListViewItem\",\n $action: decodeAction(button[\"action_id\"], button[\"value\"]),\n children: { $type: \"Text\", value: text },\n});\n\n/**\n * Recognizes a section block that stands for a `ListViewItem`: mrkdwn text\n * paired with a button accessory, and no `fields` (a fields section is\n * always a Facts section, never a list row).\n */\nconst listViewItemFromSection = (block: unknown): UIElement | undefined => {\n if (!isRecord(block) || block[\"type\"] !== \"section\") return undefined;\n if (Array.isArray(block[\"fields\"])) return undefined;\n const accessory = block[\"accessory\"];\n if (!isRecord(accessory) || accessory[\"type\"] !== \"button\") {\n return undefined;\n }\n return listViewItemFrom(textOf(block[\"text\"]), accessory);\n};\n\nconst isDividerBlock = (block: unknown): boolean =>\n isRecord(block) && block[\"type\"] === \"divider\";\n\nconst sectionFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement[] => {\n const fields = block[\"fields\"];\n if (Array.isArray(fields)) {\n return boundedArray(\n fields,\n FACT_FIELD_CAP,\n warnings,\n \"Fact\",\n `fields were clamped to ${FACT_FIELD_CAP} entries.`,\n ).map((field) => factFrom(field, warnings));\n }\n return [{ $type: \"Text\", value: textOf(block[\"text\"]) }];\n};\n\nconst captionFrom = (element: unknown): UIElement => ({\n $type: \"Caption\",\n value: textOf(element),\n});\n\nconst contextFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const elements = boundedArray(\n block[\"elements\"],\n CONTEXT_ELEMENT_CAP,\n warnings,\n \"Row\",\n `elements were clamped to ${CONTEXT_ELEMENT_CAP} entries.`,\n );\n if (elements.length > 1) {\n return { $type: \"Row\", children: elements.map(captionFrom) };\n }\n return {\n $type: \"Caption\",\n value: elements.length === 1 ? textOf(elements[0]) : \"\",\n };\n};\n\nconst optionFrom = (\n option: unknown,\n): { label: string; value: string } | undefined => {\n if (!isRecord(option) || typeof option[\"value\"] !== \"string\") {\n return undefined;\n }\n return { label: textOf(option[\"text\"]), value: option[\"value\"] };\n};\n\nconst buttonFrom = (element: Record<string, unknown>): UIElement => {\n const style = element[\"style\"];\n return {\n $type: \"Button\",\n label: textOf(element[\"text\"]),\n ...(style === \"primary\" || style === \"danger\"\n ? { buttonStyle: style }\n : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n };\n};\n\nconst selectFrom = (\n element: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const options = boundedArray(\n element[\"options\"],\n SELECT_OPTION_CAP,\n warnings,\n \"Select\",\n `options were clamped to ${SELECT_OPTION_CAP} entries.`,\n );\n const placeholder = element[\"placeholder\"];\n return {\n $type: \"Select\",\n options: options.map(optionFrom).filter(isDefined),\n ...(isRecord(placeholder) ? { placeholder: textOf(placeholder) } : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n };\n};\n\nconst datePickerFrom = (element: Record<string, unknown>): UIElement => ({\n $type: \"DatePicker\",\n ...(typeof element[\"initial_date\"] === \"string\"\n ? { value: element[\"initial_date\"] }\n : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n});\n\nconst checkboxFrom = (element: Record<string, unknown>): UIElement => {\n const options = Array.isArray(element[\"options\"]) ? element[\"options\"] : [];\n const first = options[0];\n const initialOptions = Array.isArray(element[\"initial_options\"])\n ? element[\"initial_options\"]\n : [];\n const firstValue =\n isRecord(first) && typeof first[\"value\"] === \"string\" ? first[\"value\"] : \"\";\n const firstChecked = initialOptions\n .slice(0, SELECT_OPTION_CAP)\n .some((option) => isRecord(option) && option[\"value\"] === firstValue);\n return {\n $type: \"Checkbox\",\n label: isRecord(first) ? textOf(first[\"text\"]) : \"\",\n name: firstValue,\n ...(firstChecked ? { defaultChecked: true } : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n };\n};\n\nconst radioGroupFrom = (\n element: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const options = boundedArray(\n element[\"options\"],\n RADIO_OPTION_CAP,\n warnings,\n \"RadioGroup\",\n `options were clamped to ${RADIO_OPTION_CAP} entries.`,\n );\n const initialOption = element[\"initial_option\"];\n const initialValue =\n isRecord(initialOption) && typeof initialOption[\"value\"] === \"string\"\n ? initialOption[\"value\"]\n : undefined;\n return {\n $type: \"RadioGroup\",\n options: options.map(optionFrom).filter(isDefined),\n ...(initialValue !== undefined ? { defaultValue: initialValue } : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n };\n};\n\nconst actionElementFrom = (\n element: unknown,\n warnings: SlackConversionWarning[],\n): UIElement | undefined => {\n if (isRecord(element)) {\n switch (element[\"type\"]) {\n case \"button\":\n return buttonFrom(element);\n case \"static_select\":\n return selectFrom(element, warnings);\n case \"datepicker\":\n return datePickerFrom(element);\n case \"checkboxes\":\n return checkboxFrom(element);\n case \"radio_buttons\":\n return radioGroupFrom(element, warnings);\n }\n }\n warn(\n warnings,\n \"dropped\",\n isRecord(element) && typeof element[\"type\"] === \"string\"\n ? element[\"type\"]\n : \"unknown\",\n \"Unknown action element type was dropped.\",\n );\n return undefined;\n};\n\nconst actionsFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement[] => {\n const elements = boundedArray(\n block[\"elements\"],\n ACTIONS_ELEMENT_CAP,\n warnings,\n \"Actions\",\n `elements were clamped to ${ACTIONS_ELEMENT_CAP} entries.`,\n );\n return elements\n .map((element) => actionElementFrom(element, warnings))\n .filter(isDefined);\n};\n\nconst inputFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement[] => {\n const element = block[\"element\"];\n if (!isRecord(element) || element[\"type\"] !== \"plain_text_input\") {\n warn(\n warnings,\n \"dropped\",\n \"Input\",\n \"Unknown input element type was dropped.\",\n );\n return [];\n }\n const placeholder = element[\"placeholder\"];\n return [\n {\n $type: \"Input\",\n label: textOf(block[\"label\"]),\n ...(isRecord(placeholder) ? { placeholder: textOf(placeholder) } : {}),\n ...(element[\"multiline\"] === true ? { multiline: true } : {}),\n $action: decodeAction(element[\"action_id\"], undefined),\n },\n ];\n};\n\ntype CardFooterButton = { readonly label: string; readonly $action: Action };\n\nconst cardFooterButtonFrom = (button: unknown): CardFooterButton | undefined =>\n isRecord(button)\n ? {\n label: textOf(button[\"text\"]),\n $action: decodeAction(button[\"action_id\"], button[\"value\"]),\n }\n : undefined;\n\n/**\n * Assigns a card's footer actions by style rather than position: the\n * primary-styled button is the confirm action and a non-primary button is\n * cancel, since that is the pairing {@link toSlackBlocks} itself emits.\n * Position is only a fallback for input that does not carry that pairing\n * (zero or multiple primary-styled buttons among two or more actions).\n */\nconst assignCardFooterActions = (\n actions: unknown[],\n warnings: SlackConversionWarning[],\n): {\n readonly confirm: CardFooterButton | undefined;\n readonly cancel: CardFooterButton | undefined;\n readonly leftover: unknown[];\n} => {\n const isPrimaryButton = (action: unknown): boolean =>\n isRecord(action) && action[\"style\"] === \"primary\";\n const primaryButtons = actions.filter(isPrimaryButton);\n\n let confirmSource: unknown = undefined;\n let cancelSource: unknown = undefined;\n let leftover: unknown[] = [];\n\n if (primaryButtons.length === 1) {\n const [confirmButton] = primaryButtons;\n const [cancelButton, ...rest] = actions.filter(\n (action) => !isPrimaryButton(action),\n );\n confirmSource = confirmButton;\n cancelSource = cancelButton;\n leftover = rest;\n } else if (primaryButtons.length === 0 && actions.length <= 1) {\n const [onlyButton] = actions;\n cancelSource = onlyButton;\n } else if (actions.length > 0) {\n const [first, second, ...rest] = actions;\n confirmSource = first;\n cancelSource = second;\n leftover = rest;\n warn(\n warnings,\n \"fallback\",\n \"Card\",\n \"Confirm and cancel buttons could not be assigned by style and fell back to position.\",\n );\n }\n\n return {\n confirm: cardFooterButtonFrom(confirmSource),\n cancel: cardFooterButtonFrom(cancelSource),\n leftover,\n };\n};\n\nconst cardFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const titleText = textOf(block[\"title\"]);\n const children: UIElement[] = [];\n const heroImage = block[\"hero_image\"];\n if (isRecord(heroImage)) {\n children.push({\n $type: \"Image\",\n src: asString(heroImage[\"image_url\"]),\n alt: asString(heroImage[\"alt_text\"]),\n });\n }\n const body = block[\"body\"];\n if (isRecord(body)) {\n children.push({ $type: \"Text\", value: textOf(body) });\n }\n const subtext = block[\"subtext\"];\n if (isRecord(subtext)) {\n children.push({ $type: \"Caption\", value: textOf(subtext) });\n }\n const actions = boundedArray(\n block[\"actions\"],\n CARD_ACTIONS_CAP,\n warnings,\n \"Card\",\n `actions were clamped to ${CARD_ACTIONS_CAP} entries.`,\n );\n const { confirm, cancel, leftover } = assignCardFooterActions(\n actions,\n warnings,\n );\n if (leftover.length > 0) {\n for (const extra of leftover) {\n if (isRecord(extra)) children.push(buttonFrom(extra));\n }\n warn(\n warnings,\n \"fallback\",\n \"Card\",\n \"Card actions beyond confirm and cancel were appended as Button children.\",\n );\n }\n return {\n $type: \"Card\",\n ...(titleText ? { title: titleText } : {}),\n ...(children.length > 0 ? { children } : {}),\n ...(confirm !== undefined ? { confirm } : {}),\n ...(cancel !== undefined ? { cancel } : {}),\n };\n};\n\nconst carouselFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const elements = boundedArray(\n block[\"elements\"],\n CAROUSEL_CARD_CAP,\n warnings,\n \"Carousel\",\n `cards were clamped to ${CAROUSEL_CARD_CAP} entries.`,\n );\n const cards = elements\n .filter(isRecord)\n .map((card) => cardFrom(card, warnings));\n return { $type: \"Carousel\", children: cards };\n};\n\nconst ALERT_LEVEL_TONES: Record<string, string> = {\n error: \"danger\",\n success: \"success\",\n warning: \"warning\",\n info: \"info\",\n};\n\nconst alertFrom = (block: Record<string, unknown>): UIElement => {\n const level = block[\"level\"];\n const tone = typeof level === \"string\" ? ALERT_LEVEL_TONES[level] : undefined;\n return {\n $type: \"Alert\",\n description: textOf(block[\"text\"]),\n ...(tone !== undefined ? { tone } : {}),\n };\n};\n\nconst dataTableCellValue = (cell: unknown): string | number => {\n if (!isRecord(cell)) return \"\";\n const text = asString(cell[\"text\"]);\n if (cell[\"type\"] === \"raw_number\") {\n const parsed = Number(text);\n if (Number.isFinite(parsed)) return parsed;\n }\n return text;\n};\n\nconst dataTableFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const { items: rows, truncated: rowsTruncated } = clampArray(\n block[\"rows\"],\n DATA_TABLE_ROW_CAP + 1,\n );\n if (rowsTruncated) {\n warn(\n warnings,\n \"clamped\",\n \"Table\",\n `rows were clamped to ${DATA_TABLE_ROW_CAP} entries.`,\n );\n }\n const [headerRow, ...dataRows] = rows;\n const { items: headerCells, truncated: headerTruncated } = clampArray(\n headerRow,\n DATA_TABLE_COLUMN_CAP,\n );\n let columnsTruncated = headerTruncated;\n const normalizedRows = dataRows.map((row) => {\n const { items: cells, truncated } = clampArray(row, DATA_TABLE_COLUMN_CAP);\n if (truncated) columnsTruncated = true;\n return cells;\n });\n if (columnsTruncated) {\n warn(\n warnings,\n \"clamped\",\n \"Table\",\n `columns were clamped to ${DATA_TABLE_COLUMN_CAP} entries.`,\n );\n }\n const headerAllEmpty = headerCells.every(\n (cell) => !isRecord(cell) || asString(cell[\"text\"]) === \"\",\n );\n const columns = headerCells.map((cell) => ({\n label: isRecord(cell) ? asString(cell[\"text\"]) : \"\",\n }));\n return {\n $type: \"Table\",\n ...(headerAllEmpty ? {} : { columns }),\n rows: normalizedRows.map((row) => row.map(dataTableCellValue)),\n };\n};\n\nfunction convertBlock(\n block: unknown,\n warnings: SlackConversionWarning[],\n): UIElement[] {\n if (isRecord(block)) {\n switch (block[\"type\"]) {\n case \"header\":\n return [headerFrom(block)];\n case \"section\":\n return sectionFrom(block, warnings);\n case \"context\":\n return [contextFrom(block, warnings)];\n case \"image\":\n return [imageFrom(block)];\n case \"divider\":\n return [{ $type: \"Divider\" }];\n case \"actions\":\n return actionsFrom(block, warnings);\n case \"input\":\n return inputFrom(block, warnings);\n case \"markdown\":\n return [markdownFrom(block)];\n case \"card\":\n return [cardFrom(block, warnings)];\n case \"carousel\":\n return [carouselFrom(block, warnings)];\n case \"alert\":\n return [alertFrom(block)];\n case \"data_table\":\n return [dataTableFrom(block, warnings)];\n }\n }\n warn(\n warnings,\n \"dropped\",\n isRecord(block) && typeof block[\"type\"] === \"string\"\n ? block[\"type\"]\n : \"unknown\",\n \"Unknown block type was dropped.\",\n );\n return [];\n}\n\nconst extractBlockList = (input: unknown): unknown[] | undefined => {\n if (Array.isArray(input)) return input;\n if (isRecord(input) && Array.isArray(input[\"blocks\"])) return input[\"blocks\"];\n return undefined;\n};\n\n/**\n * Walks the (already bounded) block list, folding a run of one or more\n * consecutive `ListViewItem`-shaped section blocks, optionally separated by\n * single divider blocks, into one `ListView` container instead of emitting\n * root-level `ListViewItem` nodes or the interleaved dividers. A divider that\n * is not directly between two such items is left for {@link convertBlock} to\n * decode as a literal `Divider`.\n */\nfunction convertBlockList(\n blocks: unknown[],\n warnings: SlackConversionWarning[],\n): UIElement[] {\n const nodes: UIElement[] = [];\n let index = 0;\n while (index < blocks.length) {\n const firstItem = listViewItemFromSection(blocks[index]);\n if (firstItem === undefined) {\n nodes.push(...convertBlock(blocks[index], warnings));\n index += 1;\n continue;\n }\n const items: UIElement[] = [firstItem];\n let cursor = index + 1;\n while (true) {\n if (isDividerBlock(blocks[cursor])) {\n const candidate = listViewItemFromSection(blocks[cursor + 1]);\n if (candidate === undefined) break;\n items.push(candidate);\n cursor += 2;\n continue;\n }\n const candidate = listViewItemFromSection(blocks[cursor]);\n if (candidate === undefined) break;\n items.push(candidate);\n cursor += 1;\n }\n nodes.push({ $type: \"ListView\", children: items });\n index = cursor;\n }\n return nodes;\n}\n\n/**\n * Converts Slack Block Kit JSON back into a generative-ui IR tree, inverting\n * {@link toSlackBlocks}. Accepts a blocks array or a `{ blocks }` wrapper;\n * malformed input resolves to an empty node list plus a warning, and an\n * unrecognized block or action element is skipped with a \"dropped\" warning.\n * Every array read along the way (blocks, fields, options, elements, cards,\n * rows, cells) is bounded to its site's cap before it is mapped over, so a\n * hostile array (sparse or proxied with a fabricated `length`) is clamped\n * with a \"clamped\" warning instead of stalling the event loop. Never throws.\n *\n * The Slack shape is lossier than the IR, so some information cannot be\n * recovered: a context block's elements always decode as `Caption` (the\n * original `Badge` vs `Caption` distinction does not survive the round trip),\n * a button's `buttonStyle` is only recovered for the `primary`/`danger` Slack\n * styles, a native alert block's `title` and `description` merge into a\n * single `description` string, and a card's layout collapses to its `title`,\n * a flat `children` list (image, then text, then caption, in that order),\n * and `confirm`/`cancel` footer buttons. A section field's label and value\n * are split on the first `*`+newline delimiter, so a label or value that\n * itself contains that exact delimiter cannot be split back unambiguously.\n * A data-table cell that Slack carries as `raw_text` always decodes as a\n * string, even when its text is the stringified form of a boolean, since the\n * Slack shape does not distinguish a boolean from ordinary text.\n */\nexport function fromSlackBlocks(blocks: unknown): FromSlackBlocksResult {\n const warnings: SlackConversionWarning[] = [];\n try {\n const list = extractBlockList(blocks);\n if (list === undefined) {\n return {\n nodes: [],\n warnings: [\n {\n code: \"dropped\",\n component: \"Root\",\n detail: \"Input could not be converted.\",\n },\n ],\n };\n }\n const boundedList = boundedArray(\n list,\n INBOUND_BLOCK_CAP,\n warnings,\n \"Root\",\n `blocks were clamped to ${INBOUND_BLOCK_CAP} entries.`,\n );\n const nodes = convertBlockList(boundedList, warnings);\n return { nodes, warnings };\n } catch {\n return {\n nodes: [],\n warnings: [\n {\n code: \"dropped\",\n component: \"Root\",\n detail: \"Input could not be converted.\",\n },\n ],\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fromSlackBlocks.js","names":[],"sources":["../../src/slack/fromSlackBlocks.ts"],"sourcesContent":["import { copyBounded } from \"../convert/copyBounded\";\nimport type { Action, UIElement } from \"../ir\";\nimport {\n ACTIONS_ELEMENT_CAP,\n CARD_ACTIONS_CAP,\n CAROUSEL_CARD_CAP,\n CONTEXT_ELEMENT_CAP,\n DATA_TABLE_COLUMN_CAP,\n DATA_TABLE_ROW_CAP,\n FACT_FIELD_CAP,\n INBOUND_BLOCK_CAP,\n RADIO_OPTION_CAP,\n SELECT_OPTION_CAP,\n} from \"./constants\";\nimport type { FromSlackBlocksResult, SlackConversionWarning } from \"./types\";\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst isDefined = <T>(value: T | undefined): value is T => value !== undefined;\n\nconst asString = (value: unknown): string =>\n typeof value === \"string\" ? value : \"\";\n\nconst textOf = (value: unknown): string =>\n isRecord(value) && typeof value[\"text\"] === \"string\" ? value[\"text\"] : \"\";\n\nconst warn = (\n warnings: SlackConversionWarning[],\n code: SlackConversionWarning[\"code\"],\n component: string,\n detail: string,\n) => {\n warnings.push({ code, component, detail });\n};\n\n/**\n * The shared bounded-iteration primitive: copies `value` to at most `cap`\n * entries without ever reading past that many indices, so a hostile array\n * (sparse or proxied with a fabricated `length`) cannot stall the event loop.\n */\nconst clampArray = (\n value: unknown,\n cap: number,\n): { readonly items: unknown[]; readonly truncated: boolean } => {\n if (!Array.isArray(value)) return { items: [], truncated: false };\n return copyBounded(value, cap);\n};\n\nconst boundedArray = (\n value: unknown,\n cap: number,\n warnings: SlackConversionWarning[],\n component: string,\n detail: string,\n): unknown[] => {\n const { items, truncated } = clampArray(value, cap);\n if (truncated) warn(warnings, \"clamped\", component, detail);\n return items;\n};\n\nconst parseValuePayload = (value: unknown): Record<string, unknown> => {\n if (typeof value !== \"string\") return {};\n try {\n const parsed: unknown = JSON.parse(value);\n return isRecord(parsed) ? parsed : {};\n } catch {\n return {};\n }\n};\n\nconst decodeAction = (actionId: unknown, value: unknown): Action => ({\n ...parseValuePayload(value),\n type: typeof actionId === \"string\" ? actionId : \"action\",\n});\n\nconst headerFrom = (block: Record<string, unknown>): UIElement => ({\n $type: \"Header\",\n text: textOf(block[\"text\"]),\n});\n\nconst imageFrom = (block: Record<string, unknown>): UIElement => ({\n $type: \"Image\",\n src: asString(block[\"image_url\"]),\n alt: asString(block[\"alt_text\"]),\n});\n\nconst markdownFrom = (block: Record<string, unknown>): UIElement => ({\n $type: \"Markdown\",\n value: asString(block[\"text\"]),\n});\n\nconst FACT_FIELD_PATTERN = /^\\*([\\s\\S]*?)\\*\\n([\\s\\S]*)$/;\n\nconst factFrom = (\n field: unknown,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const text = textOf(field);\n const match = FACT_FIELD_PATTERN.exec(text);\n if (match) {\n const [, label, value] = match;\n return { $type: \"Fact\", label, value };\n }\n warn(\n warnings,\n \"fallback\",\n \"Fact\",\n \"A section field that did not match the label/value format was kept as a value-only fact.\",\n );\n return { $type: \"Fact\", label: \"\", value: text };\n};\n\nconst listViewItemFrom = (\n text: string,\n button: Record<string, unknown>,\n): UIElement => ({\n $type: \"ListViewItem\",\n $action: decodeAction(button[\"action_id\"], button[\"value\"]),\n children: { $type: \"Text\", value: text },\n});\n\n/**\n * Recognizes a section block that stands for a `ListViewItem`: mrkdwn text\n * paired with a button accessory, and no `fields` (a fields section is\n * always a Facts section, never a list row).\n */\nconst listViewItemFromSection = (block: unknown): UIElement | undefined => {\n if (!isRecord(block) || block[\"type\"] !== \"section\") return undefined;\n if (Array.isArray(block[\"fields\"])) return undefined;\n const accessory = block[\"accessory\"];\n if (!isRecord(accessory) || accessory[\"type\"] !== \"button\") {\n return undefined;\n }\n return listViewItemFrom(textOf(block[\"text\"]), accessory);\n};\n\nconst isDividerBlock = (block: unknown): boolean =>\n isRecord(block) && block[\"type\"] === \"divider\";\n\nconst sectionFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement[] => {\n const fields = block[\"fields\"];\n if (Array.isArray(fields)) {\n return boundedArray(\n fields,\n FACT_FIELD_CAP,\n warnings,\n \"Fact\",\n `fields were clamped to ${FACT_FIELD_CAP} entries.`,\n ).map((field) => factFrom(field, warnings));\n }\n return [{ $type: \"Text\", value: textOf(block[\"text\"]) }];\n};\n\nconst captionFrom = (element: unknown): UIElement => ({\n $type: \"Caption\",\n value: textOf(element),\n});\n\nconst contextFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const elements = boundedArray(\n block[\"elements\"],\n CONTEXT_ELEMENT_CAP,\n warnings,\n \"Row\",\n `elements were clamped to ${CONTEXT_ELEMENT_CAP} entries.`,\n );\n if (elements.length > 1) {\n return { $type: \"Row\", children: elements.map(captionFrom) };\n }\n return {\n $type: \"Caption\",\n value: elements.length === 1 ? textOf(elements[0]) : \"\",\n };\n};\n\nconst optionFrom = (\n option: unknown,\n): { label: string; value: string } | undefined => {\n if (!isRecord(option) || typeof option[\"value\"] !== \"string\") {\n return undefined;\n }\n return { label: textOf(option[\"text\"]), value: option[\"value\"] };\n};\n\nconst buttonFrom = (element: Record<string, unknown>): UIElement => {\n const style = element[\"style\"];\n return {\n $type: \"Button\",\n label: textOf(element[\"text\"]),\n ...(style === \"primary\" || style === \"danger\"\n ? { buttonStyle: style }\n : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n };\n};\n\nconst selectFrom = (\n element: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const options = boundedArray(\n element[\"options\"],\n SELECT_OPTION_CAP,\n warnings,\n \"Select\",\n `options were clamped to ${SELECT_OPTION_CAP} entries.`,\n );\n const placeholder = element[\"placeholder\"];\n return {\n $type: \"Select\",\n options: options.map(optionFrom).filter(isDefined),\n ...(isRecord(placeholder) ? { placeholder: textOf(placeholder) } : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n };\n};\n\nconst datePickerFrom = (element: Record<string, unknown>): UIElement => ({\n $type: \"DatePicker\",\n ...(typeof element[\"initial_date\"] === \"string\"\n ? { value: element[\"initial_date\"] }\n : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n});\n\nconst checkboxFrom = (element: Record<string, unknown>): UIElement => {\n const options = Array.isArray(element[\"options\"]) ? element[\"options\"] : [];\n const first = options[0];\n const initialOptions = Array.isArray(element[\"initial_options\"])\n ? element[\"initial_options\"]\n : [];\n const firstValue =\n isRecord(first) && typeof first[\"value\"] === \"string\" ? first[\"value\"] : \"\";\n const firstChecked = copyBounded(\n initialOptions,\n SELECT_OPTION_CAP,\n ).items.some((option) => isRecord(option) && option[\"value\"] === firstValue);\n return {\n $type: \"Checkbox\",\n label: isRecord(first) ? textOf(first[\"text\"]) : \"\",\n name: firstValue,\n ...(firstChecked ? { defaultChecked: true } : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n };\n};\n\nconst radioGroupFrom = (\n element: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const options = boundedArray(\n element[\"options\"],\n RADIO_OPTION_CAP,\n warnings,\n \"RadioGroup\",\n `options were clamped to ${RADIO_OPTION_CAP} entries.`,\n );\n const initialOption = element[\"initial_option\"];\n const initialValue =\n isRecord(initialOption) && typeof initialOption[\"value\"] === \"string\"\n ? initialOption[\"value\"]\n : undefined;\n return {\n $type: \"RadioGroup\",\n options: options.map(optionFrom).filter(isDefined),\n ...(initialValue !== undefined ? { defaultValue: initialValue } : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n };\n};\n\nconst actionElementFrom = (\n element: unknown,\n warnings: SlackConversionWarning[],\n): UIElement | undefined => {\n if (isRecord(element)) {\n switch (element[\"type\"]) {\n case \"button\":\n return buttonFrom(element);\n case \"static_select\":\n return selectFrom(element, warnings);\n case \"datepicker\":\n return datePickerFrom(element);\n case \"checkboxes\":\n return checkboxFrom(element);\n case \"radio_buttons\":\n return radioGroupFrom(element, warnings);\n }\n }\n warn(\n warnings,\n \"dropped\",\n isRecord(element) && typeof element[\"type\"] === \"string\"\n ? element[\"type\"]\n : \"unknown\",\n \"Unknown action element type was dropped.\",\n );\n return undefined;\n};\n\nconst actionsFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement[] => {\n const elements = boundedArray(\n block[\"elements\"],\n ACTIONS_ELEMENT_CAP,\n warnings,\n \"Actions\",\n `elements were clamped to ${ACTIONS_ELEMENT_CAP} entries.`,\n );\n return elements\n .map((element) => actionElementFrom(element, warnings))\n .filter(isDefined);\n};\n\nconst inputFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement[] => {\n const element = block[\"element\"];\n if (!isRecord(element) || element[\"type\"] !== \"plain_text_input\") {\n warn(\n warnings,\n \"dropped\",\n \"Input\",\n \"Unknown input element type was dropped.\",\n );\n return [];\n }\n const placeholder = element[\"placeholder\"];\n return [\n {\n $type: \"Input\",\n label: textOf(block[\"label\"]),\n ...(isRecord(placeholder) ? { placeholder: textOf(placeholder) } : {}),\n ...(element[\"multiline\"] === true ? { multiline: true } : {}),\n $action: decodeAction(element[\"action_id\"], undefined),\n },\n ];\n};\n\ntype CardFooterButton = { readonly label: string; readonly $action: Action };\n\nconst cardFooterButtonFrom = (button: unknown): CardFooterButton | undefined =>\n isRecord(button)\n ? {\n label: textOf(button[\"text\"]),\n $action: decodeAction(button[\"action_id\"], button[\"value\"]),\n }\n : undefined;\n\n/**\n * Assigns a card's footer actions by style rather than position: the\n * primary-styled button is the confirm action and a non-primary button is\n * cancel, since that is the pairing {@link toSlackBlocks} itself emits.\n * Position is only a fallback for input that does not carry that pairing\n * (zero or multiple primary-styled buttons among two or more actions).\n */\nconst assignCardFooterActions = (\n actions: unknown[],\n warnings: SlackConversionWarning[],\n): {\n readonly confirm: CardFooterButton | undefined;\n readonly cancel: CardFooterButton | undefined;\n readonly leftover: unknown[];\n} => {\n const isPrimaryButton = (action: unknown): boolean =>\n isRecord(action) && action[\"style\"] === \"primary\";\n const primaryButtons = actions.filter(isPrimaryButton);\n\n let confirmSource: unknown = undefined;\n let cancelSource: unknown = undefined;\n let leftover: unknown[] = [];\n\n if (primaryButtons.length === 1) {\n const [confirmButton] = primaryButtons;\n const [cancelButton, ...rest] = actions.filter(\n (action) => !isPrimaryButton(action),\n );\n confirmSource = confirmButton;\n cancelSource = cancelButton;\n leftover = rest;\n } else if (primaryButtons.length === 0 && actions.length <= 1) {\n const [onlyButton] = actions;\n cancelSource = onlyButton;\n } else if (actions.length > 0) {\n const [first, second, ...rest] = actions;\n confirmSource = first;\n cancelSource = second;\n leftover = rest;\n warn(\n warnings,\n \"fallback\",\n \"Card\",\n \"Confirm and cancel buttons could not be assigned by style and fell back to position.\",\n );\n }\n\n return {\n confirm: cardFooterButtonFrom(confirmSource),\n cancel: cardFooterButtonFrom(cancelSource),\n leftover,\n };\n};\n\nconst cardFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const titleText = textOf(block[\"title\"]);\n const children: UIElement[] = [];\n const heroImage = block[\"hero_image\"];\n if (isRecord(heroImage)) {\n children.push({\n $type: \"Image\",\n src: asString(heroImage[\"image_url\"]),\n alt: asString(heroImage[\"alt_text\"]),\n });\n }\n const body = block[\"body\"];\n if (isRecord(body)) {\n children.push({ $type: \"Text\", value: textOf(body) });\n }\n const subtext = block[\"subtext\"];\n if (isRecord(subtext)) {\n children.push({ $type: \"Caption\", value: textOf(subtext) });\n }\n const actions = boundedArray(\n block[\"actions\"],\n CARD_ACTIONS_CAP,\n warnings,\n \"Card\",\n `actions were clamped to ${CARD_ACTIONS_CAP} entries.`,\n );\n const { confirm, cancel, leftover } = assignCardFooterActions(\n actions,\n warnings,\n );\n if (leftover.length > 0) {\n for (const extra of leftover) {\n if (isRecord(extra)) children.push(buttonFrom(extra));\n }\n warn(\n warnings,\n \"fallback\",\n \"Card\",\n \"Card actions beyond confirm and cancel were appended as Button children.\",\n );\n }\n return {\n $type: \"Card\",\n ...(titleText ? { title: titleText } : {}),\n ...(children.length > 0 ? { children } : {}),\n ...(confirm !== undefined ? { confirm } : {}),\n ...(cancel !== undefined ? { cancel } : {}),\n };\n};\n\nconst carouselFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const elements = boundedArray(\n block[\"elements\"],\n CAROUSEL_CARD_CAP,\n warnings,\n \"Carousel\",\n `cards were clamped to ${CAROUSEL_CARD_CAP} entries.`,\n );\n const cards = elements\n .filter(isRecord)\n .map((card) => cardFrom(card, warnings));\n return { $type: \"Carousel\", children: cards };\n};\n\nconst ALERT_LEVEL_TONES: Record<string, string> = {\n error: \"danger\",\n success: \"success\",\n warning: \"warning\",\n info: \"info\",\n};\n\nconst alertFrom = (block: Record<string, unknown>): UIElement => {\n const level = block[\"level\"];\n const tone = typeof level === \"string\" ? ALERT_LEVEL_TONES[level] : undefined;\n return {\n $type: \"Alert\",\n description: textOf(block[\"text\"]),\n ...(tone !== undefined ? { tone } : {}),\n };\n};\n\nconst dataTableCellValue = (cell: unknown): string | number => {\n if (!isRecord(cell)) return \"\";\n const text = asString(cell[\"text\"]);\n if (cell[\"type\"] === \"raw_number\") {\n const parsed = Number(text);\n if (Number.isFinite(parsed)) return parsed;\n }\n return text;\n};\n\nconst dataTableFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const { items: rows, truncated: rowsTruncated } = clampArray(\n block[\"rows\"],\n DATA_TABLE_ROW_CAP + 1,\n );\n if (rowsTruncated) {\n warn(\n warnings,\n \"clamped\",\n \"Table\",\n `rows were clamped to ${DATA_TABLE_ROW_CAP} entries.`,\n );\n }\n const [headerRow, ...dataRows] = rows;\n const { items: headerCells, truncated: headerTruncated } = clampArray(\n headerRow,\n DATA_TABLE_COLUMN_CAP,\n );\n let columnsTruncated = headerTruncated;\n const normalizedRows = dataRows.map((row) => {\n const { items: cells, truncated } = clampArray(row, DATA_TABLE_COLUMN_CAP);\n if (truncated) columnsTruncated = true;\n return cells;\n });\n if (columnsTruncated) {\n warn(\n warnings,\n \"clamped\",\n \"Table\",\n `columns were clamped to ${DATA_TABLE_COLUMN_CAP} entries.`,\n );\n }\n const headerAllEmpty = headerCells.every(\n (cell) => !isRecord(cell) || asString(cell[\"text\"]) === \"\",\n );\n const columns = headerCells.map((cell) => ({\n label: isRecord(cell) ? asString(cell[\"text\"]) : \"\",\n }));\n return {\n $type: \"Table\",\n ...(headerAllEmpty ? {} : { columns }),\n rows: normalizedRows.map((row) => row.map(dataTableCellValue)),\n };\n};\n\nfunction convertBlock(\n block: unknown,\n warnings: SlackConversionWarning[],\n): UIElement[] {\n if (isRecord(block)) {\n switch (block[\"type\"]) {\n case \"header\":\n return [headerFrom(block)];\n case \"section\":\n return sectionFrom(block, warnings);\n case \"context\":\n return [contextFrom(block, warnings)];\n case \"image\":\n return [imageFrom(block)];\n case \"divider\":\n return [{ $type: \"Divider\" }];\n case \"actions\":\n return actionsFrom(block, warnings);\n case \"input\":\n return inputFrom(block, warnings);\n case \"markdown\":\n return [markdownFrom(block)];\n case \"card\":\n return [cardFrom(block, warnings)];\n case \"carousel\":\n return [carouselFrom(block, warnings)];\n case \"alert\":\n return [alertFrom(block)];\n case \"data_table\":\n return [dataTableFrom(block, warnings)];\n }\n }\n warn(\n warnings,\n \"dropped\",\n isRecord(block) && typeof block[\"type\"] === \"string\"\n ? block[\"type\"]\n : \"unknown\",\n \"Unknown block type was dropped.\",\n );\n return [];\n}\n\nconst extractBlockList = (input: unknown): unknown[] | undefined => {\n if (Array.isArray(input)) return input;\n if (isRecord(input) && Array.isArray(input[\"blocks\"])) return input[\"blocks\"];\n return undefined;\n};\n\n/**\n * Walks the (already bounded) block list, folding a run of one or more\n * consecutive `ListViewItem`-shaped section blocks, optionally separated by\n * single divider blocks, into one `ListView` container instead of emitting\n * root-level `ListViewItem` nodes or the interleaved dividers. A divider that\n * is not directly between two such items is left for {@link convertBlock} to\n * decode as a literal `Divider`.\n */\nfunction convertBlockList(\n blocks: unknown[],\n warnings: SlackConversionWarning[],\n): UIElement[] {\n const nodes: UIElement[] = [];\n let index = 0;\n while (index < blocks.length) {\n const firstItem = listViewItemFromSection(blocks[index]);\n if (firstItem === undefined) {\n nodes.push(...convertBlock(blocks[index], warnings));\n index += 1;\n continue;\n }\n const items: UIElement[] = [firstItem];\n let cursor = index + 1;\n while (true) {\n if (isDividerBlock(blocks[cursor])) {\n const candidate = listViewItemFromSection(blocks[cursor + 1]);\n if (candidate === undefined) break;\n items.push(candidate);\n cursor += 2;\n continue;\n }\n const candidate = listViewItemFromSection(blocks[cursor]);\n if (candidate === undefined) break;\n items.push(candidate);\n cursor += 1;\n }\n nodes.push({ $type: \"ListView\", children: items });\n index = cursor;\n }\n return nodes;\n}\n\n/**\n * Converts Slack Block Kit JSON back into a generative-ui IR tree, inverting\n * {@link toSlackBlocks}. Accepts a blocks array or a `{ blocks }` wrapper;\n * malformed input resolves to an empty node list plus a warning, and an\n * unrecognized block or action element is skipped with a \"dropped\" warning.\n * Every array read along the way (blocks, fields, options, elements, cards,\n * rows, cells) is bounded to its site's cap before it is mapped over, so a\n * hostile array (sparse or proxied with a fabricated `length`) is clamped\n * with a \"clamped\" warning instead of stalling the event loop. Never throws.\n *\n * The Slack shape is lossier than the IR, so some information cannot be\n * recovered: a context block's elements always decode as `Caption` (the\n * original `Badge` vs `Caption` distinction does not survive the round trip),\n * a button's `buttonStyle` is only recovered for the `primary`/`danger` Slack\n * styles, a native alert block's `title` and `description` merge into a\n * single `description` string, and a card's layout collapses to its `title`,\n * a flat `children` list (image, then text, then caption, in that order),\n * and `confirm`/`cancel` footer buttons. A section field's label and value\n * are split on the first `*`+newline delimiter, so a label or value that\n * itself contains that exact delimiter cannot be split back unambiguously.\n * A data-table cell that Slack carries as `raw_text` always decodes as a\n * string, even when its text is the stringified form of a boolean, since the\n * Slack shape does not distinguish a boolean from ordinary text.\n */\nexport function fromSlackBlocks(blocks: unknown): FromSlackBlocksResult {\n const warnings: SlackConversionWarning[] = [];\n try {\n const list = extractBlockList(blocks);\n if (list === undefined) {\n return {\n nodes: [],\n warnings: [\n {\n code: \"dropped\",\n component: \"Root\",\n detail: \"Input could not be converted.\",\n },\n ],\n };\n }\n const boundedList = boundedArray(\n list,\n INBOUND_BLOCK_CAP,\n warnings,\n \"Root\",\n `blocks were clamped to ${INBOUND_BLOCK_CAP} entries.`,\n );\n const nodes = convertBlockList(boundedList, warnings);\n return { nodes, warnings };\n } catch {\n return {\n nodes: [],\n warnings: [\n {\n code: \"dropped\",\n component: \"Root\",\n detail: \"Input could not be converted.\",\n },\n ],\n };\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toSlackBlocks.d.ts","names":[],"sources":["../../src/slack/toSlackBlocks.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"toSlackBlocks.d.ts","names":[],"sources":["../../src/slack/toSlackBlocks.ts"],"mappings":";;;iBAqzCgB,cACd,eACA,UAAU,uBACT"}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { normalizeSpec } from "../ir.js";
|
|
2
|
-
import {
|
|
2
|
+
import { copyBounded } from "../convert/copyBounded.js";
|
|
3
|
+
import { CONTEXT_TEXT_CAP, DATA_TABLE_CHAR_BUDGET, FACT_FIELD_TEXT_CAP, INPUT_LABEL_CAP, MARKDOWN_TEXT_BUDGET, SECTION_TEXT_CAP, buildAlertBlock, buildAlertFallback, buildBlockBudgetNote, buildCardBlock, buildCarouselBlock, buildDataTableBlock } from "./constants.js";
|
|
4
|
+
import { boundSpec, clampReasonDetail } from "../convert/boundSpec.js";
|
|
5
|
+
import { isElement } from "../convert/isElement.js";
|
|
6
|
+
import { takeRun } from "../convert/takeRun.js";
|
|
3
7
|
//#region src/slack/toSlackBlocks.ts
|
|
4
8
|
const INTERACTIVE_TYPES = /* @__PURE__ */ new Set([
|
|
5
9
|
"Button",
|
|
@@ -21,7 +25,6 @@ const CONTROL_TYPES = /* @__PURE__ */ new Set([
|
|
|
21
25
|
]);
|
|
22
26
|
const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
23
27
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24
|
-
const isElement = (node) => isRecord(node);
|
|
25
28
|
const asString = (value) => typeof value === "string" ? value : "";
|
|
26
29
|
const warn = (context, code, component, detail) => {
|
|
27
30
|
context.warnings.push({
|
|
@@ -84,8 +87,8 @@ const toActionElement = (element, context) => {
|
|
|
84
87
|
case "Button": return buttonElement(asString(props["label"]), props["buttonStyle"], action, "Button", context);
|
|
85
88
|
case "Select": {
|
|
86
89
|
const rawOptions = Array.isArray(props["options"]) ? props["options"] : [];
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
const { items: takenOptions, truncated } = copyBounded(rawOptions, 100);
|
|
91
|
+
if (truncated) warn(context, "clamped", "Select", `options were clamped to 100 entries.`);
|
|
89
92
|
const options = takenOptions.map((option) => optionFrom(option, "Select", context)).filter((option) => option !== void 0);
|
|
90
93
|
warnDroppedOptions(options.length, takenOptions.length, "Select", context);
|
|
91
94
|
const placeholder = clampText(asString(props["placeholder"]), 150, "Select", "placeholder", context);
|
|
@@ -122,8 +125,8 @@ const toActionElement = (element, context) => {
|
|
|
122
125
|
}
|
|
123
126
|
case "RadioGroup": {
|
|
124
127
|
const rawOptions = Array.isArray(props["options"]) ? props["options"] : [];
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
const { items: takenOptions, truncated } = copyBounded(rawOptions, 10);
|
|
129
|
+
if (truncated) warn(context, "clamped", "RadioGroup", `options were clamped to 10 entries.`);
|
|
127
130
|
const options = takenOptions.map((option) => optionFrom(option, "RadioGroup", context)).filter((option) => option !== void 0);
|
|
128
131
|
warnDroppedOptions(options.length, takenOptions.length, "RadioGroup", context);
|
|
129
132
|
const selectedValue = typeof props["value"] === "string" ? props["value"] : asString(props["defaultValue"]);
|
|
@@ -244,7 +247,8 @@ const assembleCleanCard = (titleText, fields, buttons, context) => {
|
|
|
244
247
|
type: "mrkdwn",
|
|
245
248
|
text: clampText(fields.subtextRaw, 200, "Card", "subtext", context)
|
|
246
249
|
} : void 0;
|
|
247
|
-
|
|
250
|
+
const { items: boundedButtons, truncated } = copyBounded(buttons, 3);
|
|
251
|
+
if (truncated) warn(context, "clamped", "Card", `actions were clamped to 3 entries.`);
|
|
248
252
|
return buildCardBlock({
|
|
249
253
|
...fields.heroImage !== void 0 ? { heroImage: fields.heroImage } : {},
|
|
250
254
|
...titleText !== void 0 ? { title: {
|
|
@@ -253,7 +257,7 @@ const assembleCleanCard = (titleText, fields, buttons, context) => {
|
|
|
253
257
|
} } : {},
|
|
254
258
|
...body !== void 0 ? { body } : {},
|
|
255
259
|
...subtext !== void 0 ? { subtext } : {},
|
|
256
|
-
...
|
|
260
|
+
...boundedButtons.length > 0 ? { actions: boundedButtons } : {}
|
|
257
261
|
});
|
|
258
262
|
};
|
|
259
263
|
/**
|
|
@@ -362,10 +366,11 @@ const contextRow = (element, context) => {
|
|
|
362
366
|
const allChildren = normalizedList(element.children);
|
|
363
367
|
const children = allChildren.filter(isBadgeOrCaption);
|
|
364
368
|
if (allChildren.length === 0 || children.length !== allChildren.length) return;
|
|
365
|
-
|
|
369
|
+
const { items: boundedChildren, truncated } = copyBounded(children, 10);
|
|
370
|
+
if (truncated) warn(context, "clamped", "Row", `context elements were clamped to 10 entries.`);
|
|
366
371
|
return [{
|
|
367
372
|
type: "context",
|
|
368
|
-
elements:
|
|
373
|
+
elements: boundedChildren.map((child) => ({
|
|
369
374
|
type: "mrkdwn",
|
|
370
375
|
text: clampText(asString(child.props["value"]), CONTEXT_TEXT_CAP, child.type, "value", context)
|
|
371
376
|
}))
|
|
@@ -424,8 +429,9 @@ const convertCarousel = (element, context, depth) => {
|
|
|
424
429
|
if (discardedChild(child, context, depth + 1)) droppedCards += 1;
|
|
425
430
|
}
|
|
426
431
|
if (droppedCards > 0) warn(context, "dropped", "Carousel", `${droppedCards} non-card ${droppedCards === 1 ? "child was" : "children were"} dropped.`);
|
|
427
|
-
|
|
428
|
-
|
|
432
|
+
const { items: boundedCards, truncated } = copyBounded(cardChildren, 10);
|
|
433
|
+
if (truncated) warn(context, "clamped", "Carousel", `cards were clamped to 10 entries.`);
|
|
434
|
+
const cards = boundedCards.map((card) => convertCarouselCard(card, context, depth + 1)).filter((card) => card !== void 0);
|
|
429
435
|
if (cards.length < 1) {
|
|
430
436
|
warn(context, "dropped", "Carousel", "A carousel without renderable cards was dropped.");
|
|
431
437
|
return [];
|
|
@@ -446,16 +452,17 @@ const rowCharacters = (row) => row.reduce((sum, cell) => sum + cell.text.length,
|
|
|
446
452
|
const convertTable = (element, context) => {
|
|
447
453
|
const rawColumns = Array.isArray(element.props["columns"]) ? element.props["columns"] : [];
|
|
448
454
|
const rawRows = Array.isArray(element.props["rows"]) ? element.props["rows"] : [];
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
455
|
+
const { items: takenColumns, truncated: columnsTruncated } = copyBounded(rawColumns, 20);
|
|
456
|
+
const { items: takenRows, truncated: rowsTruncated } = copyBounded(rawRows, 200);
|
|
457
|
+
if (columnsTruncated) warn(context, "clamped", "Table", `columns were clamped to 20 entries.`);
|
|
458
|
+
if (rowsTruncated) warn(context, "clamped", "Table", `rows were clamped to 200 entries.`);
|
|
452
459
|
const unlabeled = takenColumns.filter((column) => !isRecord(column) || typeof column["label"] !== "string").length;
|
|
453
460
|
if (unlabeled > 0) warn(context, "dropped", "Table", `${unlabeled} column ${unlabeled === 1 ? "header was" : "headers were"} left blank for want of a string label.`);
|
|
454
461
|
const columnHeaderRow = takenColumns.map((column) => ({
|
|
455
462
|
type: "raw_text",
|
|
456
463
|
text: isRecord(column) ? asString(column["label"]) : ""
|
|
457
464
|
}));
|
|
458
|
-
const dataRows =
|
|
465
|
+
const dataRows = takenRows.map((row) => (Array.isArray(row) ? copyBounded(row, 20).items : []).map((value) => toDataTableCell(value) ?? {
|
|
459
466
|
type: "raw_text",
|
|
460
467
|
text: ""
|
|
461
468
|
}));
|
|
@@ -603,24 +610,14 @@ function convertSequence(node, context, depth) {
|
|
|
603
610
|
continue;
|
|
604
611
|
}
|
|
605
612
|
if (isElement(current) && current.type === "Fact") {
|
|
606
|
-
const facts =
|
|
607
|
-
|
|
608
|
-
const candidate = nodes[index];
|
|
609
|
-
if (!candidate || !isElement(candidate) || candidate.type !== "Fact") break;
|
|
610
|
-
facts.push(candidate);
|
|
611
|
-
index += 1;
|
|
612
|
-
}
|
|
613
|
+
const { run: facts, next } = takeRun(nodes, index, (candidate) => candidate.type === "Fact");
|
|
614
|
+
index = next;
|
|
613
615
|
blocks.push(...convertFacts(facts, context));
|
|
614
616
|
continue;
|
|
615
617
|
}
|
|
616
618
|
if (isElement(current) && INTERACTIVE_TYPES.has(current.type)) {
|
|
617
|
-
const controls =
|
|
618
|
-
|
|
619
|
-
const candidate = nodes[index];
|
|
620
|
-
if (!candidate || !isElement(candidate) || !INTERACTIVE_TYPES.has(candidate.type)) break;
|
|
621
|
-
controls.push(candidate);
|
|
622
|
-
index += 1;
|
|
623
|
-
}
|
|
619
|
+
const { run: controls, next } = takeRun(nodes, index, (candidate) => INTERACTIVE_TYPES.has(candidate.type));
|
|
620
|
+
index = next;
|
|
624
621
|
blocks.push(...convertActions(controls, context));
|
|
625
622
|
continue;
|
|
626
623
|
}
|
|
@@ -637,67 +634,6 @@ function convertSequence(node, context, depth) {
|
|
|
637
634
|
}
|
|
638
635
|
return blocks;
|
|
639
636
|
}
|
|
640
|
-
function boundNode(value, depth, onClamp, state, ancestors) {
|
|
641
|
-
if (state.remaining <= 0) {
|
|
642
|
-
if (!state.exhausted) {
|
|
643
|
-
state.exhausted = true;
|
|
644
|
-
onClamp("budget");
|
|
645
|
-
}
|
|
646
|
-
return null;
|
|
647
|
-
}
|
|
648
|
-
state.remaining -= 1;
|
|
649
|
-
if (depth > 64) {
|
|
650
|
-
onClamp("depth");
|
|
651
|
-
return null;
|
|
652
|
-
}
|
|
653
|
-
if (Array.isArray(value)) {
|
|
654
|
-
if (ancestors.has(value)) {
|
|
655
|
-
onClamp("cycle");
|
|
656
|
-
return null;
|
|
657
|
-
}
|
|
658
|
-
ancestors.add(value);
|
|
659
|
-
const bounded = Array.prototype.slice.call(value, 0, 200);
|
|
660
|
-
if (value.length > 200) onClamp("children");
|
|
661
|
-
const result = bounded.map((item) => boundNode(item, depth + 1, onClamp, state, ancestors));
|
|
662
|
-
ancestors.delete(value);
|
|
663
|
-
return result;
|
|
664
|
-
}
|
|
665
|
-
if (value !== null && typeof value === "object" && "children" in value) {
|
|
666
|
-
if (ancestors.has(value)) {
|
|
667
|
-
onClamp("cycle");
|
|
668
|
-
return null;
|
|
669
|
-
}
|
|
670
|
-
ancestors.add(value);
|
|
671
|
-
const record = value;
|
|
672
|
-
const result = {
|
|
673
|
-
...record,
|
|
674
|
-
children: boundNode(record["children"], depth + 1, onClamp, state, ancestors)
|
|
675
|
-
};
|
|
676
|
-
ancestors.delete(value);
|
|
677
|
-
return result;
|
|
678
|
-
}
|
|
679
|
-
return value;
|
|
680
|
-
}
|
|
681
|
-
/**
|
|
682
|
-
* Produces a bounded plain copy of a raw generative-ui spec before it
|
|
683
|
-
* reaches `normalizeSpec`, whose own traversal of the root array or any
|
|
684
|
-
* `children` array walks the full reported length of a hostile proxied
|
|
685
|
-
* array before any per-field cap downstream ever applies. Every array
|
|
686
|
-
* (root, or `children` at any depth) is capped to {@link CHILDREN_CAP}
|
|
687
|
-
* entries via `Array.prototype.slice`, which bounds even a proxy with a
|
|
688
|
-
* fabricated `length`; recursion itself is capped at
|
|
689
|
-
* {@link MAX_TRAVERSAL_DEPTH}. `onClamp` fires once per level that was
|
|
690
|
-
* truncated, receiving the reason for that truncation: `"children"`,
|
|
691
|
-
* `"depth"`, `"budget"`, or `"cycle"`. The walk also spends a total budget of
|
|
692
|
-
* {@link NODE_BUDGET} nodes, so shared references cannot multiply work
|
|
693
|
-
* exponentially, and a node that is its own ancestor is cut to `null`.
|
|
694
|
-
*/
|
|
695
|
-
function boundSpec(spec, onClamp) {
|
|
696
|
-
return boundNode(spec, 0, onClamp, {
|
|
697
|
-
remaining: NODE_BUDGET,
|
|
698
|
-
exhausted: false
|
|
699
|
-
}, /* @__PURE__ */ new WeakSet());
|
|
700
|
-
}
|
|
701
637
|
/** Converts a generative-UI tree into Slack Block Kit JSON and downgrade warnings. */
|
|
702
638
|
function toSlackBlocks(node, options) {
|
|
703
639
|
const surface = options?.surface === "modal" ? "modal" : "message";
|
|
@@ -710,10 +646,7 @@ function toSlackBlocks(node, options) {
|
|
|
710
646
|
dataTableCharacters: 0
|
|
711
647
|
};
|
|
712
648
|
try {
|
|
713
|
-
const bounded = boundSpec(node, (reason) =>
|
|
714
|
-
const detail = reason === "budget" ? `the tree was truncated after ${NODE_BUDGET} nodes.` : reason === "cycle" ? "a self-referencing node was dropped." : reason === "depth" ? `nodes deeper than 32 levels were dropped.` : `children were clamped to 200 entries.`;
|
|
715
|
-
warn(context, "clamped", "Root", detail);
|
|
716
|
-
});
|
|
649
|
+
const bounded = boundSpec(node, (reason) => warn(context, "clamped", "Root", clampReasonDetail(reason)));
|
|
717
650
|
const { root } = normalizeSpec(bounded);
|
|
718
651
|
const converted = convertSequence(root, context, 0);
|
|
719
652
|
if (converted.length <= context.blockCap) return {
|