@cairn214/fluent-editor
Version:
A rich text editor based on Quill 2.0, which extends rich modules and formats on the basis of Quill. It's powerful and out-of-the-box.
486 lines (485 loc) • 19.5 kB
JavaScript
"use strict";
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
const Quill = require("quill");
const editor_utils = require("../config/editor.utils.cjs.js");
const header = require("./formats/header.cjs.js");
const list = require("./formats/list.cjs.js");
const table = require("./formats/table.cjs.js");
const tableColumnTool = require("./modules/table-column-tool.cjs.js");
const tableOperationMenu = require("./modules/table-operation-menu.cjs.js");
const tableScrollBar = require("./modules/table-scroll-bar.cjs.js");
const tableSelection = require("./modules/table-selection.cjs.js");
const tableSelector = require("./modules/table-selector.cjs.js");
const index = require("./utils/index.cjs.js");
const nodeMatchers = require("./utils/node-matchers.cjs.js");
const Block = Quill.imports["blots/block"];
const Delta = Quill.imports.delta;
const Module = Quill.imports["core/module"];
class BetterTable extends Module {
static register() {
Quill.register(table.TableCol, true);
Quill.register(table.TableColGroup, true);
Quill.register(table.TableCellLine, true);
Quill.register(table.TableCell, true);
Quill.register(table.TableRow, true);
Quill.register(table.TableBody, true);
Quill.register(table.TableContainer, true);
Quill.register(table.TableViewWrapper, true);
Quill.register("formats/header", header.default, true);
Quill.register("formats/list", list.default, true);
}
constructor(quill, options) {
super(quill, options);
this.isComposition = false;
this.isTableSelectorVisible = false;
this.quill.root.addEventListener("mousedown", (event) => this.handleMouseDown(event, quill), false);
this.quill.root.addEventListener("compositionend", () => this.handleCompositionend(quill), false);
this.quill.root.addEventListener("compositionstart", () => this.handleCompositionstart(quill), false);
this.quill.root.addEventListener("keypress", (event) => this.handleKeyDown(event, quill), false);
this.quill.root.addEventListener(
"contextmenu",
(evt) => {
if (!this.table) return true;
evt.preventDefault();
const path = editor_utils.getEventComposedPath(evt);
if (!path || path.length <= 0) return;
const tableNode = path.filter((node) => {
return node.tagName && node.tagName.toUpperCase() === "TABLE" && node.classList.contains("quill-better-table");
})[0];
const rowNode = path.filter((node) => {
return node.tagName && node.tagName.toUpperCase() === "TR" && node.getAttribute("data-row");
})[0];
const cellNode = path.filter((node) => {
return node.tagName && node.tagName.toUpperCase() === "TD" && node.getAttribute("data-row");
})[0];
const isTargetCellSelected = this.tableSelection.selectedTds.map((tableCell) => tableCell.domNode).includes(cellNode);
if (this.tableSelection.selectedTds.length <= 0 || !isTargetCellSelected) {
this.tableSelection.setSelection(cellNode.getBoundingClientRect(), cellNode.getBoundingClientRect());
}
if (this.tableOperationMenu) {
this.tableOperationMenu = this.tableOperationMenu.destroy();
}
if (tableNode) {
this.tableOperationMenu = new tableOperationMenu.default(
{
table: tableNode,
row: rowNode,
cell: cellNode,
left: evt.x + 15,
top: evt.y
},
quill,
options.operationMenu
);
}
},
false
);
quill.keyboard.addBinding({ key: "Backspace" }, {}, (range, context) => {
if (range.index === 0 || this.quill.getLength() <= 1) return true;
if (context.offset === 0 && range.length === 0) {
const [prev] = this.quill.getLine(range.index - 1);
const [line] = this.quill.getLine(range.index);
if (!editor_utils.isNullOrUndefined(prev)) {
if (prev.statics.blotName === "table-cell-line" && line.statics.blotName !== "table-cell-line") {
const betterTableModule = this.quill.getModule("better-table");
let tableBlot;
try {
tableBlot = prev.parent.parent.parent.parent;
} catch (_e) {
}
if (tableBlot && tableBlot.domNode !== betterTableModule.table) {
betterTableModule.hideTableTools();
tableBlot.remove();
this.quill.update(Quill.sources.USER);
}
return false;
}
}
}
return true;
});
const thisBinding = quill.keyboard.bindings.Backspace.pop();
quill.keyboard.bindings.Backspace.splice(1, 0, thisBinding);
quill.clipboard.addMatcher("td", nodeMatchers.matchTableCell);
quill.clipboard.addMatcher("th", nodeMatchers.matchTableHeader);
quill.clipboard.addMatcher("table", nodeMatchers.matchTable);
quill.clipboard.addMatcher("h1, h2, h3, h4, h5, h6", nodeMatchers.matchHeader);
quill.clipboard.addMatcher("ol, ul", nodeMatchers.matchList);
quill.clipboard.addMatcher("span", nodeMatchers.matchMentionLink);
quill.clipboard.addMatcher("v:imageData", nodeMatchers.matchWordShapeImage);
quill.clipboard.addMatcher(Node.ELEMENT_NODE, nodeMatchers.matchInline);
quill.clipboard.matchers = quill.clipboard.matchers.filter((matcher) => {
return matcher[0] !== "tr";
});
quill.clipboard.addMatcher("tr", nodeMatchers.matchTableRow);
this.quill.on(Quill.events.EDITOR_CHANGE, () => {
const tableContainer = Quill.find(this.table);
if (!tableContainer) {
this.hideTableTools();
}
});
this.quill.on(Quill.events.SELECTION_CHANGE, (range, _oldRange, source) => {
if (!range) return;
const selectionStart = range.index;
const selectionEnd = range.index + range.length;
if (source === Quill.sources.USER) {
const tables = this.quill.root.getElementsByTagName("table");
if (tables && tables.length) {
[].forEach.call(tables, (table2) => {
const tableContainer = Quill.find(table2);
if (tableContainer) {
const tableStart = tableContainer.offset(this.quill.scroll);
const tableEnd = tableStart + tableContainer.length();
const classes = Array.from(table2.parentNode.classList);
if (selectionStart <= tableStart && tableEnd <= selectionEnd) {
table2.parentNode.classList.add("quill-better-table-selected");
} else if (classes.includes("quill-better-table-selected")) {
table2.parentNode.classList.remove("quill-better-table-selected");
}
}
});
}
}
});
this.initTableSelector();
}
initTableSelector() {
const toolbar = this.quill.getModule("toolbar");
if (!toolbar) return;
const tableButton = toolbar.container.querySelector(".ql-better-table");
if (!tableButton) return;
const tableSelectorWrapper = this.tableSelectorWrapperCreator();
tableButton.parentNode.insertBefore(tableSelectorWrapper, tableButton);
tableSelectorWrapper.appendChild(tableButton);
this.tableSelectorWrapper = tableSelectorWrapper;
tableSelectorWrapper.addEventListener("mouseenter", this.handleTableSelectorHover.bind(this), false);
tableSelectorWrapper.addEventListener("mouseleave", this.handleTableSelectorMouseOut.bind(this), false);
}
tableSelectorWrapperCreator() {
const wrapper = document.createElement("div");
wrapper.className = "ql-better-table-wrapper";
const cssContent = {
position: "relative",
display: "flex",
alignItems: "center",
justifyContent: "center",
boxSizing: "border-box"
};
index.css(wrapper, cssContent);
return wrapper;
}
handleMouseDown(evt, quill) {
const path = editor_utils.getEventComposedPath(evt);
if (!path || path.length <= 0) return;
const tableNode = path.filter((node) => {
return node.tagName && node.tagName.toUpperCase() === "TABLE" && node.classList.contains("quill-better-table");
})[0];
if (tableNode) {
tableNode.parentNode.classList.remove("quill-better-table-selected");
if (this.table === tableNode) return;
if (this.table) {
this.hideTableTools();
}
if (!quill.options.readOnly) {
this.showTableTools(tableNode, quill);
}
} else if (this.table) {
this.hideTableTools();
}
}
handleKeyDown(evt, quill) {
const key = evt.key;
const range = quill.getSelection();
if (this.isComposition || !range) return;
const [col] = quill.getLine(range.index);
const [td] = quill.getLine(range.index - 1);
if (key && key !== "Delete" && col && range) {
if (col.statics.blotName === "table-col") {
const betterTableModule = this.quill.getModule("better-table");
betterTableModule.hideTableTools();
quill.insertText(range.index - 1, "\n", Quill.sources.USER);
quill.setSelection(range.index, 0, Quill.sources.USER);
} else if (td && td.statics.blotName === "table-cell-line" && col.statics.blotName === "block") {
quill.insertText(range.index, "\n", Quill.sources.USER);
quill.setSelection(range.index, 0, Quill.sources.USER);
}
}
}
handleCompositionstart(quill) {
this.isComposition = true;
const range = quill.getSelection();
const [col] = quill.getLine(range.index);
const [td] = quill.getLine(range.index - 1);
if (col && range) {
if (col.statics.blotName === "table-col") {
const betterTableModule = this.quill.getModule("better-table");
betterTableModule.hideTableTools();
quill.insertText(range.index - 1, "\n ", Quill.sources.USER);
quill.setSelection(range.index, 0, Quill.sources.USER);
} else if (td && td.statics.blotName === "table-cell-line" && col.statics.blotName === "block") {
quill.setSelection(range.index, 0, Quill.sources.USER);
}
}
}
handleCompositionend(quill) {
const range = quill.getSelection();
const [line] = quill.getLine(range.index);
const domNode = line.domNode;
const isInParagraph = domNode.nodeName === "P";
const isInTableCell = line.statics.blotName === "table-cell-line";
if ((isInParagraph || isInTableCell) && domNode.childNodes.length > 1 && domNode.childNodes[0].nodeName === "BR") {
domNode.childNodes[0].remove();
}
if (editor_utils.isPureIE && line instanceof Block) {
quill.setSelection(range.index, 0, Quill.sources.SILENT);
}
this.isComposition = false;
}
// 触发table selector
handleTableSelectorHover() {
if (this.isTableSelectorVisible) return;
if (!this.tableSelectorWrapper) return;
this.isTableSelectorVisible = true;
this.tableSelector = new tableSelector.default({
onSelect: (rows, cols) => this.insertTable(rows, cols)
});
this.tableSelectorWrapper.appendChild(this.tableSelector.container);
const buttonRect = this.tableSelectorWrapper.getBoundingClientRect();
this.tableSelector.show(
0,
buttonRect.height
);
}
// table selector 鼠标离开
handleTableSelectorMouseOut() {
if (this.tableSelector) {
this.tableSelector.destroy();
}
this.tableSelector = null;
this.isTableSelectorVisible = false;
}
getTable(range = this.quill.getSelection()) {
if (editor_utils.isNullOrUndefined(range)) return [null, null, null, -1];
const [cellLine, offset] = this.quill.getLine(range.index);
if (editor_utils.isNullOrUndefined(cellLine) || cellLine.statics.blotName !== table.TableCellLine.blotName) {
return [null, null, null, -1];
}
const cell = cellLine.tableCell();
const row = cell.row();
const table$1 = row.table();
return [table$1, row, cell, offset];
}
insertTable(rows, columns) {
const range = this.quill.getSelection(true);
const isInsideTable = editor_utils.insideTable(range, this.quill);
if (editor_utils.isNullOrUndefined(range) || isInsideTable) return;
let delta = new Delta().retain(range.index);
delta.insert("\n");
delta = new Array(columns).fill("\n").reduce((memo, text) => {
memo.insert(text, { "table-col": true });
return memo;
}, delta);
delta = new Array(rows).fill(0).reduce((memo) => {
const tableRowId = table.rowId();
return new Array(columns).fill("\n").reduce((op, text) => {
op.insert(text, { "table-cell-line": { row: tableRowId, cell: table.cellId() } });
return op;
}, memo);
}, delta);
const [col] = this.quill.getLine(range.index);
if (col && col.statics.blotName === "table-col") {
delta.insert("\n");
}
this.quill.updateContents(delta, Quill.sources.USER);
const [cellLine] = this.quill.getLine(range.index + columns + 1);
if (cellLine) {
const cell = cellLine.tableCell();
const td = cell.domNode;
const mousedownEvent = document.createEvent("MouseEvent");
const mouseupEvent = document.createEvent("MouseEvent");
const keyEvent = document.createEvent("Event");
mousedownEvent.initEvent("mousedown", true, false);
mouseupEvent.initEvent("mouseup", true, false);
keyEvent.initEvent("keypress", true, false);
td.dispatchEvent(mousedownEvent);
td.dispatchEvent(mouseupEvent);
td.dispatchEvent(keyEvent);
this.tableSelection.endTd = td;
}
}
showTableTools(table2, quill) {
this.table = table2;
this.modulesContainer = document.createElement("div");
this.modulesContainer.classList.add("qlbt-modules-container");
const linkContainer = this.quill.root.parentNode.querySelector(".ql-tooltip ");
this.quill.root.parentNode.insertBefore(this.modulesContainer, linkContainer);
this.tableSelection = new tableSelection.default(table2, quill, this.modulesContainer);
this.columnTool = new tableColumnTool.default(table2, quill, this.modulesContainer);
this.tableScrollBar = new tableScrollBar.default(table2, quill, this.modulesContainer);
let timeoutID;
this.subscriber = () => {
clearTimeout(timeoutID);
timeoutID = setTimeout(this.hideTableTools, 300);
};
window.addEventListener("resize", this.subscriber, false);
}
hideTableTools() {
if (this.columnTool) {
this.columnTool.destroy();
}
if (this.tableSelection) {
this.tableSelection.destroy();
}
if (this.tableOperationMenu) {
this.tableOperationMenu.destroy();
}
if (this.tableScrollBar) {
this.tableScrollBar.destroy();
}
if (this.modulesContainer) {
this.modulesContainer.remove();
}
this.columnTool = null;
this.tableSelection = null;
this.tableOperationMenu = null;
this.tableScrollBar = null;
this.modulesContainer = null;
this.table = null;
if (this.subscriber) {
window.removeEventListener("resize", this.subscriber);
}
}
}
BetterTable.keyboardBindings = {
"line delete": {
key: "Delete",
shiftKey: null,
format: { "table-col": false, "table-cell-line": false },
collapsed: true,
offset: 0,
handler(range, context) {
if (range.index === 0 && context.line.next && context.line.next.statics.blotName === "table-view") {
const [p] = this.quill.getLine(range.index - 1);
const [col] = this.quill.getLine(range.index + 1);
if (p && p.statics.blotName === "block" && col && col.statics.blotName === "table-col") {
return false;
}
}
return true;
}
},
"table-col enter": {
key: "Enter",
shiftKey: null,
format: ["table-col"],
collapsed: true,
offset: 0,
handler(range, context) {
if (context.offset === 0 && range.length === 0) {
const [col] = this.quill.getLine(range.index);
if (col && col.statics.blotName === "table-col") {
const betterTableModule = this.quill.getModule("better-table");
betterTableModule.hideTableTools();
this.quill.insertText(range.index - 1, "\n", Quill.sources.USER);
this.quill.setSelection(range.index, 0, Quill.sources.USER);
}
}
}
},
"table-col delete": {
key: "Delete",
shiftKey: null,
format: ["table-col"],
collapsed: true,
offset: 0,
handler(range, context) {
if (context.offset === 0 && range.length === 0) {
const [col] = this.quill.getLine(range.index);
if (col && col.statics.blotName === "table-col") {
const betterTableModule = this.quill.getModule("better-table");
let tableBlot;
try {
tableBlot = col.parent.parent.parent;
} catch (_e) {
}
if (tableBlot && tableBlot.domNode !== betterTableModule.table) {
betterTableModule.hideTableTools();
tableBlot.remove();
this.quill.update(Quill.sources.USER);
}
}
}
}
},
"table-col backspace": {
key: "Backspace",
shiftKey: null,
format: ["table-col"],
collapsed: true,
offset: 0,
handler() {
}
},
"table-cell-line backspace": {
key: "Backspace",
format: ["table-cell-line"],
collapsed: true,
offset: 0,
handler(range) {
const [line] = this.quill.getLine(range.index);
if (!line.prev) {
return false;
}
return true;
}
},
"table-cell-line delete": {
key: "Delete",
format: ["table-cell-line"],
handler(range, _context) {
const [line] = this.quill.getLine(range.index);
const index2 = this.quill.getIndex(line);
const length = line.length();
if (!line.next && (!line.prev && length === 1 || range.index !== index2 && range.index + 1 >= index2 + length)) {
return false;
}
return true;
}
},
"table-cell-line enter": {
key: "Enter",
shiftKey: null,
format: ["table-cell-line"],
handler(range, context) {
const node = window.getSelection().anchorNode;
if (node instanceof HTMLDivElement && node.classList.contains("quill-better-table-wrapper")) {
this.quill.setSelection(range.index + 1, 0, Quill.sources.USER);
this.quill.focus();
return;
}
if (this.quill.selection && this.quill.selection.composing) return;
const Scope = Quill.imports.parchment.Scope;
if (range.length > 0) {
this.quill.scroll.deleteAt(range.index, range.length);
}
const lineFormats = Object.keys(context.format).reduce((formats, format) => {
if (this.quill.scroll.query(format, Scope.BLOCK) && !Array.isArray(context.format[format])) {
formats[format] = context.format[format];
}
return formats;
}, {});
this.quill.insertText(range.index, "\n", lineFormats["table-cell-line"], Quill.sources.USER);
this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
this.quill.focus();
Object.keys(context.format).forEach((name) => {
if (!editor_utils.isNullOrUndefined(lineFormats[name])) return;
if (Array.isArray(context.format[name])) return;
if (name === "link") return;
this.quill.format(name, context.format[name], Quill.sources.USER);
});
}
}
};
exports.default = BetterTable;
//# sourceMappingURL=better-table.cjs.js.map