Skip to content
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

Add support for RichText elements group #133

Open
SnapeEye opened this issue Apr 3, 2024 · 5 comments
Open

Add support for RichText elements group #133

SnapeEye opened this issue Apr 3, 2024 · 5 comments
Labels
enhancement New feature or request

Comments

@SnapeEye
Copy link

SnapeEye commented Apr 3, 2024

Is your feature request related to a problem? Please describe.
Currently, it is not possible to create and manage RichText elements as they are not supported by this package.
So in order to use any RichText element (input, section or block), you have to use builder, build to json, parse the result, manually add RichText elements, convert updated entity to json again. This may be used as a workaround (but still a bad one), but when you have to add such elements multiple times - it's pure hell to manage it manually.

Describe the solution you'd like
Add support for RichText (section, input, block, etc.).

Describe alternatives you've considered
You can manually add this (as was described in first section), but I thinkits not even a workaround.

Additional context
So if you only need f.e. to add it to the end of your UI - it may seem OK.
image

But for more complex UI with multiple unsupported elements - it will be pure hell to manage it.

@SnapeEye SnapeEye added the enhancement New feature or request label Apr 3, 2024
@ckrauterlovescoffee
Copy link

Any idea when this may be worked on?

@azmi-plavaga
Copy link

Another related enhancement #85

@tobice
Copy link

tobice commented May 2, 2024

I'd say there is a better workaround available, as the compositional API makes it really easy to fall back into raw JSON if you need it:

function CustomRichText() { 
    return {
        build: () => ({
            type: 'rich_text',
            elements: [{ ... }],
        })
     } 
}

Message().blocks(CustomRichText() as any).buildToObject();

It's possible that this very bare bones implementation will break some advanced usage (I couldn't get the TS types just right; hence the as any) but for now it seems to work just fine.

@benjamincburns
Copy link

benjamincburns commented Jun 9, 2024

I wound up writing some components to support my needs (basic bulleted lists with nesting) for an internal project. If I had to guess, I'd imagine @raycharius (and/or any other maintainers) haven't yet added support because rich text is extremely verbose w.r.t. boilerplate, so it's unlikely to be used unless people absolutely need it. If it were me, I'd want to come up with a builder design that reduces that complexity somewhat for common use cases, otherwise things are likely to get quite unreadable quite quickly in user code.

I'll share the code that I wrote here in case someone wants to use it as a starting point for a PR. Note that I started off using the internal mixins, but I ran into issues and wound up writing the common methods like elements and whatnot myself. I also skipped over setter support in the builder factory functions, and I've probably broken the setter support provided by the base builder type in a couple of my builder constructor overrides.

import {
  Appendable,
  BlockBuilderBase,
  ElementBuilderBase,
  getElementsForContext,
  Settable,
  SlackBlockDto,
  SlackElementDto
} from 'slack-block-builder/dist/internal';

export class RichTextTextElementBuilder extends ElementBuilderBase {
  constructor(options?: { text?: string }) {
    super();
    const { text } = options ?? {};
    this.text(text);
  }

  bold(value: Settable<boolean> = true) {
    return this.set(value, 'bold');
  }

  italic(value: Settable<boolean> = true) {
    return this.set(value, 'italic');
  }

  strike(value: Settable<boolean> = true) {
    return this.set(value, 'strike');
  }

  code(value: Settable<boolean> = true) {
    return this.set(value, 'code');
  }

  text(value: Settable<string>) {
    return this.set(value, 'text');
  }

  build() {
    return this.getResult(SlackElementDto, {
      type: 'text',
      text: this.props.text,
      ...(this.props.bold || this.props.italic || this.props.strike || this.props.code
        ? {
            style: {
              bold: this.props.bold,
              italic: this.props.italic,
              strike: this.props.strike,
              code: this.props.code
            }
          }
        : {})
    });
  }
}

export function RichTextTextElement(options?: { text?: string }) {
  return new RichTextTextElementBuilder(options);
}

