Giter Site home page Giter Site logo

raycharius / slack-block-builder Goto Github PK

View Code? Open in Web Editor NEW
535.0 6.0 47.0 3.78 MB

Lightweight Node.js library for building Slack Block Kit UIs, with a declarative syntax inspired by SwiftUI.

Home Page: https://blockbuilder.dev

License: MIT License

TypeScript 100.00%
slack slackbot slack-api slack-webhook slack-commands slack-block-kit slack-app slackapi slack-bot slackware

slack-block-builder's Introduction

ℹ️ Block Builder was built in Kyiv, Ukraine πŸ‡ΊπŸ‡¦ If it has saved you time as a developer and brought value to your projects and products, we would like to ask you to consider donating to Come Back Alive to help Ukraine in its fight against Russian aggression. Every cent helps. πŸ™


Logo

Maintainable code for Slack interactive messages, modals, and home tabs.

Lightweight, zero-dependency library for declaratively building Slack Block Kit UI.

View the Docs Β»

Quick Start Guide Β· Request Feature Β· Report Bug

An example of using Block Builder


npm NPM codecov

Block Builder helps you keep your Slack app code for UI maintainable, testable, and reusable. It has a declarative, chainable syntax inspired by SwiftUI and is built for better UI architecture.

⚑   Features

  • Declarative SwiftUI inspired syntax.
  • Commonly-used UI components, such as a Paginator and Accordion.
  • Inline conditional helper functions for declaratively appending or omitting UI content.
  • The ability to build more complex flows using loops and conditionals.
  • A printPreviewURL() method that outputs a link to preview your UI on Slack's Block Kit Builder website for easier prototyping.
  • A set of helper functions for formatting text with Slack's markdown standard.
  • In-depth doc site at https://blockbuilder.dev.
  • Support for all current Slack Block Kit objects.
  • A great TypeScript experience.
  • Extensive JSDoc hints with explanations, validation rules, and quick links to full documentation.
  • Zero dependencies.

🎁   Benefits

  • Write three times less code.
  • Build more sophisticated, elegant flows.
  • Design better UI architecture for your Slack apps.
  • Focus more on code in your IDE than on studying the Slack API docs.
  • Easily integrate localizations into your app.

πŸ‘₯ Β  Join The Community

Feedback – love it! Aside from GitHub Issues, there's a Slack channel available in the Slack Community workspace to discuss Block Builder – we'll see you there! πŸ™Œ

πŸ’Ύ Β  Installation

Block Builder requires Node.js 12 or higher, and, when using TypeScript, TypeScript 3.8 or higher.

Using NPM:

npm install --save slack-block-builder

Using Yarn:

yarn add slack-block-builder

πŸ‘Ύ Β  Usage

For full documentation, make sure you head over to https://blockbuilder.dev.

Importing

The functions for creating objects can be both imported directly or through an object that groups them by category.

// Importing exposed groups of objects

import { Surfaces, Blocks, Elements, Bits, Utilities } from 'slack-block-builder';

// Importing objects top-level

import { Modal, Section, Actions, Button } from 'slack-block-builder';

The same goes for importing Slack markdown helper functions:

// Importing the Md object

import { Surfaces, Blocks, Md } from 'slack-block-builder';

// Importing the functions top-level

import { Modal, Section, bold, link } from 'slack-block-builder';

Object Breakdown

Surfaces – Contains functions for creating modals, messages, home tabs, and workflow steps.

Blocks – Layout blocks used to organize the UI.

Elements – UI elements that are used to capture user interaction.

Bits – These are composition objects and other bits and pieces from Slack's docs. Included are Attachment, Options, OptionGroup, and ConfirmationDialog. They felt like they were deserving of their own category.

Utilities – A group of utility functions. See Utility Functions.

Md – Helper functions for formatting text with Slack's markdown. See Markdown Helpers.

Block Kit Support and Reference

Below is a list of supported objects and how to access them in Block Builder:

Name Type Support Accessed Via
Home Tab Surface βœ… Surfaces.HomeTab()
Message Surface βœ… Surfaces.Message()
Modal Surface βœ… Surfaces.Modal()
Workflow Step Surface βœ… Surfaces.WorkflowStep()
Actions Block βœ… Blocks.Actions()
Context Block βœ… Blocks.Context()
Divider Block βœ… Blocks.Divider()
File Block βœ… Blocks.File()
Header Block βœ… Blocks.Header()
Image Block βœ… Blocks.Image()
Input Block βœ… Blocks.Input()
Section Block βœ… Blocks.Section()
Video Block βœ… Blocks.Video()
Button Element βœ…οΈ Elements.Button()
Checkboxes Element βœ… Elements.Checkboxes()
Date Picker Element βœ… Elements.DatePicker()
Date Time Picker Element βœ… Elements.DateTimePicker()
Email Input Element βœ… Elements.EmailInput()
File Input Element βœ… Elements.FileInput()
Time Picker Element βœ… Elements.TimePicker()
Image Element βœ… Elements.Img()
Number Input Element βœ… Elements.NumberInput()
Overflow Menu Element βœ… Elements.OverflowMenu()
Radio Buttons Element βœ… Elements.RadioButtons()
Plain-Text Input Element βœ… Elements.TextInput()
Select Menus Element βœ… Elements.[Type]Select()
Multi-Select Menus Element βœ… Elements.[Type]MultiSelect()
URL Input Element βœ… Elements.NumberInput()
Option Composition Object βœ… Bits.Option()
Confirm Dialog Composition Object βœ… Bits.ConfirmationDialog()
Option Group Composition Object βœ… Bits.OptionGroup()
Attachment Legacy Feature βœ… Bits.Attachment()

