@livepreso/react-plugin-textfield 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.lintstagedrc.js +6 -0
- package/.rush/temp/shrinkwrap-deps.json +311 -0
- package/.vscode/settings.json +22 -0
- package/CHANGELOG.json +17 -0
- package/CHANGELOG.md +9 -0
- package/components/BubbleMenu.js +181 -0
- package/components/BubbleMenu.module.scss +9 -0
- package/components/LinkEditDialog.js +105 -0
- package/components/LinkEditDialog.module.scss +171 -0
- package/components/Popover.js +43 -0
- package/components/Popover.module.scss +80 -0
- package/components/PrimaryToolbar.js +27 -0
- package/components/Select/Select.js +71 -0
- package/components/Select/Select.module.scss +100 -0
- package/components/Select/SelectGroup.js +37 -0
- package/components/Select/index.js +1 -0
- package/components/TableCellMenu.js +43 -0
- package/components/TableToolbar.js +41 -0
- package/components/Tooltip.js +34 -0
- package/components/Tooltip.module.scss +87 -0
- package/components/VerticalAlignToggle.js +65 -0
- package/components/color-picker/ColorPicker.js +28 -0
- package/components/color-picker/ColorPicker.module.scss +8 -0
- package/components/color-picker/ColorPickerChip.js +22 -0
- package/components/color-picker/ColorPickerChip.module.scss +25 -0
- package/components/color-picker/ColorPickerCombo.js +45 -0
- package/components/color-picker/ColorPickerCombo.module.scss +23 -0
- package/components/color-picker/TextColorIcon.js +18 -0
- package/components/editor-toolbars/EditorMenu.js +104 -0
- package/components/editor-toolbars/EditorMenu.module.scss +96 -0
- package/components/editor-toolbars/EditorToolbar.js +146 -0
- package/components/editor-toolbars/EditorToolbar.module.scss +75 -0
- package/components/editor-toolbars/MenuItem.js +24 -0
- package/components/editor-toolbars/SubMenu.js +50 -0
- package/components/editor-toolbars/ToolbarButton.js +26 -0
- package/components/editor-toolbars/ToolbarToggle.js +35 -0
- package/components/editor-toolbars/ToolbarToggleGroup.js +43 -0
- package/components/editor-toolbars/utils.js +7 -0
- package/components/hooks/use-presenter.js +5 -0
- package/components/style.module.scss +63 -0
- package/components/tiptap/ListItem.js +5 -0
- package/components/tiptap/Table.js +397 -0
- package/components/tiptap/TableCells.js +99 -0
- package/components/utils.js +84 -0
- package/configs/generate-toolbar-configuration.js +130 -0
- package/configs/generate-toolbar-options.js +96 -0
- package/configs/table-toolbar-configuration.js +187 -0
- package/configs/toolbar-configuration.js +330 -0
- package/constants.js +198 -0
- package/eslint.config.mjs +15 -0
- package/icons/AddColumnLeft.js +15 -0
- package/icons/AddColumnRight.js +15 -0
- package/icons/AddRowAbove.js +15 -0
- package/icons/AddRowBelow.js +15 -0
- package/icons/AlignHorizontalCenter.js +13 -0
- package/icons/AlignHorizontalLeft.js +13 -0
- package/icons/AlignHorizontalRight.js +13 -0
- package/icons/AlignVerticalBottom.js +13 -0
- package/icons/AlignVerticalCenter.js +13 -0
- package/icons/AlignVerticalTop.js +13 -0
- package/icons/Backspace.js +13 -0
- package/icons/Bold.js +14 -0
- package/icons/BorderAll.js +13 -0
- package/icons/BorderBottom.js +13 -0
- package/icons/BorderClear.js +13 -0
- package/icons/BorderHorizontal.js +13 -0
- package/icons/BorderInner.js +13 -0
- package/icons/BorderLeft.js +13 -0
- package/icons/BorderOuter.js +13 -0
- package/icons/BorderRight.js +13 -0
- package/icons/BorderTop.js +13 -0
- package/icons/BorderVertical.js +13 -0
- package/icons/Close.js +13 -0
- package/icons/Delete.js +13 -0
- package/icons/EvenlyDistribute.js +14 -0
- package/icons/FitWidth.js +13 -0
- package/icons/FitWidthArrows.js +21 -0
- package/icons/FormatAlignCenter.js +13 -0
- package/icons/FormatAlignJustify.js +13 -0
- package/icons/FormatAlignLeft.js +13 -0
- package/icons/FormatAlignRight.js +13 -0
- package/icons/FormatBold.js +13 -0
- package/icons/FormatClear.js +13 -0
- package/icons/FormatColorFill.js +13 -0
- package/icons/FormatColorText.js +13 -0
- package/icons/FormatItalic.js +13 -0
- package/icons/FormatLineSpacing.js +13 -0
- package/icons/FormatListBulleted.js +13 -0
- package/icons/FormatListNumbered.js +13 -0
- package/icons/FormatStrikethrough.js +13 -0
- package/icons/FormatUnderlined.js +13 -0
- package/icons/HorizontalRule.js +13 -0
- package/icons/Italic.js +14 -0
- package/icons/ItalicIcon.js +18 -0
- package/icons/Link.js +13 -0
- package/icons/LinkOff.js +13 -0
- package/icons/MergeCells.js +14 -0
- package/icons/Redo.js +13 -0
- package/icons/RemoveColumnOutline.js +28 -0
- package/icons/RemoveRowOutline.js +25 -0
- package/icons/SplitCells.js +14 -0
- package/icons/SplitScene.js +13 -0
- package/icons/Subscript.js +13 -0
- package/icons/Superscript.js +13 -0
- package/icons/Underline.js +14 -0
- package/icons/Undo.js +13 -0
- package/icons/VerticalAlignBottom.js +13 -0
- package/icons/VerticalAlignCenter.js +13 -0
- package/icons/VerticalAlignTop.js +13 -0
- package/icons/add_column_left.svg +6 -0
- package/icons/add_column_right.svg +6 -0
- package/icons/add_row_above.svg +6 -0
- package/icons/add_row_below.svg +6 -0
- package/icons/align_horizontal_center.svg +1 -0
- package/icons/align_horizontal_left.svg +1 -0
- package/icons/align_horizontal_right.svg +1 -0
- package/icons/align_vertical_bottom.svg +1 -0
- package/icons/align_vertical_center.svg +1 -0
- package/icons/align_vertical_top.svg +1 -0
- package/icons/backspace.svg +1 -0
- package/icons/bold.svg +1 -0
- package/icons/border_all.svg +1 -0
- package/icons/border_bottom.svg +1 -0
- package/icons/border_clear.svg +1 -0
- package/icons/border_horizontal.svg +1 -0
- package/icons/border_inner.svg +1 -0
- package/icons/border_left.svg +1 -0
- package/icons/border_outer.svg +1 -0
- package/icons/border_right.svg +1 -0
- package/icons/border_top.svg +1 -0
- package/icons/border_vertical.svg +1 -0
- package/icons/close.svg +1 -0
- package/icons/delete.svg +1 -0
- package/icons/evenly_distribute.svg +5 -0
- package/icons/fit_width.svg +1 -0
- package/icons/fit_width_arrows.svg +12 -0
- package/icons/format_align_center.svg +1 -0
- package/icons/format_align_justify.svg +1 -0
- package/icons/format_align_left.svg +1 -0
- package/icons/format_align_right.svg +1 -0
- package/icons/format_bold.svg +1 -0
- package/icons/format_clear.svg +1 -0
- package/icons/format_color_fill.svg +1 -0
- package/icons/format_color_text.svg +5 -0
- package/icons/format_color_text_ungrouped.svg +6 -0
- package/icons/format_italic.svg +1 -0
- package/icons/format_line_spacing.svg +1 -0
- package/icons/format_list_bulleted.svg +1 -0
- package/icons/format_list_numbered.svg +1 -0
- package/icons/format_strikethrough.svg +1 -0
- package/icons/format_underlined.svg +1 -0
- package/icons/horizontal_rule.svg +1 -0
- package/icons/index.js +191 -0
- package/icons/italic.svg +1 -0
- package/icons/link.svg +1 -0
- package/icons/link_off.svg +1 -0
- package/icons/merge_cells.svg +5 -0
- package/icons/redo.svg +1 -0
- package/icons/remove_column_outline.svg +20 -0
- package/icons/remove_row_outline.svg +17 -0
- package/icons/split_cells.svg +5 -0
- package/icons/split_scene.svg +1 -0
- package/icons/subscript.svg +1 -0
- package/icons/superscript.svg +1 -0
- package/icons/underline.svg +1 -0
- package/icons/undo.svg +1 -0
- package/icons/vertical_align_bottom.svg +1 -0
- package/icons/vertical_align_center.svg +1 -0
- package/icons/vertical_align_top.svg +1 -0
- package/index.js +334 -0
- package/index.module.scss +106 -0
- package/package.json +63 -0
- package/scripts/extract-svg.js +288 -0
- package/utils/color-utils.js +42 -0
- package/utils/generate-vertical-alignment-icon.js +22 -0
- package/utils/generateCustomExtensions.js +49 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import { CellSelection, TableMap } from "@tiptap/pm/tables";
|
|
2
|
+
import { Table as TableBase } from "@tiptap/extension-table";
|
|
3
|
+
import { getDomWidth } from "../utils";
|
|
4
|
+
import { CELL_MIN_WIDTH } from "../../constants";
|
|
5
|
+
|
|
6
|
+
export const CELL_VALIGN_TOP = "top";
|
|
7
|
+
export const CELL_VALIGN_MIDDLE = "middle";
|
|
8
|
+
export const CELL_VALIGN_BOTTOM = "bottom";
|
|
9
|
+
export const CELL_VALIGN_MIXED = "mixed";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Finds the table node and its position if the current selection is inside a table
|
|
13
|
+
* @param state The current editor state
|
|
14
|
+
* @returns An object containing the table node and its position, or null if not in a table
|
|
15
|
+
*/
|
|
16
|
+
export function findTable(state) {
|
|
17
|
+
const { doc, selection } = state;
|
|
18
|
+
const { from } = selection;
|
|
19
|
+
|
|
20
|
+
// Find the node before the cursor/selection
|
|
21
|
+
let $pos = doc.resolve(from);
|
|
22
|
+
|
|
23
|
+
// Walk up the document tree to find a table node
|
|
24
|
+
for (let depth = $pos.depth; depth > 0; depth--) {
|
|
25
|
+
const node = $pos.node(depth);
|
|
26
|
+
|
|
27
|
+
if (node.type.spec.tableRole === "table") {
|
|
28
|
+
const pos = $pos.before(depth);
|
|
29
|
+
return { node, pos };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Finds all cells in the current selection
|
|
38
|
+
* @param state The current editor state
|
|
39
|
+
* @returns An array of objects containing cell nodes and their positions, or empty array if not in a table
|
|
40
|
+
*/
|
|
41
|
+
export function findCellsInSelection(state) {
|
|
42
|
+
const { selection, doc } = state;
|
|
43
|
+
const result = [];
|
|
44
|
+
|
|
45
|
+
// Check if we have a cell selection (multiple cells selected)
|
|
46
|
+
if (selection instanceof CellSelection) {
|
|
47
|
+
const table = findTable(state);
|
|
48
|
+
|
|
49
|
+
if (!table) return result;
|
|
50
|
+
const cells = [];
|
|
51
|
+
selection.forEachCell((node, pos) => {
|
|
52
|
+
cells.push(node);
|
|
53
|
+
});
|
|
54
|
+
return cells;
|
|
55
|
+
} else {
|
|
56
|
+
// Regular selection - find the cell we're in
|
|
57
|
+
let $pos = doc.resolve(selection.from);
|
|
58
|
+
|
|
59
|
+
for (let depth = $pos.depth; depth > 0; depth--) {
|
|
60
|
+
const node = $pos.node(depth);
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
node.type.spec.tableRole === "cell" ||
|
|
64
|
+
node.type.spec.tableRole === "header_cell"
|
|
65
|
+
) {
|
|
66
|
+
result.push(node);
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function getColwidths({
|
|
76
|
+
tableNode,
|
|
77
|
+
cellMinWidth = CELL_MIN_WIDTH,
|
|
78
|
+
columnCount,
|
|
79
|
+
}) {
|
|
80
|
+
let hasColwidth = false;
|
|
81
|
+
// Get the first row to determine the number of columns
|
|
82
|
+
const firstRow = tableNode.firstChild;
|
|
83
|
+
if (!firstRow) return [];
|
|
84
|
+
|
|
85
|
+
let colwidths = [];
|
|
86
|
+
// Iterate through cells in the first row to calculate total width
|
|
87
|
+
firstRow.forEach((cell) => {
|
|
88
|
+
if (cell.attrs.colwidth && Array.isArray(cell.attrs.colwidth)) {
|
|
89
|
+
// Sum up the column widths for this cell
|
|
90
|
+
hasColwidth = true;
|
|
91
|
+
colwidths.push(
|
|
92
|
+
cell.attrs.colwidth.reduce((sum, width) => {
|
|
93
|
+
// colwidth 0, return min cell width
|
|
94
|
+
if (!width) return sum + cellMinWidth;
|
|
95
|
+
return sum + width;
|
|
96
|
+
}, 0),
|
|
97
|
+
);
|
|
98
|
+
} else {
|
|
99
|
+
// If no colwidth, add default width for each column this cell spans
|
|
100
|
+
const colspan = cell.attrs.colspan || 1;
|
|
101
|
+
colwidths.push(cellMinWidth * colspan);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const totalWidth = colwidths.reduce((sum, width) => sum + width, 0);
|
|
106
|
+
|
|
107
|
+
// If no colwidth attributes found or total is too small, use a default total
|
|
108
|
+
if (!hasColwidth || totalWidth < columnCount * cellMinWidth) {
|
|
109
|
+
const maxWidth = Math.max(totalWidth, columnCount * cellMinWidth);
|
|
110
|
+
return Array(columnCount).fill(maxWidth);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return colwidths;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Calculates total width of currently selected table
|
|
118
|
+
*
|
|
119
|
+
* @param tableNode The table node
|
|
120
|
+
* @param cellMinWidth The minimum width for each cell (default: 25)
|
|
121
|
+
* @returns Number (pixels) width of currently selected table
|
|
122
|
+
*/
|
|
123
|
+
function getTableWidth({ tableNode, cellMinWidth = CELL_MIN_WIDTH }) {
|
|
124
|
+
// Create a table map to understand the table structure
|
|
125
|
+
const tableMap = TableMap.get(tableNode);
|
|
126
|
+
const columnCount = tableMap.width;
|
|
127
|
+
|
|
128
|
+
if (columnCount === 0) return 0;
|
|
129
|
+
|
|
130
|
+
// Calculate total width from existing column widths
|
|
131
|
+
const currColwidths = getColwidths({
|
|
132
|
+
tableNode,
|
|
133
|
+
cellMinWidth,
|
|
134
|
+
columnCount,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return currColwidths.reduce((sum, width) => sum + width, 0);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Updates widths for all columns in a table based on a supplied max width, or the current total width
|
|
142
|
+
*
|
|
143
|
+
* @param tr The current transaction
|
|
144
|
+
* @param tableNode The table node
|
|
145
|
+
* @param tablePos The position of the table in the document
|
|
146
|
+
* @param cellMinWidth The minimum width for each cell
|
|
147
|
+
* @param setEqualColumns If true, all columns will have the same width (default: false)
|
|
148
|
+
* @param maxWidth The maximum width for all columns, calculates current total width if maxWidth is not provided
|
|
149
|
+
* @returns The updated transaction
|
|
150
|
+
*/
|
|
151
|
+
function setColumnWidths({
|
|
152
|
+
tr,
|
|
153
|
+
table,
|
|
154
|
+
cellMinWidth = CELL_MIN_WIDTH,
|
|
155
|
+
setEqualColumns = false,
|
|
156
|
+
useFullWidth = false,
|
|
157
|
+
maxWidth,
|
|
158
|
+
}) {
|
|
159
|
+
const tableNode = table.node;
|
|
160
|
+
const tablePos = table.pos;
|
|
161
|
+
// this avoids a no-param-reassign warning
|
|
162
|
+
let transaction = tr;
|
|
163
|
+
// Create a table map to understand the table structure
|
|
164
|
+
const tableMap = TableMap.get(tableNode);
|
|
165
|
+
const columnCount = tableMap.width;
|
|
166
|
+
|
|
167
|
+
if (columnCount === 0) return tr;
|
|
168
|
+
|
|
169
|
+
// Calculate total width from existing column widths
|
|
170
|
+
const currColwidths = getColwidths({
|
|
171
|
+
tableNode,
|
|
172
|
+
cellMinWidth,
|
|
173
|
+
columnCount,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
if (!currColwidths.length) return tr;
|
|
177
|
+
|
|
178
|
+
const currWidth = currColwidths.reduce((sum, width) => sum + width, 0);
|
|
179
|
+
|
|
180
|
+
let totalWidth = currWidth;
|
|
181
|
+
if (useFullWidth || currWidth > maxWidth) {
|
|
182
|
+
totalWidth = maxWidth;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let colwidths = Array(columnCount).fill(0);
|
|
186
|
+
|
|
187
|
+
if (setEqualColumns) {
|
|
188
|
+
// Calculate equal width for each column
|
|
189
|
+
const equalColumnWidth = totalWidth / columnCount;
|
|
190
|
+
// Create an array of equal column widths
|
|
191
|
+
colwidths = colwidths.map(() => equalColumnWidth);
|
|
192
|
+
} else {
|
|
193
|
+
// Adjust column widths based on the new total width
|
|
194
|
+
colwidths = currColwidths.map((width) => (width / currWidth) * totalWidth);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Now update all cells in the table to use the appropriate colwidth
|
|
198
|
+
// We need to traverse the entire table structure
|
|
199
|
+
let rowPos = tablePos + 1; // Start at the first row
|
|
200
|
+
|
|
201
|
+
// Process each row in the table
|
|
202
|
+
tableNode.forEach((row, rowOffset) => {
|
|
203
|
+
if (row.type.name !== "tableRow") return;
|
|
204
|
+
|
|
205
|
+
let cellPos = rowPos + 1; // Start at the first cell in this row
|
|
206
|
+
let colIndex = 0;
|
|
207
|
+
|
|
208
|
+
// Process each cell in the row
|
|
209
|
+
row.forEach((cell) => {
|
|
210
|
+
const colspan = cell.attrs.colspan || 1;
|
|
211
|
+
|
|
212
|
+
// For cells that span multiple columns, we need to sum the widths
|
|
213
|
+
const cellColwidths = colwidths.slice(colIndex, colIndex + colspan);
|
|
214
|
+
|
|
215
|
+
transaction = transaction.setNodeMarkup(cellPos, null, {
|
|
216
|
+
...cell.attrs,
|
|
217
|
+
colwidth: cellColwidths,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Move column index forward by the colspan
|
|
221
|
+
colIndex += colspan;
|
|
222
|
+
|
|
223
|
+
// Move to the next cell position
|
|
224
|
+
cellPos += cell.nodeSize;
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Move to the next row position
|
|
228
|
+
rowPos += row.nodeSize;
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
return transaction;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// helper that returns all found values of a given cell attribute
|
|
235
|
+
// this includes undefined values (these can be mapped to their defaults, ie vertical align: top)
|
|
236
|
+
|
|
237
|
+
function getCellAttributes(editor, attributeName) {
|
|
238
|
+
const selectedCells = findCellsInSelection(editor.state);
|
|
239
|
+
const foundValues = selectedCells.reduce((acc, cell) => {
|
|
240
|
+
const newVal = cell.attrs[attributeName];
|
|
241
|
+
if (acc.indexOf(newVal) > -1) return acc;
|
|
242
|
+
return [...acc, newVal];
|
|
243
|
+
}, []);
|
|
244
|
+
return foundValues;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/* summarises the alignments in the currently selected cells, accounts for unstyled cells */
|
|
248
|
+
export function getCellAlignment(editor) {
|
|
249
|
+
const alignments = getCellAttributes(editor, "verticalAlign");
|
|
250
|
+
// return value may contain falsey values, which equate to 'top'- map them, and rmeove any duplicates
|
|
251
|
+
const mappedAlignments = alignments
|
|
252
|
+
.map((v) => v || CELL_VALIGN_TOP)
|
|
253
|
+
.filter((v, index, all) => all.indexOf(v) === index);
|
|
254
|
+
if (mappedAlignments.length === 1) return mappedAlignments[0];
|
|
255
|
+
return CELL_VALIGN_MIXED;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Extension that adds equal column width functionality to tables
|
|
260
|
+
*/
|
|
261
|
+
export const TableExtended = TableBase.extend({
|
|
262
|
+
addOptions() {
|
|
263
|
+
return {
|
|
264
|
+
...this.parent?.(),
|
|
265
|
+
cellMinWidth: CELL_MIN_WIDTH,
|
|
266
|
+
lastColumnResizable: true,
|
|
267
|
+
resizable: true,
|
|
268
|
+
};
|
|
269
|
+
},
|
|
270
|
+
addCommands() {
|
|
271
|
+
return {
|
|
272
|
+
...this.parent?.(),
|
|
273
|
+
/**
|
|
274
|
+
* Set equal widths for all columns in the current table based on the current total width
|
|
275
|
+
*/
|
|
276
|
+
setTableEqualColumnWidths:
|
|
277
|
+
() =>
|
|
278
|
+
({ state, tr, dispatch, editor }) => {
|
|
279
|
+
const table = findTable(state);
|
|
280
|
+
|
|
281
|
+
if (!table) return false;
|
|
282
|
+
|
|
283
|
+
const tableDom = editor.editorView.domAtPos(table.pos);
|
|
284
|
+
|
|
285
|
+
const newTr = setColumnWidths({
|
|
286
|
+
tr,
|
|
287
|
+
table,
|
|
288
|
+
setEqualColumns: true,
|
|
289
|
+
maxWidth: getDomWidth(tableDom.node),
|
|
290
|
+
cellMinWidth: this.options.cellMinWidth,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
if (dispatch) {
|
|
294
|
+
dispatch(newTr);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return true;
|
|
298
|
+
},
|
|
299
|
+
setFullWidth:
|
|
300
|
+
() =>
|
|
301
|
+
({ state, tr, dispatch, editor }) => {
|
|
302
|
+
const table = findTable(state);
|
|
303
|
+
|
|
304
|
+
if (!table) return false;
|
|
305
|
+
|
|
306
|
+
const tableDom = editor.editorView.domAtPos(table.pos);
|
|
307
|
+
|
|
308
|
+
const newTr = setColumnWidths({
|
|
309
|
+
tr,
|
|
310
|
+
table,
|
|
311
|
+
useFullWidth: true,
|
|
312
|
+
maxWidth: getDomWidth(tableDom.node),
|
|
313
|
+
cellMinWidth: this.options.cellMinWidth,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
if (dispatch) {
|
|
317
|
+
dispatch(newTr);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return true;
|
|
321
|
+
},
|
|
322
|
+
cleanWidth:
|
|
323
|
+
() =>
|
|
324
|
+
({ state, tr, dispatch, editor }) => {
|
|
325
|
+
const table = findTable(state);
|
|
326
|
+
|
|
327
|
+
if (!table) return false;
|
|
328
|
+
|
|
329
|
+
const tableDom = editor.editorView.domAtPos(table.pos);
|
|
330
|
+
const tableWidth = getTableWidth({
|
|
331
|
+
tableNode: table.node,
|
|
332
|
+
cellMinWidth: this.options.cellMinWidth,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const availableWidth = getDomWidth(tableDom.node);
|
|
336
|
+
|
|
337
|
+
if (tableWidth <= availableWidth) {
|
|
338
|
+
dispatch(tr);
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const newTr = setColumnWidths({
|
|
343
|
+
tr,
|
|
344
|
+
table,
|
|
345
|
+
maxWidth: availableWidth,
|
|
346
|
+
cellMinWidth: this.options.cellMinWidth,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
if (dispatch) {
|
|
350
|
+
dispatch(newTr);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return true;
|
|
354
|
+
},
|
|
355
|
+
applyStyleToCells:
|
|
356
|
+
(style) =>
|
|
357
|
+
({ tr, state, dispatch }) => {
|
|
358
|
+
const cells = findCellsInSelection(state);
|
|
359
|
+
|
|
360
|
+
if (cells.length === 0) return false;
|
|
361
|
+
|
|
362
|
+
const newTr = tr.setNodes(
|
|
363
|
+
cells.map(({ node }) => ({
|
|
364
|
+
node,
|
|
365
|
+
attrs: {
|
|
366
|
+
...node.attrs,
|
|
367
|
+
style: style,
|
|
368
|
+
},
|
|
369
|
+
})),
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
if (dispatch) {
|
|
373
|
+
dispatch(newTr);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return true;
|
|
377
|
+
},
|
|
378
|
+
/**
|
|
379
|
+
* Find the current table if the selection is inside one
|
|
380
|
+
*/
|
|
381
|
+
findTable:
|
|
382
|
+
() =>
|
|
383
|
+
({ state }) => {
|
|
384
|
+
return findTable(state);
|
|
385
|
+
},
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Find all cells in the current selection
|
|
389
|
+
*/
|
|
390
|
+
findCellsInSelection:
|
|
391
|
+
() =>
|
|
392
|
+
({ state }) => {
|
|
393
|
+
return findCellsInSelection(state);
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
},
|
|
397
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { TableCell as TableCellBase } from "@tiptap/extension-table";
|
|
2
|
+
import { TableHeader as TableHeaderBase } from "@tiptap/extension-table";
|
|
3
|
+
import { hexIsDark } from "../../utils/color-utils";
|
|
4
|
+
|
|
5
|
+
const tableCellAttributes = {
|
|
6
|
+
colwidth: {
|
|
7
|
+
default: null,
|
|
8
|
+
parseHTML: (element) => {
|
|
9
|
+
const colwidth = element.getAttribute("colwidth");
|
|
10
|
+
const width = element.getAttribute("width");
|
|
11
|
+
|
|
12
|
+
if (!colwidth && width) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const value = colwidth
|
|
17
|
+
? colwidth.split(",").map((width) => parseInt(width, 10))
|
|
18
|
+
: null;
|
|
19
|
+
|
|
20
|
+
return value;
|
|
21
|
+
},
|
|
22
|
+
renderHTML: ({ width, colwidth }) => {
|
|
23
|
+
if (!colwidth && width) {
|
|
24
|
+
return { width, colwidth: null };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
colwidth: colwidth ? colwidth.join(",") : null,
|
|
29
|
+
width: null,
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
// Strategy Recs can receive tables, but the width might be percentages, and will
|
|
34
|
+
// be saved to width instead of colwidth. This allows it to be passed straight through.
|
|
35
|
+
width: {
|
|
36
|
+
default: null,
|
|
37
|
+
parseHTML: (element) => {
|
|
38
|
+
return element.getAttribute("width") || null;
|
|
39
|
+
},
|
|
40
|
+
renderHTML: ({ width, colwidth }) => {
|
|
41
|
+
if (colwidth) return { colwidth, width: null };
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
width,
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
backgroundColor: {
|
|
49
|
+
default: null,
|
|
50
|
+
parseHTML: (element) => element.getAttribute("data-background-color"),
|
|
51
|
+
renderHTML: ({ backgroundColor }) => {
|
|
52
|
+
const attrs = {
|
|
53
|
+
"data-background-color": backgroundColor,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (backgroundColor) {
|
|
57
|
+
attrs.style = `background-color: ${backgroundColor};`;
|
|
58
|
+
attrs.class = hexIsDark(backgroundColor)
|
|
59
|
+
? "tiptap-table-cell-dark"
|
|
60
|
+
: "tiptap-table-cell-light";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return attrs;
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
verticalAlign: {
|
|
67
|
+
default: null,
|
|
68
|
+
parseHTML: (element) => element.getAttribute("data-vertical-align"),
|
|
69
|
+
renderHTML: ({ verticalAlign }) => {
|
|
70
|
+
const attrs = {
|
|
71
|
+
"data-vertical-align": verticalAlign,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (verticalAlign) {
|
|
75
|
+
attrs.style = `vertical-align: ${verticalAlign};`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return attrs;
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const TableCell = TableCellBase.extend({
|
|
84
|
+
addAttributes() {
|
|
85
|
+
return {
|
|
86
|
+
...this.parent?.(),
|
|
87
|
+
...tableCellAttributes,
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export const TableHeader = TableHeaderBase.extend({
|
|
93
|
+
addAttributes() {
|
|
94
|
+
return {
|
|
95
|
+
...this.parent?.(),
|
|
96
|
+
...tableCellAttributes,
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export function getContentActualDimensions() {
|
|
2
|
+
const slideshow = document.getElementById("slideshow");
|
|
3
|
+
const slideshowBounds = slideshow.getBoundingClientRect();
|
|
4
|
+
return { width: slideshowBounds.width, height: slideshowBounds.height };
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function getContentDimensions() {
|
|
8
|
+
const slideshow = document.getElementById("slideshow");
|
|
9
|
+
const slideshowStyle = slideshow.style;
|
|
10
|
+
|
|
11
|
+
const slideshowWidth = slideshowStyle
|
|
12
|
+
.getPropertyValue("width")
|
|
13
|
+
?.replace("px", "");
|
|
14
|
+
|
|
15
|
+
const slideshowHeight = slideshowStyle
|
|
16
|
+
.getPropertyValue("height")
|
|
17
|
+
?.replace("px", "");
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
width: slideshowWidth ? parseInt(slideshowWidth, 10) : null,
|
|
21
|
+
height: slideshowHeight ? parseInt(slideshowHeight, 10) : null,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getContentRatio() {
|
|
26
|
+
const { width: actualWidth } = getContentActualDimensions();
|
|
27
|
+
const { width: rawWidth } = getContentDimensions();
|
|
28
|
+
|
|
29
|
+
return rawWidth && actualWidth ? actualWidth / rawWidth : 1;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getDomWidth(dom) {
|
|
33
|
+
const bounds = dom?.getBoundingClientRect() || { width: 0 };
|
|
34
|
+
return bounds.width ? bounds.width / getContentRatio() : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Tests if the element is contained within the bounds of a mask element
|
|
39
|
+
* and returns appropriate rect combining the element and mask rects.
|
|
40
|
+
*
|
|
41
|
+
* If the test is outside of the vertical limits of the mask, the mask's
|
|
42
|
+
* top, bottom and height will replace the testRect's, while textRect
|
|
43
|
+
* maintains its width, left and right.
|
|
44
|
+
*
|
|
45
|
+
* @param {Object} element - return of getBoundingClientRect()
|
|
46
|
+
* @param {Object} parent - return of getBoundingClientRect()
|
|
47
|
+
* @returns {Object}
|
|
48
|
+
*/
|
|
49
|
+
export const getTestRect = (testRect, maskRect) => {
|
|
50
|
+
const newRect = {
|
|
51
|
+
y: testRect.y,
|
|
52
|
+
x: testRect.x,
|
|
53
|
+
width: testRect.width,
|
|
54
|
+
height: testRect.height,
|
|
55
|
+
top: testRect.top,
|
|
56
|
+
bottom: testRect.bottom,
|
|
57
|
+
left: testRect.left,
|
|
58
|
+
right: testRect.right,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if (testRect.top < maskRect.top) {
|
|
62
|
+
newRect.y = maskRect.y;
|
|
63
|
+
newRect.top = maskRect.top;
|
|
64
|
+
newRect.height = testRect.height - (maskRect.top - testRect.top);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (testRect.bottom > maskRect.bottom) {
|
|
68
|
+
newRect.bottom = maskRect.bottom;
|
|
69
|
+
newRect.height = testRect.height - (testRect.bottom - maskRect.bottom);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (testRect.left < maskRect.left) {
|
|
73
|
+
newRect.x = maskRect.x;
|
|
74
|
+
newRect.left = maskRect.left;
|
|
75
|
+
newRect.width = testRect.width - (maskRect.left - testRect.left);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (testRect.right > maskRect.right) {
|
|
79
|
+
newRect.right = maskRect.right;
|
|
80
|
+
newRect.width = testRect.width - (testRect.right - maskRect.right);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return newRect;
|
|
84
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_TOOLBAR_GROUPS,
|
|
3
|
+
PRIMARY_TOOLBAR_IDS,
|
|
4
|
+
TABLE_TOOLBAR_IDS,
|
|
5
|
+
} from "../constants";
|
|
6
|
+
|
|
7
|
+
const generateGroup = (id, { label = null, items = [], options = {} } = {}) => {
|
|
8
|
+
if (items.length === 0) return null;
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
id,
|
|
12
|
+
label,
|
|
13
|
+
items: items.map((generator) => generator(options)),
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* List of baked-in toolbar ids,
|
|
19
|
+
* these cannot be used when creating custom options.
|
|
20
|
+
*/
|
|
21
|
+
const reservedIds = [
|
|
22
|
+
...Object.values(PRIMARY_TOOLBAR_IDS),
|
|
23
|
+
...Object.values(TABLE_TOOLBAR_IDS),
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Custom options (aka. items) can either be a function that returns an item object,
|
|
28
|
+
* of the final object. Supplying a function allows for options like 'textStyles' and 'colors'
|
|
29
|
+
* to be passed to the user when generating the option.
|
|
30
|
+
*
|
|
31
|
+
* This function tests the validity of the supplied item, and returns a function
|
|
32
|
+
* that can be used by the "generateGroup" function to create its items.
|
|
33
|
+
*
|
|
34
|
+
* @param {Object|Function} item
|
|
35
|
+
* @returns {Function} - function that returns an item object.
|
|
36
|
+
*/
|
|
37
|
+
const getCustomGenerator = (item, options = {}) => {
|
|
38
|
+
if (typeof item !== "function" && !(item.constructor === Object)) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
"An custom toolbar option must be a function or an object.",
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let testItem = item;
|
|
45
|
+
|
|
46
|
+
if (typeof item === "function") {
|
|
47
|
+
testItem = item(options);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const { id, command, renderItem } = testItem;
|
|
51
|
+
|
|
52
|
+
if (!id) {
|
|
53
|
+
throw new Error("An id is required when creating a custom toolbar option.");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (reservedIds.includes(id)) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Custom id '${id}' is not allowed as it is used by the default toolbar.\nReserved ids: ${reservedIds.join(
|
|
59
|
+
", ",
|
|
60
|
+
)}.`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (typeof command !== "function" && typeof renderItem !== "function") {
|
|
65
|
+
throw new Error(
|
|
66
|
+
"When creating a custom toolbar option, a command or renderItem function must be provided.",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return typeof item === "function" ? item : () => item;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const generateToolbarConfiguration = ({
|
|
74
|
+
toolbar = [],
|
|
75
|
+
itemGenerators = {},
|
|
76
|
+
textStyles = [],
|
|
77
|
+
colors = [],
|
|
78
|
+
} = {}) => {
|
|
79
|
+
const options = {
|
|
80
|
+
textStyles,
|
|
81
|
+
colors,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const generateItem = (item) => {
|
|
85
|
+
if (typeof item === "string" && itemGenerators[item]) {
|
|
86
|
+
return itemGenerators[item];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Assumes custom item has been supplied
|
|
90
|
+
return getCustomGenerator(item, options);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return toolbar
|
|
94
|
+
.map((group) => {
|
|
95
|
+
if (typeof group === "string") {
|
|
96
|
+
if (!DEFAULT_TOOLBAR_GROUPS[group]) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Requested default group '${group}' is not found. Available groups: ${Object.keys(
|
|
99
|
+
DEFAULT_TOOLBAR_GROUPS,
|
|
100
|
+
).join(", ")}`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const { label, items = [] } = DEFAULT_TOOLBAR_GROUPS[group];
|
|
105
|
+
return generateGroup(group, {
|
|
106
|
+
label,
|
|
107
|
+
items: items.map(generateItem).filter(Boolean),
|
|
108
|
+
options,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Is a list of items instead of a pre-defined group
|
|
113
|
+
if (Array.isArray(group)) {
|
|
114
|
+
const id = group.join("_");
|
|
115
|
+
return generateGroup(id, {
|
|
116
|
+
items: group.map(generateItem).filter(Boolean),
|
|
117
|
+
options,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
throw new Error(
|
|
122
|
+
"Toolbar options must be either an existing id (supplied as string) or an array of items. Received: " +
|
|
123
|
+
JSON.stringify(group) +
|
|
124
|
+
" of type " +
|
|
125
|
+
typeof group +
|
|
126
|
+
".",
|
|
127
|
+
);
|
|
128
|
+
})
|
|
129
|
+
.filter(Boolean);
|
|
130
|
+
};
|