@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.
357 lines (356 loc) • 12.3 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 { MindMapResizeAction } from "../modules/custom-resize-action.es.js";
/* empty css */
const BlockEmbed = Quill.import("blots/embed");
const _MindMapPlaceholderBlot = class _MindMapPlaceholderBlot extends BlockEmbed {
constructor(scroll, domNode) {
super(scroll, domNode);
__publicField(this, "quill", null);
__publicField(this, "mindMap", null);
__publicField(this, "data");
__publicField(this, "zoomCount", 0);
__publicField(this, "contextMenu", null);
__publicField(this, "currentNode", null);
__publicField(this, "width", 100);
__publicField(this, "height", 500);
__publicField(this, "parentObserver", null);
__publicField(this, "nextPObserver", null);
const data = _MindMapPlaceholderBlot.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 = _MindMapPlaceholderBlot.value(domNode);
this.initMindMap();
}
static value(domNode) {
const dataStr = JSON.parse(domNode.getAttribute("data-mind-map"));
const value = 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;
}
static create(value) {
const node = super.create();
if (value) {
node.setAttribute("data-mind-map", 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 = _MindMapPlaceholderBlot.findQuill(this.domNode);
}
initMindMap() {
if (this.domNode.isConnected) {
this.insertMindMapEditor();
} else {
const observer = new MutationObserver(() => {
if (this.domNode.isConnected) {
this.insertMindMapEditor();
observer.disconnect();
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
}
insertMindMapEditor() {
this.domNode.style.width = `${this.width}${this.data.width ? "px" : "%"}`;
this.domNode.style.height = `${this.height}px`;
while (this.domNode.firstChild) {
this.domNode.removeChild(this.domNode.firstChild);
}
this.updateAlignmentStyle();
this.observeParentAlignment();
const { backgroundConfig, resizeConfig, lineConfig, themeConfig, deps } = getAllConfigs(this.quill);
const { SimpleMindMap, Themes, Drag, Export } = deps || window;
Themes.init(SimpleMindMap);
SimpleMindMap.usePlugin(Drag).usePlugin(Export);
this.mindMap = new SimpleMindMap({
el: this.domNode,
mousewheelAction: "zoom",
disableMouseWheelZoom: true,
layout: this.data.layout,
theme: themeConfig,
data: this.data.root ? this.data.root : this.data
});
const styleConfig = { ...backgroundConfig };
if (lineConfig && typeof lineConfig === "object") {
Object.assign(styleConfig, lineConfig);
}
this.mindMap.setThemeConfig(styleConfig);
const handleScroll = () => {
if (this.mindMap && this.domNode && this.domNode.isConnected) {
this.mindMap.getElRectInfo();
}
};
window.addEventListener("scroll", handleScroll, { passive: true });
this.domNode.addEventListener("remove", () => {
window.removeEventListener("scroll", handleScroll);
});
if (resizeConfig) {
new MindMapResizeAction(this);
}
createControlPanel(this, this.quill);
initContextMenu(this, this.quill);
this.observeOwnParentChange();
this.observeNextPElement();
this.addMouseHoverEvents();
this.mindMap.on("node_tree_render_end", () => {
this.data = this.mindMap.getData({});
this.domNode.setAttribute("data-mind-map", JSON.stringify(this.data));
});
this.mindMap.on("node_dblclick", this.handleNodeDblClick.bind(this));
this.domNode.addEventListener("click", (e) => {
if (this.quill) {
this.mindMap.getElRectInfo();
const mindMapBlot = Quill.find(this.domNode);
const index = this.quill.getIndex(mindMapBlot);
if (index && typeof index === "number") {
this.quill.setSelection(index + 1, 0);
}
}
});
}
addMouseHoverEvents() {
this.domNode.addEventListener("mouseenter", () => {
this.showControlPanel();
});
this.domNode.addEventListener("mouseleave", () => {
this.mindMap.execCommand("CLEAR_ACTIVE_NODE");
this.hideControlPanel();
});
}
getControlElements() {
const leftUpControl = this.domNode.querySelector(".ql-mind-map-left-up-control");
const control = this.domNode.querySelector(".ql-mind-map-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 = "inline-flex";
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") || `mind-map-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(node, e) {
if (node && node.uid && e) {
this.currentNode = node;
this.createEditInput(node, e);
}
}
// 创建编辑输入框
createEditInput(node, e) {
const input = document.createElement("textarea");
input.className = "ql-mind-map-edit-input";
input.value = node.nodeData.data.text || "";
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",
fontFamily: "微软雅黑",
fontSize: "16px",
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 ? e.pageX : 100}px`,
top: `${e.pageY ? e.pageY : 100}px`,
overflow: "hidden"
});
document.body.appendChild(input);
autoResize();
input.addEventListener("keydown", (event) => {
if (event.key === "Enter" && event.shiftKey) {
autoResize();
}
});
input.focus();
const currentNode = node;
const handleDrawClick = () => {
this.updateNodeText(input.value, currentNode);
input.remove();
this.mindMap.off("draw_click", handleDrawClick);
};
this.mindMap.on("draw_click", handleDrawClick);
input.addEventListener("keydown", (event) => {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
this.updateNodeText(input.value, currentNode);
input.remove();
this.mindMap.off("draw_click", handleDrawClick);
}
});
}
updateNodeText(text, node) {
if (node && this.mindMap) {
node.setText(text);
const editWraps = document.querySelectorAll(".smm-node-edit-wrap");
editWraps.forEach((editWrap) => {
const input = editWrap.querySelector("input, textarea");
if (input) {
input.value = text;
} else {
editWrap.textContent = text;
}
});
this.data = this.mindMap.getData({});
this.domNode.setAttribute("data-mind-map", JSON.stringify(this.data));
}
}
format(name, value) {
}
remove() {
this.mindMap.destroy();
if (this.nextPObserver) {
this.nextPObserver.disconnect();
this.nextPObserver = null;
}
super.remove();
}
};
__publicField(_MindMapPlaceholderBlot, "blotName", "mind-map");
__publicField(_MindMapPlaceholderBlot, "tagName", "div");
__publicField(_MindMapPlaceholderBlot, "className", "ql-mind-map-item");
let MindMapPlaceholderBlot = _MindMapPlaceholderBlot;
export {
MindMapPlaceholderBlot as default
};
//# sourceMappingURL=mind-map-blot.es.js.map