Creating a Simple Interactive Message

Let's take a look at how to compose an interactive message. Even though Slack now has modals, these have always been the basis for Slack apps.

Functions that return Block Kit objects have setter methods for all of the properties, but also support parameters that are passed into the constructor for properties with primitive types.

import { Message, Blocks, Elements } from 'slack-block-builder';

export default ({ channel, dangerLevel }) =>
  Message()
    .channel(channel)
    .text('Alas, my friend.')
    .blocks(
      Blocks.Section()
        .text('One does not simply walk into Slack and click a button.'),
      Blocks.Section()
        .text('At least that\'s what my friend Slackomir said :crossed_swords:'),
      Blocks.Divider(),
      Blocks.Actions()
        .elements(
          Elements.Button()
            .text('Sure One Does')
            .actionId('gotClicked')
            .danger(dangerLevel > 42), // Optional argument, defaults to 'true'
          Elements.Button()
            .text('One Does Not')
            .actionId('scaredyCat')
            .primary()))
    .asUser()
    .buildToJSON();

And now an example with using both the setter methods and passing parameters into the functions at initiation:

import { Message, Blocks, Elements } from 'slack-block-builder';

export default ({ channel, dangerLevel }) =>
  Message({ channel, text: 'Alas, my friend.' })
    .blocks(
      Blocks.Section({ text: 'One does not simply walk into Slack and click a button.' }),
      Blocks.Section({ text: 'At least that\'s what my friend Slackomir said :crossed_swords:' }),
      Blocks.Divider(),
      Blocks.Actions()
        .elements(
          Elements.Button({ text: 'Sure One Does', actionId: 'gotClicked' })
            .danger(dangerLevel > 42), // Optional argument, defaults to 'true'
          Elements.Button({ text: 'One Does Not', actionId: 'scaredyCat' })
            .primary()))
    .asUser()
    .buildToJSON();

Both of these examples render the message below. And the best part? It only took 15 lines of code, as opposed to the 44 lines of JSON generated as a result.

An example of using Block Builder for Messages

View Example on Slack Block Kit Builder Website

Creating a Simple Modal

Let's take a look at how modals are created. Here we'll also take a look at working with Bits and with loops, by adding options with the Array.map() method.

import { Modal, Blocks, Elements, Bits, setIfTruthy } from 'slack-block-builder';

export default ({ menuOptions, selected }) =>
  Modal({ title: 'PizzaMate', submit: 'Get Fed' })
    .blocks(
      Blocks.Section({ text: 'Hey there, colleague!' }),
      Blocks.Section({ text: 'Hurray for corporate pizza! Let\'s get you fed and happy :pizza:' }),
      Blocks.Input({ label: 'What can we call you?' })
        .element(
          Elements.TextInput({ placeholder: 'Hi, my name is... (What?!) (Who?!)' })
            .actionId('name')),
      Blocks.Input({ label: 'Which floor are you on?' })
        .element(
          Elements.TextInput({ placeholder: 'HQ – Fifth Floor' })
            .actionId('floor')),
      Blocks.Input({ label: 'What\'ll you have?' })
        .element(
          Elements.StaticSelect({ placeholder: 'Choose your favorite...' })
            .actionId('item')
            .options(menuOptions
              .map((item) => Bits.Option({ text: item.name, value: item.id })))
            .initialOption(setIfTruthy(selected, Bits.Option({ text: selected.name, value: selected.id })))))
    .buildToJSON();

Both of these examples render the modal below.

An example of using Block Builder for Modals

View Example on Slack Block Kit Builder Website

Paginator Component

Block Builder provides a Paginator component that assists in producing paginated UI. It allows you to dictate the UI to build for each items passed in and provides to the actionId all of the data (page, perPage, totalPages, offset, totalItems ) you need to produce the right page when a user clicks the Next or Previous buttons.

Note that there is a demo app available that demonstrates how to use components.

The Paginator component supports optional customizations, such as:

nextButtonText – Used to pass in custom text for the Next button, but has a default.

previousButtonText – Used to pass in custom text for the Next button, but has a default.

pageCountText – Used to pass in custom text for the page count, accepts a function and passes the function an object with page and totalPages properties.

import { Modal, Blocks, Elements, Paginator } from 'slack-block-builder';

export default ({ tasks, totalTasks, page, perPage }) => Modal({ title: 'Open Tasks' })
  .blocks(
    Blocks.Section({ text: 'Hi! :wave: And welcome to the FAQ section! Take a look around and if you don\'t find what you need, feel free to open an issue on GitHub.' }),
    Blocks.Section({ text: `You currently have *${totalTasks} open task(s)*:` }),
    Paginator({
      perPage,
      items: tasks,
      totalItems: totalTasks,
      page: page || 1,
      actionId: ({ page, offset }) => JSON.stringify({ action: 'render-tasks', page, offset }),
      blocksForEach: ({ item }) => [
        Blocks.Divider(),
        Blocks.Section({ text: `*${item.title}*` })
          .accessory(
            Elements.Button({ text: 'View Details' })
              .actionId('view-details')
              .value(item.id.toString())),
        Blocks.Section({ text: `*Due Date:* ${getDate(item.dueDate)}` }),
      ],
    }).getBlocks())
  .close('Done')
  .buildToJSON();

