@atlaskit/editor-core
Version:
A package contains Atlassian editor core functionality
171 lines (152 loc) • 5.6 kB
text/typescript
import {
EditorState,
Selection,
TableMap,
Transaction,
CellSelection,
TextSelection
} from '../../prosemirror';
import * as tableBaseCommands from '../../prosemirror/prosemirror-tables';
import { stateKey } from './';
import { createTableNode, isIsolating } from './utils';
import { analyticsService } from '../../analytics';
export interface Command {
(state: EditorState<any>, dispatch?: (tr: Transaction) => void): boolean;
}
const TAB_FORWARD_DIRECTION = 1;
const TAB_BACKWARD_DIRECTION = -1;
const createTable = (): Command => {
return (state: EditorState<any>, dispatch: (tr: Transaction) => void): boolean => {
const pluginState = stateKey.getState(state);
if (pluginState.tableDisabled || pluginState.tableElement) {
return false;
}
pluginState.focusEditor();
const table = createTableNode(3, 3, state.schema);
const tr = state.tr.replaceSelectionWith(table);
tr.setSelection(Selection.near(tr.doc.resolve(state.selection.from)));
dispatch(tr.scrollIntoView());
return true;
};
};
const goToNextCell = (direction: number): Command => {
return (state: EditorState<any>, dispatch: (tr: Transaction) => void): boolean => {
const pluginState = stateKey.getState(state);
if (!pluginState.tableNode) {
return false;
}
const offset = pluginState.tableStartPos();
if (!offset) {
return false;
}
const map = TableMap.get(pluginState.tableNode);
const start = pluginState.getCurrentCellStartPos();
const firstCellPos = map.positionAt(0, 0, pluginState.tableNode) + offset + 1;
const lastCellPos = map.positionAt(map.height - 1, map.width - 1, pluginState.tableNode) + offset + 1;
const event = direction === TAB_FORWARD_DIRECTION ? 'next_cell' : 'previous_cell';
analyticsService.trackEvent(`atlassian.editor.format.table.${event}.keyboard`);
if (firstCellPos === start && direction === TAB_BACKWARD_DIRECTION) {
pluginState.insertRow(0);
return true;
}
if (lastCellPos === start && direction === TAB_FORWARD_DIRECTION) {
pluginState.insertRow(map.height);
return true;
}
if (!pluginState.view.hasFocus()) {
pluginState.view.focus();
}
const result = tableBaseCommands.goToNextCell(direction)(state, dispatch);
// cancel text selection that is created by default
if (result) {
const latestState = pluginState.view.state;
dispatch(latestState.tr.setSelection(Selection.near(latestState.selection.$from)));
}
return result;
};
};
const cut = (): Command => {
return (state: EditorState<any>, dispatch: (tr: Transaction) => void): boolean => {
const pluginState = stateKey.getState(state);
pluginState.closeFloatingToolbar();
return true;
};
};
const copy = (): Command => {
return (state: EditorState<any>, dispatch: (tr: Transaction) => void): boolean => {
const pluginState = stateKey.getState(state);
pluginState.closeFloatingToolbar();
return true;
};
};
const paste = (): Command => {
return (state: EditorState<any>, dispatch: (tr: Transaction) => void): boolean => {
const pluginState = stateKey.getState(state);
pluginState.closeFloatingToolbar();
return true;
};
};
const emptyCells = (): Command => {
return (state: EditorState<any>, dispatch: (tr: Transaction) => void): boolean => {
const pluginState = stateKey.getState(state);
if (!pluginState.cellSelection) {
return false;
}
pluginState.resetHoverSelection();
pluginState.emptySelectedCells();
const { $head: { pos, parentOffset } } = state.selection as CellSelection;
const newPos = pos - parentOffset;
pluginState.moveCursorInsideTableTo(newPos);
analyticsService.trackEvent('atlassian.editor.format.table.delete_content.keyboard');
return true;
};
};
const moveCursorBackward = (): Command => {
return (state: EditorState<any>, dispatch: (tr: Transaction) => void): boolean => {
const pluginState = stateKey.getState(state);
const { $cursor } = state.selection as TextSelection;
// if cursor is in the middle of a text node, do nothing
if (!$cursor || (pluginState.view ? !pluginState.view.endOfTextblock('backward', state) : $cursor.parentOffset > 0)) {
return false;
}
// find the node before the cursor
let before;
let cut;
if (!isIsolating($cursor.parent)) {
for (let i = $cursor.depth - 1; !before && i >= 0; i--) {
if ($cursor.index(i) > 0) {
cut = $cursor.before(i + 1);
before = $cursor.node(i).child($cursor.index(i) - 1);
}
if (isIsolating($cursor.node(i))) {
break;
}
}
}
// if the node before is not a table node - do nothing
if (!before || before.type !== state.schema.nodes.table) {
return false;
}
const { tr } = state;
const lastCellPos = cut - 4;
// need to move cursor inside the table to be able to calculate table's offset
tr.setSelection(new TextSelection(state.doc.resolve(lastCellPos)));
const { $from } = tr.selection;
const start = $from.start(-1);
const pos = start + $from.parent.nodeSize - 1;
// move cursor to the last cell
// it doesn't join node before (last cell) with node after (content after the cursor)
// due to ridiculous amount of PM code that would have been required to overwrite
dispatch(tr.setSelection(new TextSelection(state.doc.resolve(pos))));
return true;
};
};
export default {
createTable,
goToNextCell,
cut,
copy,
paste,
moveCursorBackward,
emptyCells
};