remirror/remirror

View on GitHub
packages/remirror__extension-callout/src/callout-utils.ts

Summary

Maintainability
A
0 mins
Test Coverage
C
70%
import {
  CommandFunction,
  findParentNodeOfType,
  includes,
  isEqual,
  isObject,
  isString,
  NodeType,
  ProsemirrorAttributes,
  ProsemirrorNode,
} from '@remirror/core';
import { ExtensionCalloutMessages } from '@remirror/messages';

import type { CalloutExtensionAttributes } from './callout-types';

export const dataAttributeType = 'data-callout-type';

export const dataAttributeEmoji = 'data-callout-emoji';

/**
 * Check that the attributes exist and are valid for the codeBlock
 * updateAttributes.
 */
export function isValidCalloutExtensionAttributes(
  attributes: ProsemirrorAttributes,
): attributes is CalloutExtensionAttributes {
  if (attributes && isObject(attributes)) {
    const attributesChecklist = Object.entries(attributes).map(([key, value]) => {
      switch (key) {
        case 'type':
          return !!(isString(value) && value.length > 0);

        case 'emoji':
          return !!(isString(value) && value.length > 0);

        default:
          return true;
      }
    });
    return attributesChecklist.every((attr) => !!attr);
  }

  return false;
}

/**
 * Updates the node attrs.
 *
 * This is used to update the type of the callout.
 */
export function updateNodeAttributes(type: NodeType) {
  return (attributes: CalloutExtensionAttributes, pos?: number): CommandFunction =>
    ({ state: { tr, selection, doc }, dispatch }) => {
      if (!isValidCalloutExtensionAttributes(attributes)) {
        throw new Error('Invalid attrs passed to the updateAttributes method');
      }

      const parent = findParentNodeOfType({
        types: type,
        selection: pos ? doc.resolve(pos) : selection,
      });

      if (!parent || isEqual(attributes, parent.node.attrs)) {
        // Do nothing since the attrs are the same
        return false;
      }

      tr.setNodeMarkup(parent.pos, type, {
        ...parent.node.attrs,
        ...attributes,
      });

      if (dispatch) {
        dispatch(tr);
      }

      return true;
    };
}

const { DESCRIPTION, LABEL } = ExtensionCalloutMessages;

export const toggleCalloutOptions: Remirror.CommandDecoratorOptions = {
  icon: ({ attrs }) => {
    switch (attrs?.type as CalloutExtensionAttributes['type']) {
      case 'error':
        return 'closeCircleLine';
      case 'success':
        return 'checkboxCircleLine';
      case 'warning':
        return 'errorWarningLine';
      default:
        return 'informationLine';
    }
  },
  description: ({ t, attrs }) =>
    t({
      ...DESCRIPTION,
      values: {
        type: attrs?.type,
      },
    }),
  label: ({ t, attrs }) =>
    t({
      ...LABEL,
      values: {
        type: attrs?.type,
      },
    }),
};

/**
 * Get the callout type from the provided string.
 */
export function getCalloutType(
  value: string | null | undefined,
  validTypes: string[],
  defaultType: string,
): string {
  return includes(validTypes, value) ? value : defaultType;
}

/**
 * The default emoji render function.
 */
export function defaultEmojiRender(node: ProsemirrorNode): HTMLElement {
  const emoji = document.createElement('span');
  emoji.textContent = node.attrs.emoji;
  return emoji;
}