export class RichTextSectionBuilder extends ElementBuilderBase {
  elements<T>(...elements: Appendable<T>) {
    return this.append(elements.flat(), 'elements');
  }

  build() {
    return this.getResult(SlackElementDto, {
      type: 'rich_text_section',
      elements: getElementsForContext(this.props.elements)
    });
  }
}

export function RichTextSection() {
  return new RichTextSectionBuilder();
}

export class RichTextListBuilder extends ElementBuilderBase {
  style(style: Settable<'bullet' | 'ordered'>) {
    return this.set(style, 'style');
  }

  indent(indent: Settable<number>) {
    return this.set(indent, 'indent');
  }

  border(border: Settable<number>) {
    return this.set(border, 'border');
  }

  offset(offset: Settable<number>) {
    return this.set(offset, 'offset');
  }

  elements<T>(...elements: Appendable<T>) {
    return this.append(elements.flat(), 'elements');
  }

  build() {
    return this.getResult(SlackElementDto, {
      type: 'rich_text_list',
      style: this.props.style ?? 'bullet',
      indent: this.props.indent,
      border: this.props.border,
      offset: this.props.offset,
      elements: getElementsForContext(this.props.elements)
    });
  }
}

export function RichTextList() {
  return new RichTextListBuilder();
}

export class RichTextBuilder extends BlockBuilderBase {
  blockId(value: Settable<string>) {
    return this.set(value, 'block_id');
  }

  elements<T>(...elements: Appendable<T>) {
    return this.append(elements.flat(), 'elements');
  }

  end() {
    return this;
  }

  build() {
    return this.getResult(SlackBlockDto, {
      type: 'rich_text',
      elements: getElementsForContext(this.props.elements)
    });
  }
}

export function RichText() {
  return new RichTextBuilder();
}

Example usage (simple bulleted list with nesting and text styling):

RichText().elements(
  RichTextSection().elements(
    RichTextTextElement().text('Title of list').bold(),
    RichTextTextElement().text('\n')
  ),
  RichTextList().elements(
    // top-level bullet
    RichTextSection().elements(
      // styled and unstyled text on the same line, just here to show how verbose this stuff is
      RichTextTextElement().text('list item:').bold(),
      RichTextTextElement().text('1')
    ),
    // sub-list
    RichTextSection().elements(
      RichTextList().indent(1).elements(
        RichTextSection().elements(
          RichTextTextElement().text('sub item:').bold(),
          RichTextTextElement().text('1.1')
        ),
        RichTextSection().elements(
          RichTextTextElement().text('sub item:').bold(),
          RichTextTextElement().text('1.2')
        )
      )
    ),
    // top-level bullet
    RichTextSection().elements(
      RichTextTextElement().text('list item:').bold(),
      RichTextTextElement().text('2')
    ),
    // top-level bullet
    RichTextSection().elements(
      RichTextTextElement().text('list item:').bold(),
      RichTextTextElement().text('3')
    )
  )
)

@SnapeEye
Copy link
Author

I wound up writing some components to support my needs (basic bulleted lists with nesting) for an internal project. If I had to guess, I'd imagine @raycharius (and/or any other maintainers) haven't yet added support because rich text is extremely verbose w.r.t. boilerplate, so it's unlikely to be used unless people absolutely need it. If it were me, I'd want to come up with a builder design that reduces that complexity somewhat for common use cases, otherwise things are likely to get quite unreadable quite quickly in user code.

I'll share the code that I wrote here in case someone wants to use it as a starting point for a PR. Note that I started off using the internal mixins, but I ran into issues and wound up writing the common methods like elements and whatnot myself. I also skipped over setter support in the builder factory functions, and I've probably broken the setter support provided by the base builder type in a couple of my builder constructor overrides.

import {
  Appendable,
  BlockBuilderBase,
  ElementBuilderBase,
  getElementsForContext,
  Settable,
  SlackBlockDto,
  SlackElementDto
} from 'slack-block-builder/dist/internal';

export class RichTextTextElementBuilder extends ElementBuilderBase {
  constructor(options?: { text?: string }) {
    super();
    const { text } = options ?? {};
    this.text(text);
  }