The code above renders the modal below. And be sure to check out the full documentation on the Block Builder doc site for more information.

An example of using Block Builder for Modals

View Example on Slack Block Kit Builder Website

Accordion Component

Using the Accordion component, you can easily create a customizable accordion for your Slack app. It not only assists in building a suitable UI, but also calculates the next state and gives you access to it in the actionId of the buttons in the accordion, so that you can pass that back to your app's backend and use it to render the next state.

Note that there is a demo app available that demonstrates how to use components.

The Accordion component supports optional customizations, such as:

collapseOnExpand – Dictates whether or not multiple items can be expanded at once. When set to true, only one item will be expanded at any given time.

expandButtonText – Used to pass in custom text for the button that expands an item, but has a default.

collapseButtonText – Used to pass in custom text for the button that collapses an expanded item, but has a default.

isExpandable – Used to display or not the expand/collapse button for a given item.

import { Modal, Blocks, Accordion } from 'slack-block-builder';

export default ({ faqs, expandedItems }) => Modal({ title: 'FAQ' })
  .blocks(
    Blocks.Section({ text: 'Hi! :wave: And welcome to the FAQ section! Take a look around and if you don\'t find what you need, feel free to open an issue on GitHub.'}),
    Blocks.Divider(),
    Accordion({
      items: faqs,
      expandedItems: expandedItems || [], // In this case, the value is [1]
      collapseOnExpand: true,
      titleText: ({ item }) => `*${item.question}*`,
      actionId: ({ expandedItems }) => JSON.stringify({ action: 'render-faqs', expandedItems }),
      blocksForExpanded: ({ item }) => [
       Blocks.Section({ text: `${item.answer}` }),
      ],
    }).getBlocks())
  .close('Done')
  .buildToJSON();

The code above renders the modal below. And be sure to check out the full documentation on the Block Builder doc site for more information.

An example of using Block Builder for Modals

View Example on Slack Block Kit Builder Website

Utility Functions

The Utilities object contains various utility functions for creating UI. Currently, there are two:

BlockCollection() – Accepts multiple arguments or an array of blocks and returns them in an array, in their built state.

AttachmentCollection() – Accepts multiple arguments or an array of attachments and returns them in an array, in their built state.

OptionCollection() – Accepts multiple arguments or an array of options and returns them in an array, in their built state.

OptionGroupCollection() – Accepts multiple arguments or an array of option groups and returns them in an array, in their built state.

Both BlockCollection() and AttachmentCollection() are useful when you wish to keep surface or view configuration separate from UI representation.

An example using Slack's WebClient from their SDK for Node.js:

import { BlockCollection, AttachmentCollection, Blocks } from 'slack-block-builder';
import { WebClient } from '@slack/web-api';

const client = new WebClient(process.env.SLACK_TOKEN);

client.chat.postMessage({
  channel: 'ABCDEFG',
  text: 'Hello, my dear, sweet world!',
  blocks: BlockCollection( /* Pass in blocks */ ),
  attachments: AttachmentCollection( /* Pass in attachments */ ),
})
.then((response) => console.log(response))
.catch((error) => console.log(error));

Another example where you might find BlockCollection() helpful is when unfurling links in messages:

import { BlockCollection, Blocks } from 'slack-block-builder';
import { WebClient } from '@slack/web-api';

const client = new WebClient(process.env.SLACK_TOKEN);

const unfurl = ({ channel, ts, url }) => client.chat.unfurl({
  channel,
  ts,
  unfurls: { [url]: BlockCollection( /* Pass in blocks */ ) },
})
.then((response) => console.log(response))
.catch((error) => console.log(error));

Both OptionCollection() and OptionGroupCollection() come in handy when returning an array of options or option groups for select menus with external data sources, as seen in Slack's API docs:

return { options: OptionCollection( /* Pass in options */ ) };

// Or:

return { options: OptionGroupCollection( /* Pass in option groups */ ) };

Working With Inline Conditionals

There are a few helper functions available to make it easy to work with inline conditionals within your UI source code.

They can be imported separately:

import { setIfTruthy, omitIfTruthy, setIfFalsy, omitIfFalsy } from 'slack-block-builder';

Or as a part of the conditionals object:

import { conditionals } from 'slack-block-builder';

Each function accepts two arguments – the first being a value that is evaluated whether it is either null, undefined, or false, and the second being the value to set or omit:

import { Modal, Blocks, Elements, Bits, setIfTruthy } from 'slack-block-builder';

