detritus

Utils.Markupnamespace

Format and escape text with Discord's markdown.

On this page
import { Utils } from 'detritus-client';

Utils.Markup;
12 interfaces · 4 variables · 16 functions

Interfaces

Object shapes for options, payloads and events.

Functions

Standalone helpers.

Variables

Exported values.

escape

variable const src/utils/markup.ts:407
const escape: Readonly<{ all: (text: string, options?: MarkupFilterOptions) => string; bold: (text: string, options?: MarkupFilterOptions) => string; codeblock: (text: string, options?: CodeblockFilterOptions) => string; codestring: (text: string, options?: MarkupFilterOptions) => string; header: (text: string, options?: HeaderFilterOptions) => string; italics: (text: string, options?: MarkupFilterOptions) => string; links: (text: string, replacement?: string) => string; list: (text: string, options?: ListFilterOptions) => string; mentions: (text: string, replacement?: string) => string; quote: (text: string, options?: QuoteFilterOptions) => string; spoiler: (text: string, options?: MarkupFilterOptions) => string; strike: (text: string, options?: MarkupFilterOptions) => string; … 3 more }>
export const escape = Object.freeze({
  all: (text: string, options: MarkupFilterOptions = {}): string => {
    const filter: MarkupFilter = Object.assign({}, defaultMarkupFilter, options);

    text = text.replace(Regexes[Strings.ESCAPE], Replacements[Strings.ESCAPE]);
    text = text.replace(Regexes[Strings.ITALICS], Replacements[Strings.ITALICS]);
    text = text.replace(Regexes[Strings.BOLD], Replacements[Strings.BOLD]);
    text = text.replace(Regexes[Strings.CODESTRING], Replacements[Strings.CODESTRING]);
    text = text.replace(Regexes[Strings.SPOILER], Replacements[Strings.SPOILER]);
    text = text.replace(Regexes[Strings.STRIKE], Replacements[Strings.STRIKE]);
    text = text.replace(Regexes[Strings.UNDERLINE], Replacements[Strings.UNDERLINE]);

    text = text.replace(Regexes[Strings.QUOTE], Replacements[Strings.QUOTE]);
    text = text.replace(Regexes[Strings.SUBTEXT], Replacements[Strings.SUBTEXT]); // this includes header
    // `\-`/`\*` for bullets, numbered ones escape the dot (`1\.`), a backslash before a digit isn't an escape and would show
    text = text.replace(Regexes.LIST_LINE_START, (match: string, space: string, marker: string) => {
      return (marker.endsWith('.')) ? `${space}${marker.slice(0, -1)}\\.` : `${space}\\${marker}`;
    });

    if (filter.links) {
      text = escape.links(text, filter.mentionEscapeCharacter);
    }
    if (filter.mentions) {
      text = escape.mentions(text, filter.mentionEscapeCharacter);
    }
    return trueSlice(text, filter.limit);
  },
  bold: (text: string, options: MarkupFilterOptions = {}): string => {
    const filter: MarkupFilter = Object.assign({}, defaultBoldFilter, options);

    // backslashes first, a trailing one would escape the closing marker
    text = text.replace(Regexes[Strings.ESCAPE], Replacements[Strings.ESCAPE]);
    text = text.replace(Regexes[Strings.BOLD], filter.replacement);
    if (filter.mentions) {
      text = escape.mentions(text, filter.mentionEscapeCharacter);
    }
    return trueSlice(text, filter.limit);
  },
  codeblock: (text: string, options: CodeblockFilterOptions = {}): string => {
    const filter: CodeblockFilter = Object.assign({}, defaultCodeblockFilter, options);

    while (text.includes(Strings.CODEBLOCK)) {
      text = text.replace(Regexes[Strings.CODEBLOCK], filter.replacement);
    }

    if (options.limit === undefined) {
      filter.limit -= filter.language.length;
    }
    return trueSlice(text, filter.limit);
  },
  codestring: (text: string, options: MarkupFilterOptions = {}): string => {
    const filter: MarkupFilter = Object.assign({}, defaultCodestringFilter, options);

    text = text.replace(Regexes[Strings.CODESTRING], filter.replacement);
    if (filter.mentions) {
      text = escape.mentions(text, filter.mentionEscapeCharacter);
    }
    return trueSlice(text, filter.limit);
  },
  header: (text: string, options: HeaderFilterOptions = {}): string => {
    const filter: HeaderFilter = Object.assign({}, defaultHeaderFilter, options);

    text = text.replace(Regexes[Strings.HEADER], filter.replacement);
    if (!filter.multiline) {
      text = text.replace(/\n/g, ' ');
    }
    if (filter.mentions) {
      text = escape.mentions(text, filter.mentionEscapeCharacter);
    }
    return trueSlice(text, filter.limit);
  },
  italics: (text: string, options: MarkupFilterOptions = {}): string => {
    const filter: MarkupFilter = Object.assign({}, defaultItalicsFilter, options);

    // backslashes first, a trailing one would escape the closing marker
    text = text.replace(Regexes[Strings.ESCAPE], Replacements[Strings.ESCAPE]);
    text = text.replace(Regexes[Strings.ITALICS], filter.replacement);
    if (filter.mentions) {
      text = escape.mentions(text, filter.mentionEscapeCharacter);
    }
    return trueSlice(text, filter.limit);
  },
  links: (text: string, replacement: string = Replacements.MENTION): string => {
    text = text.replace(Regexes.LINK, `]${replacement}(`);
    return text;
  },
  list: (text: string, options: ListFilterOptions = {}): string => {
    const filter: ListFilter = Object.assign({}, defaultListFilter, options);

    text = text.replace(Regexes[Strings.LIST], filter.replacement);
    if (!filter.multiline) {
      text = text.replace(/\n/g, ' ');
    }
    if (filter.mentions) {
      text = escape.mentions(text, filter.mentionEscapeCharacter);
    }
    return trueSlice(text, filter.limit);
  },
  mentions: (text: string, replacement: string = Replacements.MENTION): string => {
    text = text.replace(Regexes.MENTION_HARDCORE, `@${replacement}`);
    return text;
  },
  quote: (text: string, options: QuoteFilterOptions = {}): string => {
    const filter: QuoteFilter = Object.assign({}, defaultQuoteFilter, options);

    text = text.replace(Regexes[Strings.QUOTE], filter.replacement);
    if (!filter.multiline) {
      text = text.replace(/\n/g, ' ');
    }
    if (filter.mentions) {
      text = escape.mentions(text, filter.mentionEscapeCharacter);
    }
    return trueSlice(text, filter.limit);
  },
  spoiler: (text: string, options: MarkupFilterOptions = {}): string => {
    const filter: MarkupFilter = Object.assign({}, defaultSpoilerFilter, options);

    // backslashes first, a trailing one would escape the closing marker
    text = text.replace(Regexes[Strings.ESCAPE], Replacements[Strings.ESCAPE]);
    text = text.replace(Regexes[Strings.SPOILER], filter.replacement);
    if (filter.mentions) {
      text = escape.mentions(text, filter.mentionEscapeCharacter);
    }
    return trueSlice(text, filter.limit);
  },
  strike: (text: string, options: MarkupFilterOptions = {}): string => {
    const filter: MarkupFilter = Object.assign({}, defaultStrikeFilter, options);

    // backslashes first, a trailing one would escape the closing marker
    text = text.replace(Regexes[Strings.ESCAPE], Replacements[Strings.ESCAPE]);
    text = text.replace(Regexes[Strings.STRIKE], filter.replacement);
    if (filter.mentions) {
      text = escape.mentions(text, filter.mentionEscapeCharacter);
    }
    return trueSlice(text, filter.limit);
  },
  subtext: (text: string, options: SubtextFilterOptions = {}): string => {
    const filter: SubtextFilter = Object.assign({}, defaultSubtextFilter, options);

    text = text.replace(Regexes[Strings.SUBTEXT], filter.replacement);
    if (!filter.multiline) {
      text = text.replace(/\n/g, ' ');
    }
    if (filter.mentions) {
      text = escape.mentions(text, filter.mentionEscapeCharacter);
    }
    return trueSlice(text, filter.limit);
  },
  underline: (text: string, options: MarkupFilterOptions = {}): string => {
    const filter: MarkupFilter = Object.assign({}, defaultUnderlineFilter, options);

    // backslashes first, a trailing one would escape the closing marker
    text = text.replace(Regexes[Strings.ESCAPE], Replacements[Strings.ESCAPE]);
    text = text.replace(Regexes[Strings.UNDERLINE], filter.replacement);
    if (filter.mentions) {
      text = escape.mentions(text, filter.mentionEscapeCharacter);
    }
    return trueSlice(text, filter.limit);
  },
  url: (text: string, options: MarkupFilterOptions = {}): string => {
    text = text.replace(Regexes.URL, (match: string) => {
      return '%' + match.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0');
    });
    return text;
  },
});

