Files
outline/shared/editor/nodes/Table.ts
dependabot[bot] fbd16d4b9a chore(deps-dev): bump prettier from 2.1.2 to 2.8.8 (#5372)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2023-05-22 19:14:56 -07:00

202 lines
5.6 KiB
TypeScript

import { NodeSpec, Node as ProsemirrorNode, Schema } from "prosemirror-model";
import { EditorState, Plugin, TextSelection } from "prosemirror-state";
import {
addColumnAfter,
addColumnBefore,
deleteColumn,
deleteRow,
deleteTable,
goToNextCell,
isInTable,
tableEditing,
toggleHeaderCell,
toggleHeaderColumn,
toggleHeaderRow,
CellSelection,
} from "prosemirror-tables";
import {
addRowAt,
createTable,
getCellsInColumn,
moveRow,
setTextSelection,
} from "prosemirror-utils";
import { Decoration, DecorationSet } from "prosemirror-view";
import { MarkdownSerializerState } from "../lib/markdown/serializer";
import { getRowIndexFromText } from "../queries/getRowIndex";
import tablesRule from "../rules/tables";
import { Dispatch } from "../types";
import Node from "./Node";
export default class Table extends Node {
get name() {
return "table";
}
get schema(): NodeSpec {
return {
content: "tr+",
tableRole: "table",
isolating: true,
group: "block",
parseDOM: [{ tag: "table" }],
toDOM() {
return [
"div",
{ class: "scrollable-wrapper table-wrapper" },
[
"div",
{ class: "scrollable" },
["table", { class: "rme-table" }, ["tbody", 0]],
],
];
},
};
}
get rulePlugins() {
return [tablesRule];
}
commands({ schema }: { schema: Schema }) {
return {
createTable:
({ rowsCount, colsCount }: { rowsCount: number; colsCount: number }) =>
(state: EditorState, dispatch: Dispatch) => {
const offset = state.tr.selection.anchor + 1;
const nodes = createTable(schema, rowsCount, colsCount);
const tr = state.tr.replaceSelectionWith(nodes).scrollIntoView();
const resolvedPos = tr.doc.resolve(offset);
tr.setSelection(TextSelection.near(resolvedPos));
dispatch(tr);
return true;
},
setColumnAttr:
({ index, alignment }: { index: number; alignment: string }) =>
(state: EditorState, dispatch: Dispatch) => {
const cells = getCellsInColumn(index)(state.selection) || [];
let transaction = state.tr;
cells.forEach(({ pos }) => {
transaction = transaction.setNodeMarkup(pos, undefined, {
alignment,
});
});
dispatch(transaction);
return true;
},
addColumnBefore: () => addColumnBefore,
addColumnAfter: () => addColumnAfter,
deleteColumn: () => deleteColumn,
addRowAfter:
({ index }: { index: number }) =>
(state: EditorState, dispatch: Dispatch) => {
if (index === 0) {
// A little hack to avoid cloning the heading row by cloning the row
// beneath and then moving it to the right index.
const tr = addRowAt(index + 2, true)(state.tr);
dispatch(moveRow(index + 2, index + 1)(tr));
} else {
dispatch(addRowAt(index + 1, true)(state.tr));
}
return true;
},
deleteRow: () => deleteRow,
deleteTable: () => deleteTable,
toggleHeaderColumn: () => toggleHeaderColumn,
toggleHeaderRow: () => toggleHeaderRow,
toggleHeaderCell: () => toggleHeaderCell,
};
}
keys() {
return {
Tab: goToNextCell(1),
"Shift-Tab": goToNextCell(-1),
Enter: (state: EditorState, dispatch: Dispatch) => {
if (!isInTable(state)) {
return false;
}
const index = getRowIndexFromText(
state.selection as unknown as CellSelection
);
if (index === 0) {
const cells = getCellsInColumn(0)(state.selection);
if (!cells) {
return false;
}
const tr = addRowAt(index + 2, true)(state.tr);
dispatch(
setTextSelection(cells[1].pos)(moveRow(index + 2, index + 1)(tr))
);
} else {
dispatch(addRowAt(index + 1, true)(state.tr));
}
return true;
},
};
}
toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) {
state.renderTable(node);
state.closeBlock(node);
}
parseMarkdown() {
return { block: "table" };
}
get plugins() {
return [
tableEditing(),
new Plugin({
props: {
decorations: (state) => {
const { doc } = state;
const decorations: Decoration[] = [];
let index = 0;
doc.descendants((node, pos) => {
if (node.type.name !== this.name) {
return;
}
const elements = document.getElementsByClassName("rme-table");
const table = elements[index];
if (!table) {
return;
}
const element = table.parentElement;
const shadowRight = !!(
element && element.scrollWidth > element.clientWidth
);
if (shadowRight) {
decorations.push(
Decoration.widget(
pos + 1,
() => {
const shadow = document.createElement("div");
shadow.className = "scrollable-shadow right";
return shadow;
},
{
key: "table-shadow-right",
}
)
);
}
index++;
});
return DecorationSet.create(doc, decorations);
},
},
}),
];
}
}