npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@slack-wrench/blocks

v3.1.2

Published

Typescript Slack Blockkit components

Downloads

507

Readme

Blocks

Create messages with block kit more functionally and with less recall and repetition. Never need to remember json formats or accidentally type markdown instead of mrkdwn again!

This package helps abstract away some of the specifics, and deduplicate some of the repetitive code needed to create blocks being sent to Slack.

It also automatically handles various slack API limitations on content to ensure blocks built with dynamic content at least don't break when sent to Slack. Learn more about this in the Limits section.

Install

# Yarn
yarn add @slack-wrench/blocks

# npm
npm install @slack-wrench/blocks

Usage

Example block kit builder search channel message

import { App } from '@slack/bolt';
import { Actions, Blocks, Button, Context, DateString, Divider, Markdown, MdSection, User } from '@slack-wrench/blocks';

const app = new App({ /* token, secret */ });

app.message(':wave:', async ({ message, say }) => {
  say({
    blocks: Blocks([
      MdSection(`Hello, ${User(message.user)}! Let me help you find some channels.`, {
        accessory: Button('Search', 'changeSearch'),
      }),
      Divider(),
      MdSection('*Channels*'),
      MdSection('*house-ravenclaw*\nDiscuss Ravenclaw business'),
      Context([
        Markdown(
          `120 members\nLast post: ${DateString(1575643433, 'date_pretty', '1575643433')}`,
        ),
      ]),
      Actions([
        Button(':thumbsup:', 'thumbsUp', {
          value: 'house-ravenclaw',
        }),
        Button(':thumbsdown:', 'thumbsDown', {
          value: 'house-ravenclaw',
        }),
      ]),
  });
});

Before:

const { App } = require('@slack/bolt');

const app = new App({
  /* token, secret */
});

app.message(':wave:', async ({ message, say }) => {
  say({
    blocks: [
      {
        accessory: {
          type: 'button',
          text: { type: 'plain_text', text: 'Search', emoji: true },
          action_id: 'changeSearch',
        },
        text: {
          type: 'mrkdwn',
          text: `Hello, <@${message.user}>Let me help you find some channels.`,
        },
        type: 'section',
      },
      { type: 'divider' },
      { text: { type: 'mrkdwn', text: '*Channels*' }, type: 'section' },
      {
        text: {
          type: 'mrkdwn',
          text: '*house-ravenclaw*\nDiscuss Ravenclaw business',
        },
        type: 'section',
      },
      {
        type: 'context',
        elements: [
          {
            type: 'mrkdwn',
            text:
              '120 members\nLast post: <!date^1575643433^{date_pretty}|1575643433>',
          },
        ],
      },
      {
        type: 'actions',
        elements: [
          {
            type: 'button',
            text: { type: 'plain_text', text: ':thumbsup:', emoji: true },
            action_id: 'thumbsUp',
            value: 'house-ravenclaw',
          },
          {
            type: 'button',
            text: { type: 'plain_text', text: ':thumbsdown:', emoji: true },
            action_id: 'thumbsDown',
            value: 'house-ravenclaw',
          },
        ],
      },
    ],
  });
});

Limits

The Slack API fails completely if all of the various fields don't meet the length requirements outlined in their documentation. This is a lot to keep track of, so this package makes an attempt at sane defaults for cutting off content so that your requests to the Slack APIs won't error, even if you have very long dynamic content.

Depending on the type of the field being limited, different default limit behavior is applied.

Defaults

There are three primary functions applied, depending on the field type: truncate, ellipsis, or disallow.

  • truncate cuts the field at the maximum. Examples:
    • List of checkboxes - maximum 10 via Slack API; if you try to builds blocks with 15 we'll give you back just the first 10)
    • URLs - in many blocks maximum 3000 characters, but if above that we just cut off the end
  • ellipsis happens for most text fields - titles, descriptions, placeholders, etc.
    • title, descriptions, placeholders, etc.
    • automatically processes text elements ({ text: '<string>'}) as well as string fields
  • disallow happens for any field that is typically programmatic and the value is required to stay the same for app functionality
    • block IDs, action IDs, etc.

For example, on an Option composition object, here are the functions applied by default and the limits for the fields.

const optionLimits: LimitOpts = {
  text: [75, ellipsis],
  value: [75, disallow],
  description: [75, ellipsis],
  url: [3000, truncate],
};

So, the text field has a maximum length of 75. If the provided text in building the block is greater than 75, then the text field is 'limited' via the ellipsis function.

// { text: 'text', value: 'value' }
Option('text', 'value');

// { text: '<first 73 characters> …', value: 'value' }
Option('<80 character text>', 'value');

// Throws
Option('text', '<80 character id value>');

Overriding

You can override the applied functions in most blocks by using the LimiterFuncs argument. This involves passing an object mapping of fields (strings) to functions. Provided functions include truncate, ellipsis, disallow, and identity.

For example, if you didn't want it to throw on a value being too long, you could truncate that field instead of disallowing:

// map the `value` field to the truncate function instead of the default disallow
Option('title', dynamicText, undefined, { value: truncate });

Custom Limiter Functions

In the same way that you can override, you can also provide your own custom functions (e.g. parsing URLs and removing query parameters, showing the last 10 instead of the first 10 in an array...).

The limiter function is passed two values - the limit (number) for the field in context and the string or array on which the check is done.

For example, to provide a function that just rendered an error string for text that is too long, you could do this:

Option(dynamicText, 'value', undefined, {
  text: (limit, dynamicText) =>
    `ERR: TOO LONG. LONGER than (${limit}): ${dynamicText.substring(0, 10)}...`,
});

Limiter function signature: <T>(limit: number, value: T) => T. Be sure to return a value that is the same type as the value passed - text element, string, option object array, etc. depending on the field.

Note that this function only gets called when the passed dynamicText is greater than the limit. Also, if you end up returning a value under the limit, the block could break when the API call is made to Slack (since Slack will refuse the request).

Nested Limits

For some blocks, rules are applied to nested blocks in specific fields. For example, the Section block fields field requires that each text object in fields has a maximum text length of 2000.

In cases such as this, you can override the default limiter function by providing a tuple instead of just a value limiter: [valLimiter, eachLimiters], where eachLimiters is another mapping of Limiters. For example, to truncate at 2000 characters instead of ellipsis for fields on a section, you could do:

Section(
  {
    text: PlainText(text),
    fields: [{ text: dynamicText }],
  },
  {
    fields: [truncate, { text: truncate }],
  },
);