-
Notifications
You must be signed in to change notification settings - Fork 0
Message shortcuts with multi-bot support and modal validation #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
+1,505
−335
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
e67f0da
feat(06-01): add icon fields to SlackAppConfig and getBotForShortcut …
tyom 04178e6
feat(06-01): extend getConnectedBots API and make dispatchShortcut ta…
tyom dd5ccc5
feat(06-02): extend types and add shortcut aggregation for multi-bot …
tyom f5231af
feat(06-02): restructure context menu with Connect to apps submenu
tyom c72b7c4
feat: add showcase message shortcut and fix shortcut payload type
tyom 00f64c7
fix: prevent Electron drag region from blocking image preview close b…
tyom 919d297
feat: add Connect to apps submenu to three-dot message menu
tyom b893988
fix: unify file image rendering with ImageBlock and fix shortcut imag…
tyom 4c23344
fix: hide image collapse toggle in modals to match real Slack
tyom 6c56a0e
fix: replace required field asterisk with validation error on submit …
tyom cb80e07
fix: add red border on validation error and live revalidation after f…
tyom 84db29c
fix: match real Slack modal width (520px)
tyom d393936
Format the code
tyom b5c128f
feat: display bot icon from registration config
tyom 5750963
feat: group sequential messages by same author
tyom 983619c
Format code
tyom 99f0ccd
fix: address review findings across shortcuts and UI
tyom 4a0e6bd
fix: support bare domain links in mrkdwn and use compact line breaks …
tyom 542f30a
fix: use alias for app module imports in components
tyom f06a252
fix: render bot icon as image in shortcut menus when it's a URL
tyom 277fe99
Format code
tyom c8f3994
fix: address review findings across JSON templates, imports, and mrkd…
tyom File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,10 @@ | ||
| import type { App } from '@slack/bolt' | ||
| import * as commands from './commands/showcase' | ||
| import * as actions from './actions/showcase-actions' | ||
| import * as shortcuts from './shortcuts/showcase-shortcuts' | ||
|
|
||
| export function registerListeners(app: App) { | ||
| commands.register(app) | ||
| actions.register(app) | ||
| shortcuts.register(app) | ||
| } |
89 changes: 89 additions & 0 deletions
89
apps/showcase-bot/src/listeners/shortcuts/showcase-shortcuts.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| import type { App, MessageShortcut } from '@slack/bolt' | ||
| import type { KnownBlock } from '@slack/types' | ||
| import { slackLogger } from '../../utils/logger' | ||
|
|
||
| export function register(app: App) { | ||
| app.shortcut<MessageShortcut>( | ||
| { callback_id: 'showcase_message_context', type: 'message_action' }, | ||
| async ({ shortcut, ack, client }) => { | ||
| await ack() | ||
|
|
||
| const { message, channel, user } = shortcut | ||
|
|
||
| // Truncate text to stay within Slack's 3000-char mrkdwn limit | ||
| const maxTextLen = 3000 - '```\n\n```'.length - '*Text*\n'.length | ||
| const rawText = message.text || '' | ||
| const truncatedText = | ||
| rawText.length > maxTextLen | ||
| ? rawText.slice(0, maxTextLen - 3) + '...' | ||
| : rawText | ||
|
|
||
| const fields = [ | ||
| ['Timestamp', message.ts], | ||
| ['Channel', channel.id], | ||
| ['User', user.id], | ||
| ['Text', truncatedText ? `\`\`\`${truncatedText}\`\`\`` : '_empty_'], | ||
| ] | ||
|
|
||
| const blocks: KnownBlock[] = fields.map(([label, value]) => ({ | ||
| type: 'section' as const, | ||
| text: { | ||
| type: 'mrkdwn' as const, | ||
| text: `*${label}*\n${value}`, | ||
| }, | ||
| })) | ||
|
|
||
| // Show attached files | ||
| const files = message.files as | ||
| | Array<{ url_private?: string; mimetype?: string }> | ||
| | undefined | ||
| if (files?.length) { | ||
| for (const file of files) { | ||
| if (file.url_private && file.mimetype?.startsWith('image/')) { | ||
| blocks.push({ | ||
| type: 'image', | ||
| image_url: file.url_private, | ||
| alt_text: 'Attached image', | ||
| }) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Show images from Block Kit blocks | ||
| const msgBlocks = message.blocks as | ||
| | Array<{ | ||
| type: string | ||
| image_url?: string | ||
| alt_text?: string | ||
| title?: { text?: string } | ||
| }> | ||
| | undefined | ||
| if (msgBlocks?.length) { | ||
| for (const block of msgBlocks) { | ||
| if (block.type === 'image' && block.image_url) { | ||
| blocks.push({ | ||
| type: 'image', | ||
| image_url: block.image_url, | ||
| alt_text: block.alt_text || block.title?.text || 'Image', | ||
| }) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| try { | ||
| await client.views.open({ | ||
| trigger_id: shortcut.trigger_id, | ||
| view: { | ||
| type: 'modal', | ||
| title: { type: 'plain_text', text: 'Message Context' }, | ||
| close: { type: 'plain_text', text: 'Close' }, | ||
| blocks, | ||
| }, | ||
| }) | ||
| slackLogger.info('Opened message context modal') | ||
| } catch (err) { | ||
| slackLogger.error({ err }, 'Failed to open message context modal') | ||
| } | ||
| } | ||
| ) | ||
| } | ||
109 changes: 109 additions & 0 deletions
109
apps/showcase-bot/src/messages/blocks/13-template-approval.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| { | ||
| "blocks": [ | ||
| { | ||
| "type": "section", | ||
| "text": { | ||
| "type": "mrkdwn", | ||
| "text": "You have a new request:\n*<google.com|Fred Enriquez - Time Off request>*" | ||
| } | ||
| }, | ||
| { | ||
| "type": "section", | ||
| "text": { | ||
| "type": "mrkdwn", | ||
| "text": "*Type:*\nPaid time off\n*When:*\nAug 10-Aug 13\n*Hours:* 16.0 (2 days)\n*Remaining balance:* 32.0 hours (4 days)\n*Comments:* \"Family in town, going camping!\"" | ||
| }, | ||
| "accessory": { | ||
| "type": "image", | ||
| "image_url": "https://api.slack.com/img/blocks/bkb_template_images/approvalsNewDevice.png", | ||
| "alt_text": "computer thumbnail" | ||
| } | ||
| }, | ||
| { | ||
| "type": "actions", | ||
| "elements": [ | ||
| { | ||
| "type": "button", | ||
| "text": { | ||
| "type": "plain_text", | ||
| "emoji": true, | ||
| "text": "Approve" | ||
| }, | ||
| "style": "primary", | ||
| "action_id": "showcase_approval_approve_pto", | ||
| "value": "click_me_123" | ||
| }, | ||
| { | ||
| "type": "button", | ||
| "text": { | ||
| "type": "plain_text", | ||
| "emoji": true, | ||
| "text": "Deny" | ||
| }, | ||
| "style": "danger", | ||
| "action_id": "showcase_approval_deny_pto", | ||
| "value": "click_me_123" | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| "type": "section", | ||
| "text": { | ||
| "type": "mrkdwn", | ||
| "text": "You have a new request:\n*<fakeLink.toEmployeeProfile.com|Fred Enriquez - New device request>*" | ||
| } | ||
| }, | ||
| { | ||
| "type": "section", | ||
| "fields": [ | ||
| { | ||
| "type": "mrkdwn", | ||
| "text": "*Type:*\nComputer (laptop)" | ||
| }, | ||
| { | ||
| "type": "mrkdwn", | ||
| "text": "*When:*\nSubmitted Aug 10" | ||
| }, | ||
| { | ||
| "type": "mrkdwn", | ||
| "text": "*Last Update:*\nMar 10, 2015 (3 years, 5 months)" | ||
| }, | ||
| { | ||
| "type": "mrkdwn", | ||
| "text": "*Reason:*\nAll vowel keys aren't working." | ||
| }, | ||
| { | ||
| "type": "mrkdwn", | ||
| "text": "*Specs:*\n\"Cheetah Pro 15\" - Fast, really fast" | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| "type": "actions", | ||
| "elements": [ | ||
| { | ||
| "type": "button", | ||
| "text": { | ||
| "type": "plain_text", | ||
| "emoji": true, | ||
| "text": "Approve" | ||
| }, | ||
| "style": "primary", | ||
| "action_id": "showcase_approval_approve_device", | ||
| "value": "click_me_123" | ||
| }, | ||
| { | ||
| "type": "button", | ||
| "text": { | ||
| "type": "plain_text", | ||
| "emoji": true, | ||
| "text": "Deny" | ||
| }, | ||
| "style": "danger", | ||
| "action_id": "showcase_approval_deny_device", | ||
| "value": "click_me_123" | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } |
109 changes: 109 additions & 0 deletions
109
apps/showcase-bot/src/messages/blocks/14-template-notification.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| { | ||
| "blocks": [ | ||
| { | ||
| "type": "section", | ||
| "text": { | ||
| "type": "plain_text", | ||
| "emoji": true, | ||
| "text": "Looks like you have a scheduling conflict with this event:" | ||
| } | ||
| }, | ||
| { | ||
| "type": "divider" | ||
| }, | ||
| { | ||
| "type": "section", | ||
| "text": { | ||
| "type": "mrkdwn", | ||
| "text": "*<fakeLink.toUserProfiles.com|Iris / Zelda 1-1>*\nTuesday, January 21 4:00-4:30pm\nBuilding 2 - Havarti Cheese (3)\n2 guests" | ||
| }, | ||
| "accessory": { | ||
| "type": "image", | ||
| "image_url": "https://api.slack.com/img/blocks/bkb_template_images/notifications.png", | ||
| "alt_text": "calendar thumbnail" | ||
| } | ||
| }, | ||
| { | ||
| "type": "context", | ||
| "elements": [ | ||
| { | ||
| "type": "image", | ||
| "image_url": "https://api.slack.com/img/blocks/bkb_template_images/notificationsWarningIcon.png", | ||
| "alt_text": "notifications warning icon" | ||
| }, | ||
| { | ||
| "type": "mrkdwn", | ||
| "text": "*Conflicts with Team Huddle: 4:15-4:30pm*" | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| "type": "divider" | ||
| }, | ||
| { | ||
| "type": "section", | ||
| "text": { | ||
| "type": "mrkdwn", | ||
| "text": "*Propose a new time:*" | ||
| } | ||
| }, | ||
| { | ||
| "type": "section", | ||
| "text": { | ||
| "type": "mrkdwn", | ||
| "text": "*Today - 4:30-5pm*\nEveryone is available: @iris, @zelda" | ||
| }, | ||
| "accessory": { | ||
| "type": "button", | ||
| "text": { | ||
| "type": "plain_text", | ||
| "emoji": true, | ||
| "text": "Choose" | ||
| }, | ||
| "action_id": "showcase_notification_choose_today", | ||
| "value": "click_me_123" | ||
| } | ||
| }, | ||
| { | ||
| "type": "section", | ||
| "text": { | ||
| "type": "mrkdwn", | ||
| "text": "*Tomorrow - 4-4:30pm*\nEveryone is available: @iris, @zelda" | ||
| }, | ||
| "accessory": { | ||
| "type": "button", | ||
| "text": { | ||
| "type": "plain_text", | ||
| "emoji": true, | ||
| "text": "Choose" | ||
| }, | ||
| "action_id": "showcase_notification_choose_tomorrow_4", | ||
| "value": "click_me_123" | ||
| } | ||
| }, | ||
| { | ||
| "type": "section", | ||
| "text": { | ||
| "type": "mrkdwn", | ||
| "text": "*Tomorrow - 6-6:30pm*\nSome people aren't available: @iris, ~@zelda~" | ||
| }, | ||
| "accessory": { | ||
| "type": "button", | ||
| "text": { | ||
| "type": "plain_text", | ||
| "emoji": true, | ||
| "text": "Choose" | ||
| }, | ||
| "action_id": "showcase_notification_choose_tomorrow_6", | ||
| "value": "click_me_123" | ||
| } | ||
| }, | ||
| { | ||
| "type": "section", | ||
| "text": { | ||
| "type": "mrkdwn", | ||
| "text": "*<fakelink.ToMoreTimes.com|Show more times>*" | ||
| } | ||
| } | ||
| ] | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.