  bold(value: Settable<boolean> = true) {
    return this.set(value, 'bold');
  }

  italic(value: Settable<boolean> = true) {
    return this.set(value, 'italic');
  }

  strike(value: Settable<boolean> = true) {
    return this.set(value, 'strike');
  }

  code(value: Settable<boolean> = true) {
    return this.set(value, 'code');
  }

  text(value: Settable<string>) {
    return this.set(value, 'text');
  }

  build() {
    return this.getResult(SlackElementDto, {
      type: 'text',
      text: this.props.text,
      ...(this.props.bold || this.props.italic || this.props.strike || this.props.code
        ? {
            style: {
              bold: this.props.bold,
              italic: this.props.italic,
              strike: this.props.strike,
              code: this.props.code
            }
          }
        : {})
    });
  }
}

export function RichTextTextElement(options?: { text?: string }) {
  return new RichTextTextElementBuilder(options);
}

export class RichTextSectionBuilder extends ElementBuilderBase {
  elements<T>(...elements: Appendable<T>) {
    return this.append(elements.flat(), 'elements');
  }

  build() {
    return this.getResult(SlackElementDto, {
      type: 'rich_text_section',
      elements: getElementsForContext(this.props.elements)
    });
  }
}

export function RichTextSection() {
  return new RichTextSectionBuilder();
}

export class RichTextListBuilder extends ElementBuilderBase {
  style(style: Settable<'bullet' | 'ordered'>) {
    return this.set(style, 'style');
  }

  indent(indent: Settable<number>) {
    return this.set(indent, 'indent');
  }

  border(border: Settable<number>) {
    return this.set(border, 'border');
  }

  offset(offset: Settable<number>) {
    return this.set(offset, 'offset');
  }

  elements<T>(...elements: Appendable<T>) {
    return this.append(elements.flat(), 'elements');
  }

  build() {
    return this.getResult(SlackElementDto, {
      type: 'rich_text_list',
      style: this.props.style ?? 'bullet',
      indent: this.props.indent,
      border: this.props.border,
      offset: this.props.offset,
      elements: getElementsForContext(this.props.elements)
    });
  }
}

export function RichTextList() {
  return new RichTextListBuilder();
}

export class RichTextBuilder extends BlockBuilderBase {
  blockId(value: Settable<string>) {
    return this.set(value, 'block_id');
  }

  elements<T>(...elements: Appendable<T>) {
    return this.append(elements.flat(), 'elements');
  }

  end() {
    return this;
  }

  build() {
    return this.getResult(SlackBlockDto, {
      type: 'rich_text',
      elements: getElementsForContext(this.props.elements)
    });
  }
}

export function RichText() {
  return new RichTextBuilder();
}

Example usage (simple bulleted list with nesting and text styling):

RichText().elements(
  RichTextSection().elements(
    RichTextTextElement().text('Title of list').bold(),
    RichTextTextElement().text('\n')
  ),
  RichTextList().elements(
    // top-level bullet
    RichTextSection().elements(
      // styled and unstyled text on the same line, just here to show how verbose this stuff is
      RichTextTextElement().text('list item:').bold(),
      RichTextTextElement().text('1')
    ),
    // sub-list
    RichTextSection().elements(
      RichTextList().indent(1).elements(
        RichTextSection().elements(
          RichTextTextElement().text('sub item:').bold(),
          RichTextTextElement().text('1.1')
        ),
        RichTextSection().elements(
          RichTextTextElement().text('sub item:').bold(),
          RichTextTextElement().text('1.2')
        )
      )
    ),
    // top-level bullet
    RichTextSection().elements(
      RichTextTextElement().text('list item:').bold(),
      RichTextTextElement().text('2')
    ),
    // top-level bullet
    RichTextSection().elements(
      RichTextTextElement().text('list item:').bold(),
      RichTextTextElement().text('3')
    )
  )
)

Yeah, a really good code sample to start with. It could be expanded to support even more functionality (like user and channel tags), but still it's pretty good. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants