Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240import type { WebClient } from "@slack/web-api";
interface SchemaField { action_id: string; label: string; type: string; // "plain_text_input" | "file_input" | "multi_static_select" | "static_select" | "datepicker" | "url_text_input" | "email_text_input" | "number_input" placeholder?: string; initial_value?: string; optional?: boolean; multiline?: boolean; min_length?: number; max_length?: number; options?: { label: string; value: string }[];}
export interface MetadataSchema { title: string; submit_label?: string; fields: SchemaField[];}
function buildBlocks( schema: MetadataSchema, initialMetadata: Record<string, any> = {},): any[] { const blocks: any[] = [ { type: "header", text: { type: "plain_text", text: schema.title, emoji: true }, }, { type: "divider" }, ];
for (const field of schema.fields) { const element: any = { type: field.type, action_id: field.action_id }; const initialValue = initialMetadata[field.action_id] ?? field.initial_value;
switch (field.type) { case "plain_text_input": case "url_text_input": case "email_text_input": element.placeholder = field.placeholder ? { type: "plain_text", text: field.placeholder } : undefined; element.initial_value = initialValue; element.multiline = field.multiline; element.min_length = field.min_length; element.max_length = field.max_length; break;
case "number_input": element.placeholder = field.placeholder ? { type: "plain_text", text: field.placeholder } : undefined; element.initial_value = initialValue; element.min_length = field.min_length; element.max_length = field.max_length; element.is_decimal_allowed = false; break;
case "static_select": case "multi_static_select": element.placeholder = field.placeholder ? { type: "plain_text", text: field.placeholder } : undefined; element.options = (field.options || []).map((o) => ({ text: { type: "plain_text", text: o.label }, value: o.value, })); if (field.type === "static_select" && initialValue) { const option = (field.options || []).find( (o) => o.value === initialValue, ); if (option) { element.initial_option = { text: { type: "plain_text", text: option.label }, value: option.value, }; } } if (field.type === "multi_static_select" && Array.isArray(initialValue)) { element.initial_options = (field.options || []) .filter((o) => initialValue.includes(o.value)) .map((o) => ({ text: { type: "plain_text", text: o.label }, value: o.value, })); } break;
case "datepicker": element.placeholder = field.placeholder ? { type: "plain_text", text: field.placeholder } : undefined; element.initial_date = initialValue; break;
case "file_input": // file_input requires specific setup in Slack — use as-is break; }
blocks.push({ type: "input", block_id: `field_${field.action_id}`, optional: field.optional !== false, label: { type: "plain_text", text: field.label, emoji: true }, element, }); }
return blocks;}
function extractMetadata( schema: MetadataSchema, state: Record< string, Record< string, { value?: string; selected_options?: { value: string }[]; selected_date?: string; files?: any[]; } > >,): Record<string, any> { const metadata: Record<string, any> = {}; for (const field of schema.fields) { const values = state?.[`field_${field.action_id}`]?.[field.action_id]; if (!values) continue;
switch (field.type) { case "multi_static_select": if (values.selected_options?.length) { metadata[field.action_id] = values.selected_options.map( (o) => o.value, ); } break; case "datepicker": if (values.selected_date) { metadata[field.action_id] = values.selected_date; } break; case "file_input": if (values.files?.length) metadata[field.action_id] = values.files; break; default: if (values.value) metadata[field.action_id] = values.value; } } return metadata;}
export async function openMetadataModal( client: WebClient, triggerId: string, channelId: string, messageTs: string, schema: MetadataSchema, botMessageTs?: string, apiOnly?: boolean, initialMetadata: Record<string, any> = {},) { const blocks = buildBlocks(schema, initialMetadata);
await client.views.open({ trigger_id: triggerId, view: { type: "modal", callback_id: "metadata_modal", title: { type: "plain_text", text: schema.title, emoji: true }, submit: { type: "plain_text", text: schema.submit_label || "Save to Feed", emoji: true, }, close: { type: "plain_text", text: "Cancel", emoji: true }, private_metadata: JSON.stringify({ channelId, messageTs, botMessageTs, apiOnly: apiOnly || false }), notify_on_close: true, blocks, } as any, });}
export async function openBotMessageEditModal( client: WebClient, triggerId: string, channelId: string, messageTs: string, initialText: string,) { await client.views.open({ trigger_id: triggerId, view: { type: "modal", callback_id: "edit_bot_message_modal", title: { type: "plain_text", text: "Edit bot message", emoji: true }, submit: { type: "plain_text", text: "Save", emoji: true }, close: { type: "plain_text", text: "Cancel", emoji: true }, private_metadata: JSON.stringify({ channelId, messageTs }), blocks: [ { type: "input", block_id: "edit_message", label: { type: "plain_text", text: "Message text", emoji: true }, element: { type: "plain_text_input", action_id: "edit_message", multiline: true, initial_value: initialText.slice(0, 3000), }, }, ], } as any, });}
export function extractMetadataFromView( schema: MetadataSchema, state: Record< string, Record< string, { value?: string; selected_options?: { value: string }[]; selected_date?: string; files?: any[]; } > >,): string { const metadata = extractMetadata(schema, state); return JSON.stringify(metadata);}