@opentiny/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.
370 lines (369 loc) • 12.5 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import Quill from "quill";
import { getAllConfigs } from "../config-utils.es.js";
import { expandIcon, contractIcon } from "../icons.es.js";
import { initContextMenu } from "../modules/context-menu.es.js";
import { createControlPanel } from "../modules/control-panel.es.js";
import { FlowChartResizeAction } from "../modules/custom-resize-action.es.js";
/* empty css */
const BlockEmbed = Quill.import("blots/embed");
const _FlowChartPlaceholderBlot = class _FlowChartPlaceholderBlot extends BlockEmbed {
constructor(scroll, domNode) {
super(scroll, domNode);
__publicField(this, "quill", null);
__publicField(this, "flowChart", null);
__publicField(this, "data");
__publicField(this, "contextMenu", null);
__publicField(this, "currentElement", null);
__publicField(this, "width", 100);
__publicField(this, "height", 500);
__publicField(this, "parentObserver", null);
__publicField(this, "nextPObserver", null);
const data = _FlowChartPlaceholderBlot.value(domNode);
this.width = data.width || 100;
this.height = data.height || 500;
this.domNode.style.width = `${this.width}${data.width ? "px" : "%"}`;
this.domNode.style.height = `${this.height}px`;
this.domNode.style.maxWidth = "100%";
this.domNode.style.border = "1px solid #e8e8e8";
this.domNode.setAttribute("contenteditable", "false");
this.data = _FlowChartPlaceholderBlot.value(this.domNode);
this.initFlowChart();
}
static value(domNode) {
const dataStr = JSON.parse(domNode.getAttribute("data-flow-chart"));
const value = dataStr.root ? dataStr.root : dataStr;
if (domNode.hasAttribute("width")) {
value.width = Number.parseInt(domNode.getAttribute("width"), 10);
}
if (domNode.hasAttribute("height")) {
value.height = Number.parseInt(domNode.getAttribute("height"), 10);
}
return dataStr.root ? dataStr.root : dataStr;
}
static create(value) {
const node = super.create();
if (value) {
node.setAttribute("data-flow-chart", JSON.stringify(value));
}
if (value.width) {
node.setAttribute("width", String(value.width));
node.style.width = `${value.width}%`;
}
if (value.height) {
node.setAttribute("height", String(value.height));
node.style.height = `${value.height}px`;
}
node.setAttribute("contenteditable", "false");
return node;
}
static findQuill(el) {
let cur = el;
while (cur) {
const q = cur.__quillInstance;
if (q) return q;
cur = cur.parentElement;
}
return null;
}
attach() {
super.attach();
this.quill = _FlowChartPlaceholderBlot.findQuill(this.domNode);
}
initFlowChart() {
if (this.domNode.isConnected) {
this.insertFlowChartEditor();
} else {
const observer = new MutationObserver(() => {
if (this.domNode.isConnected) {
this.insertFlowChartEditor();
observer.disconnect();
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
}
insertFlowChartEditor() {
this.domNode.style.width = `${this.width}${this.data.width ? "px" : "%"}`;
this.domNode.style.height = `${this.height}px`;
this.updateAlignmentStyle();
this.observeParentAlignment();
const { gridConfig, backgroundConfig, resizeConfig, deps } = getAllConfigs(this.quill);
const { LogicFlow, DndPanel, SelectionSelect, Snapshot } = deps || window;
this.flowChart = new LogicFlow({
container: this.domNode,
stopScrollGraph: true,
stopZoomGraph: true,
allowResize: true,
allowRotate: true,
editable: true,
preventDefaultDoubleClick: true,
preventDefault: true,
grid: gridConfig,
background: backgroundConfig,
plugins: [DndPanel, SelectionSelect, Snapshot]
});
this.flowChart.setPatternItems([
{
className: "lf-shape-dashed",
callback: () => {
this.flowChart.openSelectionSelect();
this.flowChart.once("selection:selected", () => {
this.flowChart.closeSelectionSelect();
});
}
},
{
type: "rect",
text: "矩形",
className: "lf-shape-rect"
},
{
type: "circle",
text: "圆形",
className: "lf-shape-circle"
},
{
type: "ellipse",
text: "椭圆",
className: "lf-shape-ellipse"
},
{
type: "diamond",
text: "菱形",
className: "lf-shape-diamond"
}
]);
if (resizeConfig) {
new FlowChartResizeAction(this);
}
createControlPanel(this, this.quill);
initContextMenu(this, this.quill);
this.observeOwnParentChange();
this.observeNextPElement();
this.addMouseHoverEvents();
this.flowChart.render(this.data);
this.flowChart.on("graph:updated", () => {
this.data = this.flowChart.getGraphData();
this.domNode.setAttribute("data-flow-chart", JSON.stringify(this.data));
});
this.flowChart.on("history:change", () => {
this.data = this.flowChart.getGraphData();
this.domNode.setAttribute("data-flow-chart", JSON.stringify(this.data));
});
this.flowChart.on("node:dbclick", this.handleNodeDblClick.bind(this));
this.flowChart.on("edge:dbclick", this.handleNodeDblClick.bind(this));
this.domNode.addEventListener("click", (e) => {
if (this.quill) {
const flowChartBlot = Quill.find(this.domNode);
const index = this.quill.getIndex(flowChartBlot);
if (index && typeof index === "number") {
this.quill.setSelection(index + 1, 0);
}
}
});
}
addMouseHoverEvents() {
this.domNode.addEventListener("mouseenter", () => {
this.showControlPanel();
});
this.domNode.addEventListener("mouseleave", () => {
var _a;
(_a = this.flowChart) == null ? void 0 : _a.clearSelectElements();
this.hideControlPanel();
});
}
getControlElements() {
const leftUpControl = this.domNode.querySelector(".lf-dndpanel");
const control = this.domNode.querySelector(".ql-flow-chart-control");
const panelStatusIcon = this.domNode.querySelector('[data-control-type="panel-status"]');
return { leftUpControl, control, panelStatusIcon };
}
showControlPanel() {
const { leftUpControl, control, panelStatusIcon } = this.getControlElements();
if (!leftUpControl || !control) return;
leftUpControl.style.display = "block";
control.style.display = "flex";
if (panelStatusIcon) {
const iconElement = panelStatusIcon.querySelector("i") || panelStatusIcon;
iconElement.innerHTML = expandIcon;
}
}
hideControlPanel() {
const { leftUpControl, control, panelStatusIcon } = this.getControlElements();
if (!leftUpControl || !control) return;
leftUpControl.style.display = "none";
control.style.display = "none";
if (panelStatusIcon) {
const iconElement = panelStatusIcon.querySelector("i") || panelStatusIcon;
iconElement.innerHTML = contractIcon;
}
}
observeOwnParentChange() {
let currentParent = this.domNode.parentElement;
const observer = new MutationObserver(() => {
if (this.domNode.parentElement !== currentParent) {
currentParent = this.domNode.parentElement;
this.observeParentAlignment();
}
});
observer.observe(document.body, {
attributes: false,
childList: true,
subtree: true
});
}
observeParentAlignment() {
if (this.parentObserver) {
this.parentObserver.disconnect();
}
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === "class") {
this.updateAlignmentStyle();
}
});
});
this.parentObserver = observer;
const parent = this.domNode.parentElement;
if (parent) {
observer.observe(parent, {
attributes: true,
attributeFilter: ["class"]
});
this.updateAlignmentStyle();
}
}
updateAlignmentStyle() {
const parent = this.domNode.parentElement;
if (!parent) return;
this.domNode.style.margin = "";
this.domNode.style.display = "block";
if (parent.classList.contains("ql-align-center")) {
this.domNode.style.margin = "0 auto";
} else if (parent.classList.contains("ql-align-right")) {
this.domNode.style.marginLeft = "auto";
this.domNode.style.marginRight = "0";
} else {
this.domNode.style.marginLeft = "0";
this.domNode.style.marginRight = "auto";
}
}
observeNextPElement() {
if (this.nextPObserver) {
this.nextPObserver.disconnect();
}
const parentElement = this.domNode.parentElement;
if (!parentElement) {
return;
}
const trackedParentElement = parentElement;
const parentElementId = parentElement.getAttribute("id") || `flow-chart-parent-${Date.now()}`;
parentElement.setAttribute("id", parentElementId);
const observer = new MutationObserver(() => {
if (!document.contains(trackedParentElement)) {
const elementById = document.getElementById(parentElementId);
if (!elementById) {
this.remove();
observer.disconnect();
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
this.nextPObserver = observer;
}
// 处理节点双击事件
handleNodeDblClick(event) {
const { data, position, e } = event;
if (data && data.id) {
this.createEditInput(data, position, e);
}
}
// 创建编辑输入框
createEditInput(nodeData, position, e) {
var _a;
const input = document.createElement("textarea");
input.className = "ql-flow-chart-edit-input";
input.value = ((_a = nodeData.text) == null ? void 0 : _a.value) || "";
const autoResize = () => {
input.style.height = "auto";
input.style.height = `${input.scrollHeight}px`;
};
Object.assign(input.style, {
position: "absolute",
boxSizing: "border-box",
width: "100px",
height: "35px",
padding: "5px",
lineHeight: "1.2",
whiteSpace: "pre",
textAlign: "center",
background: "#fff",
border: "1px solid #edefed",
borderRadius: "3px",
outline: "none",
transform: "translate(-50%, -50%)",
resize: "none",
zIndex: "1000",
left: `${e.pageX}px`,
top: `${e.pageY}px`,
overflow: "hidden"
});
document.body.appendChild(input);
autoResize();
input.addEventListener("input", autoResize);
input.addEventListener("keydown", (e2) => {
if (e2.key === "Enter" && !e2.shiftKey) {
this.flowChart.updateText(nodeData.id, input.value);
autoResize();
}
});
input.focus();
this.flowChart.on("blank:mousedown", () => {
this.flowChart.updateText(nodeData.id, input.value);
input.remove();
});
this.flowChart.on("node:click", () => {
this.flowChart.updateText(nodeData.id, input.value);
input.remove();
});
this.flowChart.on("edge:click", () => {
this.flowChart.updateText(nodeData.id, input.value);
input.remove();
});
}
updateText(nodeId, text) {
this.flowChart.updateNode(nodeId, {
text: { value: text }
});
}
destroyFlowChart() {
if (this.flowChart) {
this.flowChart.destroy();
this.flowChart = null;
}
const editInputs = document.querySelectorAll(".ql-flow-chart-edit-input");
editInputs.forEach((input) => input.remove());
if (this.nextPObserver) {
this.nextPObserver.disconnect();
this.nextPObserver = null;
}
}
remove() {
this.destroyFlowChart();
super.remove();
}
};
__publicField(_FlowChartPlaceholderBlot, "blotName", "flow-chart");
__publicField(_FlowChartPlaceholderBlot, "tagName", "div");
__publicField(_FlowChartPlaceholderBlot, "className", "ql-flow-chart-item");
let FlowChartPlaceholderBlot = _FlowChartPlaceholderBlot;
export {
FlowChartPlaceholderBlot as default
};
//# sourceMappingURL=flow-chart-blot.es.js.map