remirror/remirror

View on GitHub
packages/remirror__extension-react-tables/src/table-plugins.ts

Summary

Maintainability
A
0 mins
Test Coverage
F
31%
import { css } from '@emotion/css';
import {
  EditorState,
  findParentNodeOfType,
  FindProsemirrorNodeResult,
  ProsemirrorPlugin,
} from '@remirror/core';
import { Plugin, PluginKey, Transaction } from '@remirror/pm/state';
import { Decoration, DecorationSet } from '@remirror/pm/view';
import { ExtensionTablesTheme, getThemeVar } from '@remirror/theme';

import { InsertButtonAttrs } from './components/table-insert-button';

const preselectBorderColor = getThemeVar('color', 'table', 'preselect', 'border');
const preselectControllerBackgroundColor = getThemeVar('color', 'table', 'preselect', 'controller');

export function getTableStyle(attrs: ControllerStateValues): string {
  const preselectClass = css`
    /* Make the border-style 'double' instead of 'solid'. This works because 'double' has a higher priority than 'solid' */
    border-style: double;
    border-color: ${preselectBorderColor};
  `;

  const preselectControllerClass = css`
    ${preselectClass}
    background-color: ${preselectControllerBackgroundColor};
  `;

  let classNames = '';

  if (attrs.preselectColumn !== -1) {
    classNames = css`
      & table.${ExtensionTablesTheme.TABLE} tbody tr {
        th,
        td {
          &:nth-child(${attrs.preselectColumn + 1}) {
            ${preselectClass};
          }
        }
        th.${ExtensionTablesTheme.TABLE_CONTROLLER}:nth-child(${attrs.preselectColumn + 1}) {
          ${preselectControllerClass}
        }
      }
    `;
  } else if (attrs.preselectRow !== -1) {
    classNames = css`
      & table.${ExtensionTablesTheme.TABLE} tbody tr:nth-child(${attrs.preselectRow + 1}) {
        td,
        th {
          ${preselectClass};
        }
        th.${ExtensionTablesTheme.TABLE_CONTROLLER} {
          ${preselectControllerClass}
        }
      }
    `;
  } else if (attrs.preselectTable) {
    classNames = css`
      &.${ExtensionTablesTheme.TABLE_PRESELECT_ALL} table.${ExtensionTablesTheme.TABLE} tbody tr {
        td,
        th {
          ${preselectClass};
        }
        th.${ExtensionTablesTheme.TABLE_CONTROLLER} {
          ${preselectControllerClass}
        }
      }
    `;
  }

  return classNames;
}

const key = new PluginKey<ControllerState>('remirrorTableControllerPluginKey');

export { key as tableControllerPluginKey };

export function createTableControllerPlugin(): ProsemirrorPlugin<ControllerState> {
  return new Plugin<ControllerState>({
    key: key,
    state: {
      init() {
        return new ControllerState({});
      },
      apply(tr, prev: ControllerState) {
        return prev.apply(tr);
      },
    },
    props: {
      decorations: (state: EditorState) => {
        const controllerState = key.getState(state);

        if (!controllerState) {
          return null;
        }

        const { tableNodeResult, predelete, preselectTable } = controllerState.values;

        if (tableNodeResult) {
          const styleClassName = getTableStyle(controllerState.values);
          let className = `${ExtensionTablesTheme.TABLE_SHOW_CONTROLLERS} ${styleClassName}`;

          if (preselectTable) {
            className += ` ${ExtensionTablesTheme.TABLE_PRESELECT_ALL}`;
          }

          if (predelete) {
            className += ` ${ExtensionTablesTheme.TABLE_SHOW_PREDELETE}`;
          }

          const decorations = [
            Decoration.node(tableNodeResult.pos, tableNodeResult.end, {
              class: className,
            }),
          ];
          return DecorationSet.create(state.doc, decorations);
        }

        return null;
      },
    },
  });
}

interface ControllerStateValues {
  tableNodeResult: FindProsemirrorNodeResult | null | undefined; // the table that contains current selection.
  preselectTable: boolean; // if this value is true, all table cells will have a "preselect" style.
  preselectColumn: number; // if this value is not -1, all cells in this table column will have a "preselect" style.
  preselectRow: number; // if this value is not -1, all cells in this table row will have a "preselect" style.
  predelete: boolean; // if this value is true, all selected cells will have a "predelete" style.
  insertButtonAttrs: InsertButtonAttrs | null; // if and only if `insertButtonAttrs` exists, InsertButton will show.
}

type ControllerStateProps = Omit<Partial<ControllerStateValues>, 'tableNodeResult'>;

class ControllerState {
  public values: ControllerStateValues;

  constructor(public action: ControllerStateProps) {
    this.values = {
      tableNodeResult: null,
      preselectTable: false,
      preselectColumn: -1,
      preselectRow: -1,
      predelete: false,
      insertButtonAttrs: null,
      ...action,
    };
  }

  apply(tr: Transaction): ControllerState {
    this.values.tableNodeResult = findParentNodeOfType({
      types: 'table',
      selection: tr.selection,
    });

    const props: ControllerStateProps | null = tr.getMeta(key);

    if (props) {
      return new ControllerState({ ...this.values, ...props });
    }

    return this;
  }
}

export function setControllerPluginMeta(tr: Transaction, props: ControllerStateProps): Transaction {
  return tr.setMeta(key, props);
}

export function resetControllerPluginMeta(tr: Transaction): Transaction {
  return setControllerPluginMeta(tr, {
    preselectRow: -1,
    preselectColumn: -1,
    preselectTable: false,
    predelete: false,
  });
}