Files
outline/shared/editor/nodes/TableCell.ts

222 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Token } from "markdown-it";
import { NodeSpec, Slice } from "prosemirror-model";
import { Plugin } from "prosemirror-state";
import { DecorationSet, Decoration } from "prosemirror-view";
import { addRowBefore, selectRow, selectTable } from "../commands/table";
import { getCellAttrs, setCellAttrs } from "../lib/table";
import {
getCellsInColumn,
getRowIndexInMap,
isRowSelected,
isTableSelected,
} from "../queries/table";
import { EditorStyleHelper } from "../styles/EditorStyleHelper";
import { cn } from "../styles/utils";
import Node from "./Node";
export default class TableCell extends Node {
get name() {
return "td";
}
get schema(): NodeSpec {
return {
content: "block+",
tableRole: "cell",
isolating: true,
parseDOM: [{ tag: "td", getAttrs: getCellAttrs }],
toDOM(node) {
return ["td", setCellAttrs(node), 0];
},
attrs: {
colspan: { default: 1 },
rowspan: { default: 1 },
alignment: { default: null },
colwidth: { default: null },
},
};
}
toMarkdown() {
// see: renderTable
}
parseMarkdown() {
return {
block: "td",
getAttrs: (tok: Token) => ({ alignment: tok.info }),
};
}
get plugins() {
function buildAddRowDecoration(pos: number, index: number) {
const className = cn(EditorStyleHelper.tableAddRow, {
first: index === 0,
});
return Decoration.widget(
pos + 1,
() => {
const plus = document.createElement("a");
plus.role = "button";
plus.className = className;
plus.dataset.index = index.toString();
return plus;
},
{
key: cn(className, index),
}
);
}
return [
new Plugin({
props: {
transformCopied: (slice) => {
// check if the copied selection is a single table, with a single row, with a single cell. If so,
// copy the cell content only not a table with a single cell. This leads to more predictable pasting
// behavior, both in and outside the app.
if (slice.content.childCount === 1) {
const table = slice.content.firstChild;
if (
table?.type.spec.tableRole === "table" &&
table.childCount === 1
) {
const row = table.firstChild;
if (
row?.type.spec.tableRole === "row" &&
row.childCount === 1
) {
const cell = row.firstChild;
if (cell?.type.spec.tableRole === "cell") {
return new Slice(
cell.content,
slice.openStart,
slice.openEnd
);
}
}
}
}
return slice;
},
handleDOMEvents: {
mousedown: (view, event) => {
if (!(event.target instanceof HTMLElement)) {
return false;
}
const targetAddRow = event.target.closest(
`.${EditorStyleHelper.tableAddRow}`
);
if (targetAddRow) {
event.preventDefault();
event.stopImmediatePropagation();
const index = Number(targetAddRow.getAttribute("data-index"));
addRowBefore({ index })(view.state, view.dispatch);
return true;
}
const targetGrip = event.target.closest(
`.${EditorStyleHelper.tableGrip}`
);
if (targetGrip) {
event.preventDefault();
event.stopImmediatePropagation();
selectTable()(view.state, view.dispatch);
return true;
}
const targetGripRow = event.target.closest(
`.${EditorStyleHelper.tableGripRow}`
);
if (targetGripRow) {
event.preventDefault();
event.stopImmediatePropagation();
selectRow(
Number(targetGripRow.getAttribute("data-index")),
event.metaKey || event.shiftKey
)(view.state, view.dispatch);
return true;
}
return false;
},
},
decorations: (state) => {
if (!this.editor.view?.editable) {
return;
}
const { doc } = state;
const decorations: Decoration[] = [];
const rows = getCellsInColumn(0)(state);
if (rows) {
rows.forEach((pos, visualIndex) => {
const actualRowIndex = getRowIndexInMap(visualIndex, state);
const index =
actualRowIndex !== -1 ? actualRowIndex : visualIndex;
if (index === 0) {
const className = cn(EditorStyleHelper.tableGrip, {
selected: isTableSelected(state),
});
decorations.push(
Decoration.widget(
pos + 1,
() => {
const grip = document.createElement("a");
grip.role = "button";
grip.className = className;
return grip;
},
{
key: className,
}
)
);
}
const className = cn(EditorStyleHelper.tableGripRow, {
selected:
isRowSelected(index)(state) || isTableSelected(state),
first: index === 0,
last: visualIndex === rows.length - 1,
});
decorations.push(
Decoration.widget(
pos + 1,
() => {
const grip = document.createElement("a");
grip.role = "button";
grip.className = className;
grip.dataset.index = index.toString();
return grip;
},
{
key: cn(className, index),
}
)
);
if (index === 0) {
decorations.push(buildAddRowDecoration(pos, index));
}
decorations.push(buildAddRowDecoration(pos, index + 1));
});
}
return DecorationSet.create(doc, decorations);
},
},
}),
];
}
}