Regexes

variable const src/utils/markup.ts:20
const Regexes: Readonly<{ _: RegExp; __: RegExp; -: RegExp; -#: RegExp; **: RegExp; \: RegExp; #: RegExp; `: RegExp; ```: RegExp; >: RegExp; ||: RegExp; ~~: RegExp; … 7 more }>
export const Regexes = Object.freeze({
  [Strings.BOLD]: /\*\*/g,
  [Strings.CODEBLOCK]: new RegExp(Strings.CODEBLOCK, 'g'),
  [Strings.CODESTRING]: new RegExp(Strings.CODESTRING, 'g'),
  [Strings.ESCAPE]: /\\/g,
  // `m` so every line's start gets escaped, not just the text's
  [Strings.HEADER]: /^(\s*)#/gm,
  [Strings.ITALICS]: /(_|\*)/g,
  [Strings.LIST]: /(-|\*)/g,
  [Strings.QUOTE]: />/g,
  [Strings.SPOILER]: /\|\|/g,
  [Strings.STRIKE]: new RegExp(Strings.STRIKE, 'g'),
  [Strings.SUBTEXT]: /^(\s*)(-?#)/gm,
  [Strings.UNDERLINE]: new RegExp(Strings.UNDERLINE, 'g'),
  EVERYONE: /@(everyone|here)/g,
  LINK: /\]\(/g,
  LINK_TEXT: /[\[\]]/g,
  // list markers at the start of a line (`- `, `* `, `1. `)
  LIST_LINE_START: /^(\s*)([-*]|\d+\.)(?=\s)/gm,
  MENTION: /<@([!&]?[0-9]{16,21})>/g,
  MENTION_HARDCORE: /@/g,
  // anything that would end the link's url early
  URL: /[()\s]/g,
});

Replacements

variable const src/utils/markup.ts:45
const Replacements: Readonly<{ _: "\\$1"; __: "\\_\\_"; -: "\\$1"; -#: "$1\\$2"; **: "\\*\\*"; \: "\\\\"; #: "$1\\#"; `: "\\`"; ```: "``​`"; >: "\\>"; ||: "\\|\\|"; ~~: "\\~\\~"; … 2 more }>
export const Replacements = Object.freeze({
  [Strings.BOLD]: '\\*\\*',
  [Strings.CODEBLOCK]: '``\u200b`',
  [Strings.CODESTRING]: '\\`',
  [Strings.ESCAPE]: '\\\\',
  LINK_TEXT: '\\$&',
  [Strings.HEADER]: '$1\\#',
  [Strings.ITALICS]: '\\$1',
  [Strings.LIST]: '\\$1',
  [Strings.QUOTE]: '\\>',
  [Strings.SPOILER]: '\\|\\|',
  [Strings.STRIKE]: '\\~\\~',
  [Strings.SUBTEXT]: '$1\\$2',
  [Strings.UNDERLINE]: '\\_\\_',
  MENTION: '\u200b',
});

Strings

variable const src/utils/markup.ts:4
const Strings: Readonly<{ BOLD: "**"; CODEBLOCK: "```"; CODESTRING: "`"; CODESTRING_DOUBLE: "``"; ESCAPE: "\\"; HEADER: "#"; ITALICS: "_"; LIST: "-"; QUOTE: ">"; SPOILER: "||"; STRIKE: "~~"; SUBTEXT: "-#"; … 1 more }>
export const Strings = Object.freeze({
  BOLD: '**',
  CODEBLOCK: '```',
  CODESTRING: '`',
  CODESTRING_DOUBLE: '``',
  ESCAPE: '\\',
  HEADER: '#',
  ITALICS: '_',
  LIST: '-',
  QUOTE: '>',
  SPOILER: '||',
  STRIKE: '~~',
  SUBTEXT: '-#',
  UNDERLINE: '__',
});