export default ({ groups, selectedGroup, selectedGroupMembers }) => Modal()
  .title('Edit Groups')
  .callbackId('submit-edit-groups')
  .blocks(
    Blocks.Section({ text: 'Hello! Need to edit some groups?'}),
    Blocks.Input({ label: 'Select a group to get started' })
      .dispatchAction()
      .element(
        Elements.StaticSelect({ placeholder: 'Select a group...' })
          .actionId('selectedGroup')
          .options(groups
            .map(({ name, id }) => Bits.Option({ text: name, value: id })))),
    setIfTruthy(selectedGroup, [
      Blocks.Input({ label: 'Current group members' })
        .element(
          Elements.UserMultiSelect({ placeholder: 'Select members...' })
            .actionId('groupMembers')
            .initialUsers(selectedGroupMembers))
    ]))
  .submit(setIfTruthy(selectedGroup, 'Save Changes'))
  .buildToJSON();

These functions essentially return either the value passed into as the second argument or undefined, depending on the condition. Please note that falsy is defined as null, undefined, or false. To avoid side effects, values such as 0 or '' are not considered to be falsy.

Markdown Helpers

Often you'll find that you need to format text in your messages and modals. Block Builder has helper functions available to simply that process. They are available both as members of the Md object and as top-level imports. You can find the full list of functions on the Block Builder doc site:

import { Message, Blocks, Md } from 'slack-block-builder';

export default ({ channel, user }) => {
  const slashCommands = ['/schedule', '/cancel', '/remind', '/help'];

  return Message({ channel, text: 'Alas, my friend.' })
    .blocks(
      Blocks.Section({ text: `:wave: Hi there, ${Md.user(user)}!` }),
      Blocks.Section({ text: `${Md.italic('Sorry')}, I didn't get that. Why don't you try out some of my slash commands?` }),
      Blocks.Section({ text: `Here are some of the things that I can do:` }),
      Blocks.Section()
        .text(Md.listBullet(slashCommands
          .map((item) => Md.codeInline(item)))))
    .asUser()
    .buildToObject();
};

View Example on Slack Block Kit Builder Website

πŸ”— Β  Other Useful Slack-Related Projects

Bolt for JavaScript – A simple framework for building Slack apps, developed by Slack themselves.

Node Slack SDK – A great and powerful SDK for building Slack Apps from the ground up.

πŸ”₯ Β  Acknowledgements

@korywka Taras Neporozhniy (@korywka) - For help and ideas along the way!

@ft502 Alexey Chernyshov (@ft502 on Dribbble) - For such a beautiful logo!

@slackhq SlackHQ (@slackhq) - For such a wonderful product and API!

βœ’οΈ Β  Author

@raycharius Ray East (@raycharius) - Huge Fan of Slack and Block Builder Maintainer

slack-block-builder's People

Contributors

ajkitson avatar carlovsk avatar chasek-hpe avatar geoffp avatar m1kep avatar michaelbudgell avatar nlwillia avatar raycharius avatar setchy avatar shyndman avatar siopao-pao avatar slikts avatar soominchun avatar tanguyantoine avatar trev-gulls avatar zaini avatar zcei avatar zzullick avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

slack-block-builder's Issues

Context.elements() is typed incorrectly in types.d.ts

I've gathered from poking at the source code that the Context.elements method can also accept Markdown-formatted strings as input alongside Elements. Which is great, but the types.d.ts file doesn't include strings in the acceptable types.

I hacked around by declaring an overload in my own source as follows:

declare module 'slack-block-builder' {
  namespace Block {
    export interface Context extends blockBuilder.Block.Block {
      elements(
        ...elements: Array<Element.Element | Element.Element[] | string | string[]>
      ): this;
    }
  }
}

I would expect to see this in the docs somewhere as well. Just a thought.

Happy to send a pull your way too.

Great library btw. Thank you. :)

Add Markdown Helper Functions

Is your feature request related to a problem? Please describe.
Often we need to format text and including the markdown in a string requires us to know the Slack markdown syntax and is also not as obvious as using functions.

Describe the solution you'd like
Helper functions to format text on Block Kit surfaces.

Unable to use TextInput as an Accessory

If I use something like this:

Section({text: "Fill in data: "})
            .accessory(
                TextInput({actionId: "Example"})
            )

I'll get this error:
Argument of type 'TextInputBuilder' is not assignable to parameter of type 'Undefinable'.
Type 'TextInputBuilder' is missing the following properties from type 'UserSelectBuilder': confirm, initialUserts(2345)

But if I do

  Section({ text: "button" })
         .accessory(
                    Button({
                        text: "This is the btn",
                        actionId: actionId
                    })
                )

Then I get no errors. Is this a bug or am I doing something wrong. An example of how to do this on the doc site would be great

Expose build method on Blocks

I only need to generate the Blocks Element, especially when using other frameworks that can already generate the "surface" payload.

"preferred way" example in "working with conditions" produces typescript error

Describe the Bug
The "preferred way" documentation in "working with conditions" does not seem to be a valid example β€” in practice, the example produces a TypeScript error

Note:

⚠️ I'm not entirely sure if this is a bug or a documentation issue

To Reproduce
Using v2.0.1, try this:

import { Blocks, Modal } from 'slack-block-builder';

const someData = undefined;
const example = Modal({ title: 'Using If Statements' })
    .blocks(
        Blocks.Section({ text: 'Let\'s take a look at conditionals.' }),
        Blocks.Section({ text: 'This is a much better way to do it!' }))
    .blocks(someData && [
        Blocks.Section({ text: 'This is added if condition is true.' }),
        Blocks.Section({ text: 'And it\'s super easy and readable!' }),
    ])

Screen Shot 2021-07-06 at 2 15 52 PM

Given that I don't know what someData is, I'm assuming it would be either undefined or a truthy value, and given that, below the example in the docs, it is noted:

For conditions that provide a true or false instead of undefined, using a ternary operator is a great way to go.

I'm lead to believe that undefined should be a valid value for someData, and that .blocks(undefined) would also be valid.

Here's the (also not working) ternary version of the example:

const someData = undefined;
const example = Modal({ title: 'Using If Statements' })
    .blocks(
        Blocks.Section({ text: 'Let\'s take a look at conditionals.' }),
        Blocks.Section({ text: 'This is a much better way to do it!' }))
    .blocks(someData ? [
        Blocks.Section({ text: 'This is added if condition is true.' }),
        Blocks.Section({ text: 'And it\'s super easy and readable!' }),
    ]: undefined)

Screen Shot 2021-07-06 at 2 15 22 PM

Though if I return an empty array in the ternary (instead of undefined), that is an acceptable value.

Expected Behavior

Given that I'm unsure if this is a documentation issue or actual bug:

  • the documentation should provide a clearer/valid example and/or the typedefs should be updated to reflect valid values

Additional Context
n/a

Support `fallback` property on Bits.Attachment object

Description

The Bits.Attachment currently does not support the fallback property. It is assumed that most of these legacy properties have requisite alternatives with the blocks structure, but fallback is an exception.

Use Case

When authoring a message that ONLY utilizes attachments, there is a need for text to render in mobile or desktop notifications instead of the default "This content cannot be displayed". The text property which seem suitable for this purpose will prepend itself to the actual message posted in Slack in this case, not just the notification text. Therefore, if you need to provide a attachment-only message with proper text for a mobile notification, fallback must still be used for this case.

Expose .build() on Element and Block builders

Is your feature request related to a problem? Please describe.
For our specific use case slack-block-builder works very well for building block collections that we pass to the API. However, we also want to respond to Slack actions on our messages by editing the original message (e.g. reading in the current blocks object of a message and replacing certain sections).

Describe the solution you'd like
It would be ideal if the block and element builders types exposed the .build() function that is used under the hood by BlockCollection so that we could build a single element.

Describe alternatives you've considered
Currently I need to do something like:

const actionsObject = BlockCollection(Blocks.Actions().elements(element)).pop() as ActionsBlock | undefined;
const elementObject = actionsObject?.elements.pop();
return elementObject;

Where I'm casting to types exposed by @slack/bolt because SlackBlockDto doesn't have the elements field even when you can assert the type of the block.
I'd love for this to be simplified to just element.build() so we don't need to mess around with the BlockCollection and types.

Templating to populate blocks dynamically. Turn JSON into a Block (reverse buildToJSON)

Is your feature request related to a problem? Please describe.
Does something similar to templating exist? https://docs.microsoft.com/en-us/adaptive-cards/templating/

I want to be able to create a block and then populate it with data. What is the current best method to do so? I'm currently using json-templates but wondering if there is a more native solution.

Just being able to convert a JSON to a block (basically the reverse of buildToJSON) would be cool.

Describe the solution you'd like
I want to be able to create (or load/import a JSON template) a template for blocks and then be able to inject it with data, similar to Adaptive Cards: https://docs.microsoft.com/en-us/adaptive-cards/templating/

Something like this:

// const template = loadTemplate(file)
const template = Template().blocks(Blocks.Section({text: 'My name is {{user_name}}'})...)

template.expand({user_name: "Bobby"})

Describe alternatives you've considered
Currently I'm using json-templates to fill in a JSON of the block I want to populate with real data.

Add Loops Doc to Doc Site

Need to know how to iterate over iterable objects:

– Mapping, filtering, reducing
– Iterating over non-array objects.
– Self-invoking functions

Etc.

Add support for workflow steps

Are there plans to support workflow steps? For instance when opening a configuration modal, the type needs to be workflow_step rather than modal.

is it possible to add plain_text and mrkdwn under elements ?

Hi,

I am trying to add mrkdwn type under elements, but it seems this isn't possible right now.
I am trying to achieve something like this:

{
	"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": "*No response within 5 sec*"
		}
	]
},

Is it currently possible? Or will it be implemented at some point ?

Thank you very much.

Use Slack Types

Is your feature request related to a problem? Please describe.
Up until now, the library has had zero dependencies, but it makes more sense to include Slack's own types.

Describe the solution you'd like

  • Remove SlackDto classes
  • The build() methods should only return an object literal, aligned with Slack's types

WYSIWYG - REPL Demo needed. Tool that provides instant visual feedback preview of the output.

Is your feature request related to a problem? Please describe.

There's no way to build visually and live preview the output of what you're building unless you run the code...
There are the Build Methods but you need to run code, take the output of that, and go to Slack Block Kit Builder... or get the link via printPreviewUrl() and use that...
I think would be nice to have a WYSIWYG feature.

Describe the solution you'd like
A WYSIWYG REPL tool, similar to https://jsx-slack.netlify.app/ ?

Describe alternatives you've considered

Additional context

BTW, Thanks for building such an amazing tool! I love it! 🀩

Add Paginator UI Component

Add Paginator module to easily allow developers to paginate items. It should provide out-of-the-box UI as well as state management to assist with paginating on the DB level by making that data available in the action ID of the buttons.

