remirror/remirror

View on GitHub
packages/remirror__extension-text-highlight/src/text-highlight-extension.ts

Summary

Maintainability
A
30 mins
Test Coverage
F
16%
import {
  ApplySchemaAttributes,
  command,
  CommandFunction,
  extension,
  ExtensionPriority,
  ExtensionTag,
  isElementDomNode,
  isString,
  joinStyles,
  Mark,
  MarkExtension,
  MarkExtensionSpec,
  MarkSpecOverride,
  omitExtraAttributes,
  PrimitiveSelection,
  Static,
} from '@remirror/core';
import { Palette, palette } from '@remirror/extension-text-color';
import { ExtensionTextHighlightMessages as Messages } from '@remirror/messages';

const setTextHighlightOptions: Remirror.CommandDecoratorOptions = {
  icon: 'markPenLine',
  description: ({ t }) => t(Messages.DESCRIPTION),
  label: ({ t }) => t(Messages.LABEL),
};

const TEXT_HIGHLIGHT_ATTRIBUTE = 'data-text-highlight-mark';

/**
 * Add a highlight color to the selected text (or text within a specified
 * range).
 */
@extension<TextHighlightOptions>({
  defaultOptions: {
    defaultHighlight: '',
    palette,
  },
  staticKeys: ['defaultHighlight'],
})
export class TextHighlightExtension extends MarkExtension<TextHighlightOptions> {
  get name() {
    return 'textHighlight' as const;
  }

  createTags() {
    return [ExtensionTag.FormattingMark, ExtensionTag.FontStyle];
  }

  createMarkSpec(extra: ApplySchemaAttributes, override: MarkSpecOverride): MarkExtensionSpec {
    return {
      ...override,
      attrs: {
        ...extra.defaults(),
        highlight: { default: this.options.defaultHighlight },
      },
      parseDOM: [
        {
          tag: `span[${TEXT_HIGHLIGHT_ATTRIBUTE}]`,
          getAttrs: (dom) => {
            if (!isElementDomNode(dom)) {
              return null;
            }

            const highlight = dom.getAttribute(TEXT_HIGHLIGHT_ATTRIBUTE);

            if (!highlight) {
              return null;
            }

            return { ...extra.parse(dom), highlight };
          },
        },
        {
          tag: `span[${TEXT_HIGHLIGHT_ATTRIBUTE}]`,
          getAttrs: (dom) => {
            if (!isElementDomNode(dom)) {
              return null;
            }

            const highlight = dom.getAttribute(TEXT_HIGHLIGHT_ATTRIBUTE);

            if (!highlight) {
              return null;
            }

            return { ...extra.parse(dom), highlight };
          },
        },
        {
          // Get the color from the css style property. This is useful for pasted content.
          style: 'background-color',
          priority: ExtensionPriority.Low,
          getAttrs: (highlight) => {
            if (!isString(highlight)) {
              return null;
            }

            return { highlight };
          },
        },
        ...(override.parseDOM ?? []),
      ],
      toDOM: (mark: Mark) => {
        const { highlight, ...other } = omitExtraAttributes<TextHighlightAttributes>(
          mark.attrs,
          extra,
        );
        const extraAttrs = extra.dom(mark);
        let style = extraAttrs.style;

        if (highlight) {
          style = joinStyles({ backgroundColor: highlight }, style);
        }

        return [
          'mark',
          { ...other, ...extraAttrs, style, [TEXT_HIGHLIGHT_ATTRIBUTE]: highlight },
          0,
        ];
      },
    };
  }

  /**
   * Set the text highlight color value for the selected text.
   */
  @command(setTextHighlightOptions)
  setTextHighlight(highlight: string, options?: HighlightCommandOptions): CommandFunction {
    return this.store.commands.applyMark.original(this.type, { highlight }, options?.selection);
  }

  /**
   * Remove the highlight mark from the selection.
   */
  @command()
  removeTextHighlight(options?: HighlightCommandOptions): CommandFunction {
    return this.store.commands.removeMark.original({ type: this.type, ...options, expand: true });
  }
}

export interface TextHighlightOptions {
  /**
   * The default highlight value.
   *
   * @defaultValue ''
   */
  defaultHighlight?: Static<string>;

  /**
   * The color palette which is a function that returns a list of colors and
   * labels for help with ui. It is completely optional and you are free to use
   * use whatever colors you choose.
   */
  palette?: Palette;
}

export interface TextHighlightAttributes {
  /**
   * The highlight color.
   *
   * @defaultValue ''
   */
  highlight?: string;
}

interface HighlightCommandOptions {
  selection?: PrimitiveSelection;
}

declare global {
  namespace Remirror {
    interface AllExtensions {
      textHighlight: TextHighlightExtension;
    }
  }
}