Files
docmost/packages/editor-ext/src/lib/columns/column.ts
T
Philip Okugbe ef87210b3d feat: editor UI refresh and enhancements (#1968)
* feat: new image menu
* switch to resizable side handles
* use pixels

* refactor excalidraw and drawio menu

* support image resize undo

* video resize

* callout menu refresh

* refresh table menus

* fix color scheme

* fix: patch @tiptap/core ResizableNodeView to prevent resize sticking after mouseup

* feat: columns

* notes callout

* focus on first column

* capture tab key in column

* fix print

* hide columns menu when some nodes are focused

* fix print

* fix columns

* selective placeholder

* fix blockquote

* quote

* fix callout in columns
2026-02-24 15:22:37 +00:00

128 lines
2.8 KiB
TypeScript

import { Node, mergeAttributes, findParentNode } from "@tiptap/core";
import { TextSelection } from "prosemirror-state";
export interface ColumnOptions {
HTMLAttributes: Record<string, any>;
}
export interface ColumnAttributes {
width?: number | null;
}
declare module "@tiptap/core" {
interface Commands<ReturnType> {
column: {
setColumnWidth: (width: number | null) => ReturnType;
};
}
}
export const Column = Node.create<ColumnOptions>({
name: "column",
group: "block",
content: "block+",
defining: true,
isolating: true,
selectable: false,
addOptions() {
return {
HTMLAttributes: {},
};
},
addAttributes() {
return {
width: {
default: null,
parseHTML: (element) => {
const value = element.getAttribute("data-width");
return value ? parseFloat(value) : null;
},
renderHTML: (attributes: ColumnAttributes) => {
if (!attributes.width) return {};
return {
"data-width": attributes.width,
style: `flex: ${attributes.width}`,
};
},
},
};
},
parseHTML() {
return [
{
tag: `div[data-type="${this.name}"]`,
},
];
},
renderHTML({ HTMLAttributes }) {
return [
"div",
mergeAttributes(
{ "data-type": this.name },
this.options.HTMLAttributes,
HTMLAttributes,
),
0,
];
},
addKeyboardShortcuts() {
const jumpToColumn = (direction: 1 | -1) => () => {
const { state, dispatch } = this.editor.view;
const columns = findParentNode(
(node) => node.type.name === "columns",
)(state.selection);
if (!columns) return false;
const column = findParentNode(
(node) => node.type.name === "column",
)(state.selection);
if (!column) return false;
let currentIndex = -1;
columns.node.forEach((_child, offset, index) => {
if (columns.pos + 1 + offset === column.pos) {
currentIndex = index;
}
});
const targetIndex = currentIndex + direction;
if (targetIndex < 0 || targetIndex >= columns.node.childCount) {
return true;
}
let offset = 0;
for (let j = 0; j < targetIndex; j++) {
offset += columns.node.child(j).nodeSize;
}
const targetPos = columns.pos + 1 + offset + 1 + 1;
if (dispatch) {
dispatch(
state.tr.setSelection(TextSelection.create(state.doc, targetPos)),
);
}
return true;
};
return {
Tab: jumpToColumn(1),
"Shift-Tab": jumpToColumn(-1),
};
},
addCommands() {
return {
setColumnWidth:
(width) =>
({ commands }) =>
commands.updateAttributes("column", { width }),
};
},
});