There should also be a simple version that accepts an entire array of items as opposed to a subset of data and the state manager should handle this as well.

Long list of errors after yarn add

Describe the Bug
I followed the installation guide and am now getting a long list of errors. Part of it is below.

Node 10.14, TS 3.7.5

../../node_modules/slack-block-builder/dist/bits/attachment.d.ts:3:13 - error TS1005: '=' expected.

3 import type { BlockBuilder } from '../types';
              ~
../../node_modules/slack-block-builder/dist/bits/attachment.d.ts:3:35 - error TS1005: ';' expected.

3 import type { BlockBuilder } from '../types';
                                    ~~~~~~~~~~
../../node_modules/slack-block-builder/dist/bits/index.d.ts:5:1 - error TS1128: Declaration or statement expected.

5 export type { AttachmentBuilder, AttachmentParams, ConfirmationDialogBuilder, ConfirmationDialogParams, OptionBuilder, OptionParams, OptionGroupBuilder, OptionGroupParams, };
  ~~~~~~
../../node_modules/slack-block-builder/dist/bits/index.d.ts:5:13 - error TS1005: ';' expected.

5 export type { AttachmentBuilder, AttachmentParams, ConfirmationDialogBuilder, ConfirmationDialogParams, OptionBuilder, OptionParams, OptionGroupBuilder, OptionGroupParams, };
              ~
../../node_modules/slack-block-builder/dist/bits/index.d.ts:5:173 - error TS1109: Expression expected.
...

To Reproduce

Using Node 10.14, TS 3.7.5, yarn add slack-block-builder and import the module and run the file.

Expected Behavior
Not to get all those errors and for it to work.

Additional Context
I've tried deleting my node_modules and reinstalling. I assume this is a TS error but I'm unsure why it's only happening with this library (all the other stuff is working fine.)

Trouble using with @slackapi/bolt-js client.views.open and client.views.update

When trying to use a Modal with the client.views.open or update methods with BoltJS, it causes an error with TypeScript. I'm not sure if this is me doing this wrong, or something else but it looks like some sort of type mismatch. The below is an example of what I'm seeing. Thanks in advance!

Versions:
Node: 16
Typescript: 4.4.3
slack-block-builder: 2.1.1
@slackapi/bolt-js: 3.6.0

const handleShortcutTrigger: Middleware<SlackShortcutMiddlewareArgs<GlobalShortcut>> = async ({
    shortcut,
    ack,
    client,
}) => {
    await ack();
    await client.views.open({
        trigger_id: shortcut.trigger_id,
        view: genSupportRequestForm().buildToObject(),
    });
};
import { Blocks, Modal, ModalBuilder } from 'slack-block-builder';

function genSupportRequestForm(): ModalBuilder {
    return Modal({
        title: 'Support Request Form',
        submit: 'Submit',
        close: 'Cancel',
        callbackId: 'support_request_form-action',
    }).blocks(
        Blocks.Section({
            text: '*Please fill out the fields below to file a Support team request:*',
        }),
    );
}

export { genSupportRequestForm };

Error(On the view property of the client.views.open call:

TS2322: Type 'Readonly<SlackViewDto>' is not assignable to type 'View'.
    Type 'Readonly<SlackViewDto>' is not assignable to type 'WorkflowStepView'.
        Types of property 'type' are incompatible.
            Type 'SurfaceType' is not assignable to type '"workflow_step"'.

Using excludeBotUsers() on conversation select element does not confrom to Slack API

First
This lib is terrific -- it's really sped up iterating with block kit and the it's very intuitive to use. Really well done!

Happy to do a PR, but I haven't oriented to the internals of the lib yet. Let me know if that'd be helpful!

Describe the Bug
When I use excludeBotUsers on a conversation-select element, Slack rejects the output:

{
  code: 'slack_webapi_platform_error',
  data: {
    ok: false,
    error: 'invalid_arguments',
    response_metadata: {
        messages: [
          '[ERROR] failed to match all allowed schemas [json-pointer:/view]',
          '[ERROR] invalid additional property: exclude_bot_users [json-pointer:/view/blocks/0/element]'
        ]
    ...
}

The issue appears to be that exclude_bot_users is included twice. Once in filters where it belongs and once on element where it does not:

    "blocks": [
        {
            "block_id": "my-channel",
            ...
            "element": {
                "action_id": "abc-channel-select",
                "exclude_bot_users": true, //  <---- THIS IS THE ISSUE
                "type": "conversations_select",
                "filter": {
                    "exclude_bot_users": true
                }
            },
    ],
}

If I delete exclude_bot_users from element before sending it to Slack, it works just fine.

To Reproduce
Using Bolt, display this modal in response to a shortcut:

async function showModal({ ack, shortcut, client }) {
  await ack();
  const myModal = Modal({ 
    title: 'My Modal',
    callbackId: 'my-modal',
   submit: 'Submit'
  })
  .blocks(
    Blocks.Input()
      .blockId('my-channel')
      .label('Choose channel')
      .element(
        Elements.ConversationSelect({
          actionId: 'abc-channel-select'
        })
        .excludeBotUsers() // works when I comment this out
      )      
  )
  .buildToObject();

  await client.views.open({
    trigger_id: shortcut.trigger_id,
    view: myModal,
  });
}

Expected Behavior
The output conforms to the Slack API

Typescript: missing typing for Message.printPreviewUrl()

It seems that this is normally on Surfaces that inherit from AdvancedSurface, but Message has it as well.

I also had the thought that it would be nice to factor out a method that generates the preview URL, and another that prints it to console.log(), so consumers can do something else with the URL if they want to. I can do a separate issue for that though, if you’re game, @raycharius.

Matching PR for this issue is over here.

Add text() function to documentation

Via the various documentation pages for objects which support text, the text() function/setter method isn't currently documented.

For example on the getting started page it includes the following code exert:

image

...showing a call to Blocks.Section().text('some text').

However if we look at the Section() documentation page setter methods, the text() function is missing: https://www.blockbuilder.dev/#/blocks/section

Better Way to Handle Inline Logic

Currently, the library supports the ability to pass in undefined to any setter or appending method, such as:

module.exports = (someParam) => {
  return Modal({ title: 'Test Modal', close: 'Cancel' })
    .blocks(
      Blocks.Section({ text: 'This is just a test...' }),
      someParam && Blocks.Section()
        .text('This is visible if this parameter is defined.'),
      Blocks.Section({ text: 'Is there a button here?' })
        .accessory(someParam && Elements.Button()
          .text('Click Me!')
          .actionId('been-clicked')),
      Blocks.Divider(),
      Blocks.Section({ text: 'Why not think of a better way?'}),
    ).buildToObject();
};

However, this approach is not very evident and requires the developer peruse the documentation to find out about it. It also only works with undefined values, but not false or null values.

As the library is growing in popularity, I think it makes sense to find a better way to do that.

The proposal is to add in some helper functions for this, to make the code more understandable and declarative. Helper functions such as:

setIfFalsy()
setIfTruthy()
appendIfFalsy()
appendIfTruth()

Where each function accepts a statement and a value, and returns either the value or undefined.

The above modal would then look like this:

module.exports = (someParam) => {
  return Modal({ title: 'Test Modal', close: 'Cancel' })
    .blocks(
      Blocks.Section({ text: 'This is just a test...' }),
      appendIfTruthy(someParam, Blocks.Section()
        .text('This is visible if this parameter is defined.')),
      Blocks.Section({ text: 'Is there a button here?' })
        .accessory(setIfTruthy(someParam, Elements.Button()
          .text('Click Me!')
          .actionId('been-clicked'))),
      Blocks.Divider(),
      Blocks.Section({ text: 'Why not think of a better way?'}),
    ).buildToObject();
};

In such a case:

  • The code becomes more readable.
  • Logic is more declarative.
  • Works for all undefined, null, and false values for setter and appending methods.
  • Makes the documentation more straightforward.
  • Will save a lot of code for those building out similar helper functions.

Would love any input or proposals from the community on how this could be better handled!

Automatic text truncation and min/max length error handling

Is your feature request related to a problem? Please describe.
I'm always frustrated when I have to lookup the min and max lengths for text objects.

Describe the solution you'd like
Plain Text: I'd like automatic truncation of the text input for plain_text objects based on the maximum length for the composition object field. The end of the string should be replaced with a sensible pattern like ellipses ....
Ex. "<long text>123<overflow>" --> "<long text>...".

Markdown (stretch goal): Markdown formatting aware truncation for text input for mrkdwn objects (i.e. if a bold * will be cut off by the truncation move it to the end before the ellipses).
Ex. <long text> *<bold text>* --> <long text> *<bold*...

Other: Some blocks have fields which take a list of elements or composition objects. Methods to set (append) these lists should throw an error if the maximum length is exceeded. Build methods should throw an error if the minimum or maximum length is violated.

Error Handling: Whenever text is truncated a DEBUG message is logged. Whenever empty string is passed in for a text field, if the text field is optional discard, if required throw an error (since min length for text fields is always 1).

Describe alternatives you've considered
Currently I use my own utility function to truncate text to the desired length before I pass it into the builder constructor or function. If text is not truncated properly, the slack API throws an error.

Additional context
blocks

object type field field type required? min_length max_length desired behavior
*. block_id string No 255 error
actions elements object[] Yes 1 5 error
context elements (img|text)[] Yes 1 10 error
header text plain_text Yes 1 150 truncate
image image_url string Yes 1 3000 error
alt_text string Yes 1 2000 truncate
title plain_text No 1 2000 truncate
input label plain_text Yes 1 2000 truncate
hint plain_text No 1 2000 truncate
section text text Preferred 1 3000 truncate
fields text Maybe 1 10 error
fields.text string Maybe 1 2000 truncate

composition objects

object type field field type required? min_length max_length desired behavior
* action_id string Yes 1 255 error
button text plain_text Yes 1 75 truncate
url string No 1 3000 error
value string No 1 2000 error
checkboxes options option[] Yes 1 10 error
datepicker placeholder plain_text No 1 150 truncate
image image_url string Yes 1 3000 error
alt_text string Yes 1 2000 truncate
*_select placeholder plain_text Yes 1 150 truncate
*static_select options option[] Maybe 1 100 error
option_groups option_group[] Maybe 1 100 error
overflow options option[] Yes 2 5 error
plain_text_input placeholder plain_text No 1 150 truncate
radio_options options option[] Yes 1 10 error
timepicker placeholder plain_text No 1 150 truncate

block elements

object type field field type required? min_length max_length desired behavior
confirm title plain_text Yes 1 100 truncate
text text Yes 1 300 truncate
confirm plain_text Yes 1 30 truncate
deny plain_text Yes 1 30 truncate
option text text Yes 1 75 truncate
value string Yes 1 75 error
description plain_text No 1 75 truncate
url string No 1 3000 error
option_group label plain_text Yes 1 75 truncate
options option[] Yes 1 100 error

Extending Blocks.Context() blockObject

Is your feature request related to a problem? Please describe.
I've enjoyed working with this library and through it learning more about Slack Block Builder. However, I've hit a wall...
It doesn't appear possible to create a context block with only plain text elements

Describe the solution you'd like
It would be useful to have a Context blockObject that accepts an Elements.PlainText(?params) object with text, emoji and text attributes

Describe alternatives you've considered

Additional context

Example of what I am trying to achieve here:

{
	"type": "context",
	"elements": [
		{
			"type": "image",
			"image_url": "https://api.slack.com/img/blocks/bkb_template_images/tripAgentLocationMarker.png",
			"alt_text": "Location Pin Icon"
		},
		{
			"type": "plain_text",
			"emoji": true,
			"text": "Location: Central Business District"
		}
	]
}

Add optional parameter on functions like Elements.Button().primary()

Hi Ray

For functions like Elements.Button().primary() which are ultimately just setting a boolean flag for component, can we please add an optional boolean parameter to these functions that allows us to pass an expression?

This would allow me to do something like this, where one of 3 buttons could be primary based on an expression:

ui.Blocks.Actions()
  .elements(
    ui.Elements.Button()
    .text('Books')
    .value('books')
    .primary(properties.currentOption === 'books'),
  ui.Elements.Button()
    .text('Magazines')
    .value('magazines')
    .primary(properties.currentOption === 'magazines'),
  ui.Elements.Button()
    .text('News')
    .value('news')
    .primary(properties.currentOption === 'news'),

Otherwise I have to write a bunch of if statements and push each element to the ui.Blocks.Actions().elements() method, which makes for clunky syntax.

Thanks,

Chris

[Help Needed] Elements.mrkdwn missing?

https://api.slack.com/reference/block-kit/blocks#context

I'm trying to create this context block:

{
  "type": "context",
  "elements": [
    {
      "type": "image",
      "image_url": "https://image.freepik.com/free-photo/red-drawing-pin_1156-445.jpg",
      "alt_text": "images"
    },
    {
      "type": "mrkdwn",
      "text": "Location: **Dogpatch**"
    }
  ]
}

I manage to find Elements.Img but I'm stuck with the latter on the mrkdwn part. Elements.MrkDwn doesn't exist

blocks.push(
  Blocks.Context().elements(
    Elements.Img({imageUrl: "https://pbs.twimg.com/profile_images/625633822235693056/lNGUneLX_400x400.jpg", altText: "cat"}),
    // Need help in inserting mrkdwn here
))

Ephemeral Message in DirectMessage not working

Describe the Bug

The goal is able to respond/say in DM publically(seen to all parties) when using slash command in direct message.

To Reproduce

the method below is not working:

await respond(Message()
        .blocks(Blocks.Section().text("Team Members"))
        .attachments(attachments)
        .ephemeral(false)
        .buildToObject()

I have to resort to the manual way

 await respond({
     "response_type": "in_channel",
     "blocks": BlockCollection(Blocks.Section().text("Team Members")),
     "attachments": AttachmentCollection(attachments),
 })

Expected Behavior

  • to provide a way to add response_type in lib level

Support Both Strings and Slack's Plain Text and Markdown Objects

Is your feature request related to a problem? Please describe.
Up until now, all of the properties that are plain text or markdown text objects per Slack's API have accepted only strings. And the overall logic was if it can be markdown – it defaults to markdown. This can be problematic as some may desire plain text.

Additionally, it has been impossible to configure the emoji and verbatim properties for these objects.

Describe the solution you'd like
The current approach is nice, as it is not verbose. But the library for those properties, the library needs to support strings, markdown objects, and plain-text objects.

Need to create corresponding builder objects and expose those as exports.

Fix Incorrect Typing for Certain Methods

Currently, when using TypeScript, since it depends on object structure of a class as opposed to whether or not an object is an instance of that class, some of the methods can accept incorrect params.

For example:

Interface for the base class Element object:

interface Element {
  end(): this;
}

Interface for the base class for a Block object:

interface Block {
  blockId(blockId: string): this;
  end(): this;
}

Definition for elements() method:

interface Actions extends Block {
 elements(...elements: Array<Element.Element | Element.Element[]>): 
}

As such, when the elements() method is called and a Block object is passed in, TypeScript does not display an error, since the Block object does conform to the Element interface:

Message()
  .blocks(
    Blocks.Section({ text: 'This should all  be invalid'}),
    Blocks.Actions()
      .elements(Blocks.Section({ text: 'But it is not!'})))
  .buildToJSON();

Support `focus_on_load` for Block Elements

Slack has added a new feature to have focus on a certain block element upon opening a view, by setting focus_on_load on any one Block Element.

Need to add a focusOnLoad() method to supported Block Elements.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.