sp-editor
Version:
SpEditor is a HTML5 rich text editor in smartphone browsers
1,323 lines (1,322 loc) • 42.1 kB
JavaScript
/*!
* sp-editor version 1.1.1
* Author: capricorncd
* Released under the MIT License
* Repository: https://github.com/capricorncd/zx-editor
* Copyright © 2018-present, capricorncd/ Xing Zhong.
*/
var ye = Object.defineProperty;
var we = (t, e, i) => e in t ? ye(t, e, { enumerable: !0, configurable: !0, writable: !0, value: i }) : t[e] = i;
var d = (t, e, i) => (we(t, typeof e != "symbol" ? e + "" : e, i), i);
class Ee {
constructor() {
this._events = {};
}
/**
* @method on(eventName, fn)
* `on` is used to add a callback function that's going to be executed when the event is triggered.
* @param eventName `string` custom event name.
* @param fn `Function` callback function.
* @returns `EventEmitter`
*/
on(e, i) {
return !e || !i || typeof i != "function" ? this : (this._events[e] || (this._events[e] = []), this._events[e].push(i), this);
}
/**
* @method once(eventName, fn)
* `once` add a one-time listener.
* @param eventName `string` custom event name.
* @param fn `Function` callback function.
* @returns `EventEmitter`
*/
once(e, i) {
const s = (...n) => {
i.apply(this, n), this.off(e, s);
};
return this.on(e, s);
}
/**
* @method emit(eventName, arg1, arg2, ..., argN)
* `emit` is used to trigger an event.
* @param eventName `string`
* @param args `any`
* @returns `EventEmitter`
*/
emit(e, ...i) {
const s = this._events[e];
if (!s)
return this;
for (let n = 0; n < s.length; n++)
try {
s[n].apply(this, i);
} catch (r) {
this.emit("error", r, "emit");
}
return this;
}
/**
* @method off(eventName, fn)
* remove an event listener from an event.
* @param eventName `string` custom event name.
* @param fn? `Function` callback function. When `fn` is not a function, all monitoring functions of `eventName` will be removed.
* @returns `EventEmitter`
*/
off(e, i) {
if (!this._events[e])
return this;
const s = this._events[e];
if (typeof i == "function") {
const n = s.findIndex((r) => r === i);
n >= 0 && s.splice(n, 1);
} else
this._events[e].length = 0;
return this._removeEmpty(e), this;
}
/**
* _removeEmpty(eventName)
* remove empty event list
* @param eventName `string`
* @private
* @returns `void`
*/
_removeEmpty(e) {
this._events[e].length || delete this._events[e];
}
/**
* @method destroyEventEmitter()
* remove all listeners for an event.
* @returns `void`
*/
removeAllListeners() {
Object.keys(this._events).forEach((e) => this.off(e));
}
}
/*!
* zx-sml version 0.7.5
* Author: Capricorncd <capricorncd@qq.com>
* Repository: https://github.com/capricorncd/zx-sml
* Released on: 2023-05-17 20:41:58 (GMT+0900)
*/
var _e = Object.defineProperty, M = Object.getOwnPropertySymbols, ve = Object.prototype.hasOwnProperty, be = Object.prototype.propertyIsEnumerable, B = (t, e, i) => e in t ? _e(t, e, { enumerable: !0, configurable: !0, writable: !0, value: i }) : t[e] = i, k = (t, e) => {
for (var i in e || (e = {}))
ve.call(e, i) && B(t, i, e[i]);
if (M)
for (var i of M(e))
be.call(e, i) && B(t, i, e[i]);
return t;
};
function Ne(t) {
return Array.isArray(t);
}
function Y(t) {
return typeof t == "object" && t !== null && !Ne(t);
}
function K(t = "", e = "-") {
return t.replace(/[A-Z]/g, (i, s) => `${s > 0 ? e : ""}${i.toLowerCase()}`);
}
function Z(t = "", e = !1) {
const i = t.replace(/[-_\s](\w)/g, (s, n) => n.toUpperCase());
return e ? i.replace(/^\w/, (s) => s.toUpperCase()) : i;
}
function J(t) {
return t.replace(/^-?[1-9]\d{0,2}(,\d{3})+/, (e) => e.replace(/,/g, ""));
}
function Q(t, e = !1) {
if (typeof t == "number")
return t;
if (typeof t == "string") {
if (!e && /^(-?\d+(?:\.\d+)?)\D*/.test(J(t)))
return Q(RegExp.$1, !0);
const i = Number(t);
return isNaN(i) ? 0 : i;
}
return 0;
}
function $e(t) {
if (typeof t == "number")
return [t, ""];
const e = J(t).match(/^(-?\d+(?:\.\d+)?)(.*)$/);
return e ? [Q(e[1], !0), e[2]] : [0, ""];
}
function X(t) {
return typeof t == "string" ? t : t === null || typeof t > "u" ? "" : Array.isArray(t) ? t.map(X).join(" ") : typeof t == "object" ? Object.keys(t).filter((e) => t[e]).join(" ") : String(t);
}
function Ce(...t) {
return t.map(X).filter((e) => !!e).join(" ");
}
function b(t, e = 0) {
return Array.prototype.slice.call(t, e);
}
function ee(t = {}, e = !1) {
const i = e ? Z : K, s = {};
for (const [n, r] of Object.entries(t))
s[i(n)] = Y(r) ? ee(r, e) : r;
return s;
}
function v(t, e = document) {
return t ? t instanceof HTMLElement ? t : e.querySelector(t) : null;
}
function $(t, e = document) {
return b(e.querySelectorAll(t));
}
function f(t, e = {}, i) {
const s = document.createElement(t);
for (const [n, r] of Object.entries(e))
s.setAttribute(K(n), n === "style" && Y(r) ? I(r) : String(r));
return i && (Array.isArray(i) || (i = [i]), i.forEach((n) => {
if (typeof n == "string") {
const r = f("div");
r.innerHTML = n, s.append(...r.childNodes);
} else
s.append(n);
})), s;
}
function I(...t) {
const e = t.reduce((s, n) => k(k({}, s), ee(n)), {}), i = [];
for (const [s, n] of Object.entries(e))
n === "" || typeof n > "u" || n === null || i.push(`${s}:${n}`);
return i.join(";");
}
const A = (t, e = "style") => t ? (t.getAttribute(e) || "").split(/\s?;\s?/).reduce((s, n) => {
const [r, o] = n.split(/\s?:\s?/);
return r && (s[Z(r)] = o), s;
}, {}) : {}, O = (t) => document.createTextNode(t), te = (t) => {
if (!t)
return null;
if (typeof t == "string")
return O(t);
const { tag: e, attrs: i, child: s } = t;
if (!e && !i && !s)
return null;
const n = f(e || "div", i);
if (Array.isArray(s) && s.length) {
let r;
s.forEach((o) => {
r = te(o), r && n.appendChild(r);
});
} else
s && typeof s == "string" && n.appendChild(O(s));
return n;
}, xe = (t, e) => {
t.classList.add(e);
}, Le = (t, e) => {
t.classList.remove(e);
}, Te = "sp-editor__editor", D = "is-empty", ie = "SECTION", He = "BR", se = [ie, "H1", "H2", "H3", "H4", "H5", "BLOCKQUOTE", "UL", "OL"];
function N(t, e, i) {
return t.replace(
new RegExp("(^<" + e + ")|(" + e + ">$)", "gi"),
(s) => s.replace(new RegExp(e, "i"), i.toLowerCase())
);
}
function Se(t) {
return t.replace(/<li[^>]*>(.+)<\/li>/gi, "$1");
}
function L(t) {
const e = typeof t == "string" ? t : t.nodeName;
return /^UL|OL$/i.test(e);
}
function j(t) {
if (!t)
return !1;
const e = b(t.childNodes);
return e.length > 0 && e.every((i) => i.nodeName === "BR");
}
function T(t) {
return typeof t != "string" && t.nodeName && (t = t.outerHTML), typeof t == "string" && /^<(\w+)[^>]*>.*<\/\1>$/.test(t.replace(/\n/g, ""));
}
function ne(t) {
return ["PICTURE", "VIDEO", "AUDIO", "CANVAS"].includes(t.nodeName);
}
function Ae(t) {
return ["IMG"].includes(t.nodeName) || ne(t);
}
function re(t) {
if (Ae(t))
return !0;
for (let e = 0; e < t.children.length; e++)
if (re(t.children[e]))
return !0;
return !1;
}
const Oe = (t, e) => {
const i = {
minHeight: t.minHeight,
// placeholder
"--placeholder": JSON.stringify(t.placeholder),
"--placeholder-color": t.placeholderColor,
"--line-height": t.lineHeight,
// paragraphTailSpacing
"--paragraph-spacing": t.paragraphTailSpacing,
"--padding-bottom": t.paddingBottom,
// 用户自定义样式优先
...t.styles
};
t.caretColor && (i.caretColor = t.caretColor), t.textColor && (i.color = t.textColor);
const s = {
class: `${Te} ${D}`,
style: I(i)
};
return t.editable && (s.contenteditable = "true"), f("div", s, e);
}, F = (t, e) => {
var a, l, E, u, c, w, m, h, p;
if (!t)
return null;
const i = t.nodeName, s = e.toUpperCase();
if (i === s)
return null;
const n = f(e), r = t.parentElement;
let o;
if (i === "LI" && L(r)) {
if (n.innerHTML = N(t.outerHTML, i, s), o = n.firstChild, r.childElementCount > 1)
if (r.firstElementChild === t)
(a = r.parentElement) == null || a.insertBefore(o, r);
else if (r.lastElementChild === t) {
const g = (l = r.parentElement) == null ? void 0 : l.nextElementSibling;
g ? (E = g.parentElement) == null || E.insertBefore(o, g) : (u = r.parentElement) == null || u.append(o);
} else {
const g = b(r.children), y = f(r.nodeName);
let _ = g.shift();
for (; _ && _ !== t; )
y.append(_), _ = g.shift();
(c = r.parentElement) == null || c.insertBefore(y, r), (w = r.parentElement) == null || w.insertBefore(o, r), r.removeChild(t);
}
else
(m = r.parentElement) == null || m.insertBefore(o, r), (h = r.parentElement) == null || h.removeChild(r);
return o;
}
if (/UL|OL/.test(s)) {
const g = t.previousElementSibling, y = t.nextElementSibling;
if (g && L(g)) {
if (n.innerHTML = N(t.outerHTML, i, "li"), o = n.firstChild, g.append(o), r == null || r.removeChild(t), y && y.nodeName === g.nodeName) {
const _ = b(y.children);
g.append(..._), (p = y.parentElement) == null || p.removeChild(y);
}
} else
y && L(y) ? (n.innerHTML = N(t.outerHTML, i, "li"), o = n.firstChild, y.insertBefore(o, y.firstElementChild), r == null || r.removeChild(t)) : (o = n, n.innerHTML = N(t.outerHTML, i, "li"), r == null || r.replaceChild(o, t));
} else
n.innerHTML = Se(N(t.outerHTML, i, s)), o = n.firstChild, r == null || r.replaceChild(o, t);
return o;
}, H = (t) => {
var e;
!((e = t.innerText) != null && e.trim()) && !re(t) ? t.classList.add(D) : t.classList.remove(D);
};
function De(t, e, i = !1) {
var s;
for (; t && e !== t; ) {
if (!i && t.nodeName === "LI" && ((s = t.parentElement) == null ? void 0 : s.parentElement) === e || t.parentElement === e)
return t;
t = t.parentElement;
}
return e.lastElementChild;
}
const Pe = {
// 内容是否可以被编辑
editable: !0,
// 编辑器输入内容绝对定位
// editor min height
minHeight: "50vh",
paddingBottom: "50vh",
// style
placeholder: "请在此输入内容..",
placeholderColor: "#999",
lineHeight: 1.5,
childNodeName: ie,
allowedNodeNames: se,
// paragraph tail spacing, default 10px
paragraphTailSpacing: "10px",
caretColor: "",
textColor: "#333333",
// 自定义粘贴处理函数
customPasteHandler: void 0,
insertTextToNewParagraph: !1
};
class Ie extends Ee {
constructor(i) {
super();
/**
* @property version
* 获取版本号
* @returns `string`
*/
d(this, "version");
// 参数
d(this, "options");
// 编辑器内容区域HTML元素
d(this, "$editor");
// 内容元素事件处理函数
d(this, "_eventHandler");
// 内容中允许使用的元素标签
d(this, "allowedNodeNames");
d(this, "blankLine");
d(this, "range");
d(this, "_pasteHandler");
const s = typeof i.container == "string" ? v(i.container) : i.container;
if (!s)
throw new Error(`Can't found '${i.container}' Node in document!`);
this.version = "1.1.1", this.options = { ...Pe, ...i }, this.allowedNodeNames = (this.options.allowedNodeNames || se).map((r) => r.toUpperCase());
const n = this.options.childNodeName.toUpperCase();
this.options.childNodeName = n, this.blankLine = `<${n}><br></${n}>`, this.allowedNodeNames.includes(n) || this.allowedNodeNames.push(n), this.$editor = Oe(this.options, this.blankLine), s.append(this.$editor), this.range = new Range(), this.range.setStart(this.$editor.children[0], 0), this._eventHandler = (r) => {
var a;
const o = r.type;
if (o === "click") {
const l = (a = window.getSelection()) == null ? void 0 : a.getRangeAt(0);
l && (this.range = l);
} else
o === "blur" && this._verifyChild();
this.emit(o === "input" ? "change" : o, r), H(this.$editor);
}, this._pasteHandler = (r) => {
var a;
if (typeof this.options.customPasteHandler == "function")
return this.options.customPasteHandler(r);
r.preventDefault();
const o = (a = r.clipboardData) == null ? void 0 : a.getData("text");
this.insert(o);
}, this._initEvents();
}
/**
* init events
* @private
*/
_initEvents() {
this.$editor.addEventListener("focus", this._eventHandler), this.$editor.addEventListener("blur", this._eventHandler), this.$editor.addEventListener("input", this._eventHandler), this.$editor.addEventListener("click", this._eventHandler), this.$editor.addEventListener("paste", this._pasteHandler);
}
setRangeWithNode(i) {
const s = new Range();
return s.setStart(i === this.$editor ? this.$editor.childNodes[this.$editor.childNodes.length - 1] : i, 0), this.range = s, s;
}
/**
* @method use(plugin, parentElement)
* extension, 扩展插件
* @param plugin `EditorPlugin`
* @param parentElement? `HTMLElement`
*/
use(i, s) {
typeof i.install == "function" && i.install(this, s);
}
/**
* @method setHtml(html)
* 设置编辑器内容,会覆盖之前内容
* set html to the content element
* @param html `string`
*/
setHtml(i) {
this.$editor.innerHTML = this.blankLine, this.insert(i, !0), H(this.$editor);
}
/**
* @method getHtml(retainLastBlankLines)
* 获取编辑器中的HTML代码,会自动去除结尾处的空行。
* get html string from content element.
* @param retainLastBlankLines? `boolean` Retain last blank lines, If `true` the last `<section><br></section>` not will be removed.
* @return `string`
*/
getHtml(i) {
const s = this.$editor.innerHTML;
if (i)
return s;
const n = this.options.childNodeName;
return s.replace(new RegExp(`(<${n}><br\\s?\\/?><\\/${n}>)+$`, "i"), "");
}
/**
* @method insert(input, toNewParagraph)
* 向编辑器中插入内容/HTML代码/元素等
* insert html or element to content element
* @param input `string | HTMLElement`
* @param toNewParagraph? `boolean` Insert `text` in a new paragraph, only `textNode` is valid. Defaults to `false`.
*/
insert(i, s = !1) {
if (!i)
return;
const { childNodeName: n, insertTextToNewParagraph: r } = this.options;
if (i instanceof HTMLElement)
this._insertEl(i);
else {
const o = f("div", {}, i), a = b(o.childNodes);
if (!s && !r && a.every((l) => l.nodeType === Node.TEXT_NODE))
return this._insertText(i);
a.forEach((l) => {
l.nodeType === Node.ELEMENT_NODE ? l.nodeName === He ? this._insertEl(f(n, {}, "<br/>")) : this._insertEl(l) : l.textContent && this._insertEl(f(n, {}, l.textContent));
});
}
this._verifyChild(), this._dispatchChange(), H(this.$editor);
}
/**
* insert element to content element
* @param input
* @private
*/
_insertEl(i) {
const s = this.getCursorElement();
j(s) ? T(i.outerHTML) ? this.$editor.insertBefore(i, s) : s === this.$editor.children[this.$editor.children.length - 1] ? this.$editor.insertBefore(f(this.options.childNodeName, {}, i), s) : (s.innerHTML = "", s.append(i)) : (T(i.outerHTML) || (i = f(this.options.childNodeName, {}, i)), s.nextElementSibling ? this.$editor.insertBefore(i, s.nextElementSibling) : this.$editor.append(i)), this.setRangeWithNode(i);
}
/**
* insert text into editor
* @param input
* @returns
*/
_insertText(i) {
if (!i)
return;
const s = window.getSelection();
if (!(s == null ? void 0 : s.rangeCount))
return this.insert(i, !0);
s.deleteFromDocument();
const r = O(i);
s.getRangeAt(0).insertNode(r), s.setPosition(r, i.length), this.range = s.getRangeAt(0), s.collapseToEnd();
}
/**
* 验证编辑器的子元素是否为允许使用的元素,并检查其最后一段是否为空行,非空行则插入。
* Verify that the editor's child element is an allowed elements, and check if it's last child is a blank line, if not, insert a new blank line
* @private
*/
_verifyChild() {
const i = this.options.childNodeName;
let s, n, r = !1;
const o = this.$editor.childNodes;
for (let a = 0; a < o.length; a++)
n = null, s = o[a], r = s === this.range.commonAncestorContainer, s.nodeType === Node.ELEMENT_NODE && T(s) && !ne(s) ? this.allowedNodeNames.includes(s.nodeName) || (n = F(s, i)) : (n = f(i, {}, s.cloneNode(!0)), this.$editor.replaceChild(n, s)), r && n && this.setRangeWithNode(n);
j(this.$editor.lastElementChild) || this.$editor.append(f(i, {}, "<br>"));
}
/**
* @method changeNodeName(nodeName)
* 修改光标所在元素的标签
* Replace the tag of the element under the cursor
* @param nodeName? `string` allowed element names, `UL`, `SECTION` etc. If `undefined`, use the default `options.childNodeName`.
* @return `boolean`
*/
changeNodeName(i) {
if (i = (i || this.options.childNodeName).toUpperCase(), !this.allowedNodeNames.includes(i))
return !1;
const s = this.getCursorElement(), n = F(s, i);
return n ? (this.setRangeWithNode(n), this._dispatchChange(), !0) : !1;
}
/**
* @method changeStyles(styles, value)
* 修改光标所在元素的样式
* Change the style of the element where the cursor is located
* @param styles? `CSSProperties | string` When it's `undefined` or null, all styles will be removed.
* @param value? `any`
*/
changeStyles(i, s) {
const n = this.getCursorElement(!0);
if (n) {
const r = A(n);
if (i) {
const o = typeof i == "string" ? { [i]: s } : i;
n.setAttribute("style", I(r, o));
} else {
if (!Object.keys(r).length)
return;
n.removeAttribute("style");
}
this._dispatchChange();
}
}
/**
* 分派事件
*/
_dispatchChange() {
this.$editor.dispatchEvent(new InputEvent("input"));
}
/**
* @method getStyles()
* 获取光标所在的元素的`style`对象
* Get the `style` object of the element where the cursor is located
* @return `CSSProperties`
*/
getStyles() {
return A(this.getCursorElement());
}
/**
* @method getCursorElement(isOnlyEditorChild)
* 获取光标所在的元素
* Get the element where the cursor is located
* @param isOnlyEditorChild? `boolean` Must be a child element of editor `HTMLElement`. For example: when it is `false`, the `li` element is returned in `ul/ol`, and when it is `true`, the `ul/ol` element is returned.
* @return `HTMLElement`
*/
getCursorElement(i = !1) {
return De(this.range.commonAncestorContainer, this.$editor, i);
}
/**
* @method destroy()
* 销毁事件
* destroy events
*/
destroy() {
this.$editor.removeEventListener("focus", this._eventHandler), this.$editor.removeEventListener("blur", this._eventHandler), this.$editor.removeEventListener("input", this._eventHandler), this.$editor.removeEventListener("paste", this._pasteHandler), this.removeAllListeners();
}
}
const Re = ["#333333", "#d0d0d0", "#ff583d", "#fdaa25", "#44c67b", "#14b2e0", "#b065e2"], Me = {
tag: "dl",
attrs: {
class: "__style-wrapper border-bottom"
},
child: [
{
tag: "dd",
attrs: {
style: "font-weight: 800;",
"data-style": "fontWeight:800"
},
child: ["B"]
},
{
tag: "dd",
attrs: {
style: "font-style: italic;",
"data-style": "fontStyle:italic"
},
child: ["I"]
},
{
tag: "dd",
attrs: {
style: "text-decoration: line-through;",
"data-style": "textDecoration:line-through"
},
child: ["abc"]
},
{
tag: "dd",
attrs: {
style: "",
"data-style": "textAlign:left",
class: "text-align--l"
}
},
{
tag: "dd",
attrs: {
style: "",
"data-style": "textAlign:center",
class: "text-align--c"
}
},
{
tag: "dd",
attrs: {
style: "",
"data-style": "textAlign:right",
class: "text-align--r"
}
}
]
}, U = {
tag: "dl",
attrs: {
class: "__tag-wrapper"
},
child: [
{
tag: "dd",
attrs: {
class: "__h2",
"data-tag": "h2"
},
child: ["大标题", { tag: "i" }]
},
{
tag: "dd",
attrs: {
class: "__h4",
"data-tag": "h4"
},
child: ["小标题", { tag: "i" }]
},
{
tag: "dd",
attrs: {
class: "__section active",
"data-tag": "section"
},
child: ["正文", { tag: "i" }]
},
{
tag: "dd",
attrs: {
class: "__blockquote",
"data-tag": "blockquote"
},
child: ["引用", { tag: "i" }]
},
{
tag: "dd",
attrs: {
class: "__ul",
"data-tag": "ul"
},
child: ["无序列表", { tag: "i" }]
}
]
}, Be = (t) => {
const e = [];
return t.forEach((i, s) => {
/^#\w{3,6}$/.test(i) && e.push({
tag: "dd",
attrs: {
class: s === 0 ? "active" : "",
"data-color": ke(i.toLowerCase())
},
child: [
{
tag: "i",
attrs: {
style: `background:${i}`
}
}
]
});
}), e;
}, ke = (t) => t.length === 7 ? t : `#${t[1]}${t[1]}${t[2]}${t[2]}${t[3]}${t[3]}`, je = {
textStyleColors: [...Re],
textStyleTitle: "Set Style",
textStyleHeadLeftBtnText: "Clear"
};
const C = "style-panel", S = `${C}__fade-in`;
class Fe {
constructor(e) {
d(this, "editorInstance", null);
d(this, "$el");
d(this, "options");
d(this, "_headerSwitchHandler");
d(this, "_headerLeftHandler");
d(this, "$elMap", /* @__PURE__ */ new Map());
d(this, "_styleHandler");
d(this, "_colorHandler");
d(this, "_tagHandler");
const i = {
...je,
...e
};
this.options = i, this.$el = f("div", { class: `${C} border-top` }), this._styleHandler = (s) => {
const n = this.editorInstance, r = s.currentTarget, o = A(r, "data-style"), a = n.getStyles();
Object.keys(o).forEach((l) => {
a[l] && (o[l] = "");
}), n.changeStyles(o);
}, this._colorHandler = (s) => {
const n = s.currentTarget;
if (this.updateActiveClassName(n)) {
const r = this.editorInstance, o = n.getAttribute("data-color");
r.changeStyles({ color: o });
}
}, this._tagHandler = (s) => {
const n = s.currentTarget;
if (this.updateActiveClassName(n)) {
const r = this.editorInstance, o = n.getAttribute("data-tag");
r.changeNodeName(o);
}
}, this._headerLeftHandler = () => {
const s = this.editorInstance, { textColor: n, childNodeName: r } = s.options;
s.changeStyles(), s.changeNodeName(), this.updateActiveClassName(v(`[data-color="${n}"]`, this.$el)), this.updateActiveClassName(v(`[data-tag="${r}"]`, this.$el));
}, this._headerSwitchHandler = () => {
this.$el.classList.contains(S) ? this.hide() : this.show();
};
}
_initChildEl(e) {
const { textColor: i, childNodeName: s } = e, { textStyleTitle: n, textStyleHeadLeftBtnText: r, textStyleColors: o } = this.options, a = f("div", { class: `${C}__header` }, n), l = f("div", { class: "__left" }, r), E = f("div", { class: "__switch" });
a.append(l, E);
const u = [Me], c = o;
if (c.length) {
i && !c.includes(i) && c.unshift(i);
const p = {
tag: "dl",
attrs: {
class: "__color-wrapper border-bottom"
},
child: Be(c)
};
u.push(p);
}
const w = {
...U,
child: [...U.child]
}, m = s.toLowerCase();
w.child.forEach((p) => {
const g = p.attrs["data-tag"];
g === "section" && g !== m && (p.attrs["data-tag"] = m);
}), u.push(w);
const h = te({
tag: "div",
attrs: {
class: `${C}__body`
},
child: u
});
this.$el.append(a, h), $(".__style-wrapper dd", h).forEach((p) => {
p.addEventListener("click", this._styleHandler);
}), $(".__color-wrapper dd", h).forEach((p) => {
p.addEventListener("click", this._colorHandler);
}), $(".__tag-wrapper dd", h).forEach((p) => {
p.addEventListener("click", this._tagHandler);
}), l.addEventListener("click", this._headerLeftHandler), E.addEventListener("click", this._headerSwitchHandler), this.$elMap.set(l, this._headerLeftHandler), this.$elMap.set(E, this._headerSwitchHandler);
}
install(e, i) {
this.editorInstance = e, i && i.append(this.$el), this._initChildEl(e.options), e.on("click", () => {
const { textColor: s, childNodeName: n } = e.options, r = e.getStyles();
this.updateActiveClassName(v(`[data-color="${r.color || s}"]`, this.$el));
const o = e.getCursorElement(!0).nodeName.toLowerCase();
this.updateActiveClassName(v(`[data-tag="${o || n}"]`, this.$el));
});
}
show() {
this.$el.classList.add(S);
}
hide() {
this.$el.classList.remove(S);
}
updateActiveClassName(e) {
return !e || e.classList.contains("active") ? !1 : (v(".active", e.parentElement).classList.remove("active"), e.classList.add("active"), !0);
}
destroy() {
this.$elMap.forEach((e, i) => {
i.removeEventListener("click", e);
});
}
}
const Ue = {
toolbarBeenFixed: !0,
// toolbarHeight: '50px',
// buttons name, and order
toolbarButtons: ["choose-picture", "text-style"]
};
class We {
constructor(e) {
d(this, "editorInstance", null);
d(this, "visible");
d(this, "options");
d(this, "$el");
d(this, "_btnClickHandler");
this.options = {
...Ue,
...e
}, this.visible = this.options.toolbarBeenFixed;
const [i, s] = $e(this.options.toolbarHeight || "");
this.$el = f(
"div",
{
class: "sp-editor__toolbar border-top",
style: {
"--bar-height": i ? `${i}${s}` : null
}
},
'<dl class="inner-wrapper"></dl>'
), this._btnClickHandler = (n) => {
const r = n.currentTarget;
this.editorInstance && r && this.editorInstance.emit("toolbarButtonOnClick", r.getAttribute("data-name"));
}, this.options.toolbarButtons.forEach((n) => {
this.addButton({ name: n });
});
}
install(e, i) {
this.editorInstance = e, i && i.append(this.$el), this.visible && this.show();
}
show() {
xe(this.$el, "__fade-in"), this.visible = !0, this.editorInstance.emit("toolbarShow", !0, this);
}
hide() {
Le(this.$el, "__fade-in"), this.visible = !1, this.editorInstance.emit("toolbarShow", !1, this);
}
/**
* add button
* @param params
* @param index Insert index
*/
addButton(e, i) {
const s = { ...e.style }, n = f(
"dd",
{
class: Ce("icon-item", e.className),
dataName: e.name,
style: s
},
e.innerHtml
), r = $("dd", this.$el), o = v("dl", this.$el);
typeof i == "number" && i >= 0 && i < r.length ? o.insertBefore(n, r[i]) : o.append(n), n.addEventListener("click", this._btnClickHandler);
}
destroy() {
$(".icon-item", this.$el).forEach((e) => {
e.removeEventListener("click", this._btnClickHandler);
});
}
}
/*!
* image-process version 4.3.1
* Author: Xing Zhong <capricorncd@qq.com, zx198401@gmail.com>
* Repository: https://github.com/capricorncd/image-process-tools
* Released on: 2022-12-30 14:17:09 (GMT+0900)
*/
/*!
* zx-sml version 0.6.0
* Author: Xing Zhong<zx198401@gmail.com>
* Repository: https://github.com/capricorncd/zx-sml
* Released on: 2022-09-09 21:21:26 (GMT+0900)
*/
var ze = Object.defineProperty, W = Object.getOwnPropertySymbols, Ge = Object.prototype.hasOwnProperty, Ve = Object.prototype.propertyIsEnumerable, z = (t, e, i) => e in t ? ze(t, e, { enumerable: !0, configurable: !0, writable: !0, value: i }) : t[e] = i, G = (t, e) => {
for (var i in e || (e = {}))
Ge.call(e, i) && z(t, i, e[i]);
if (W)
for (var i of W(e))
Ve.call(e, i) && z(t, i, e[i]);
return t;
};
function qe(t) {
return Array.isArray(t);
}
function oe(t) {
return t !== null && !qe(t) && typeof t == "object";
}
var Ye = typeof globalThis < "u" ? globalThis : typeof window < "u" ? window : typeof global < "u" ? global : typeof self < "u" ? self : {}, x = { exports: {} };
/*! For license information please see date-utils-2020.js.LICENSE.txt */
(function(t, e) {
(function(i, s) {
t.exports = s();
})(typeof self < "u" ? self : Ye, function() {
return (() => {
var i = { 949: (n, r) => {
Object.defineProperty(r, "__esModule", { value: !0 }), r.toTwoDigits = void 0, r.toTwoDigits = function(o) {
return o[1] ? o : "0" + o;
};
}, 607: (n, r, o) => {
Object.defineProperty(r, "__esModule", { value: !0 }), r.toTwoDigits = r.toDate = r.formatDate = void 0;
var a = o(949);
Object.defineProperty(r, "toTwoDigits", { enumerable: !0, get: function() {
return a.toTwoDigits;
} });
var l = { weeks: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] };
function E(u) {
if (u instanceof Date)
return u;
if (typeof u == "number")
return new Date(u);
if (typeof u == "string") {
var c = u.trim();
if (/^\d+$/.test(c)) {
var w = c.length;
return w === 8 ? new Date([c.substr(0, 4), c.substr(4, 2), c.substr(6, 2)].join("/")) : w === 6 ? new Date([c.substr(0, 4), c.substr(4, 2), "01"].join("/")) : w === 4 ? /* @__PURE__ */ new Date(c + "/01/01") : new Date(parseInt(u));
}
if (c = c.replace(/[年月日]/g, function(h) {
return h === "日" ? "" : "/";
}).replace(/[(((].*?[)))]/g, " ").replace(/\bam|pm\b/gi, " ").replace(/\s+/g, " "), /^(\d{4})[-/](\d{1,2})[-/](\d{1,2})$/.test(c))
return new Date([RegExp.$1, RegExp.$2, RegExp.$3].join("/"));
if (/^(\d{4})[-/](\d{1,2})$/.test(c))
return new Date([RegExp.$1, RegExp.$2, "01"].join("/"));
var m = new Date(c);
return isNaN(m.getFullYear()) ? null : m;
}
return null;
}
r.formatDate = function(u, c, w) {
var m, h = E(u);
if (!h || !c)
return u + "";
if (c === "timestamp")
return h.getTime().toString();
/(y+)/i.test(c) && (m = RegExp.$1, c = c.replace(m, (h.getFullYear() + "").substr(4 - m.length))), w && Array.isArray(w.weeks) || (w = l);
var p = { "M+": h.getMonth() + 1, "d+": h.getDate(), "h+": h.getHours(), "m+": h.getMinutes(), "s+": h.getSeconds(), "w+": h.getDay(), "W+": w.weeks[h.getDay()], "a+": h.getHours() < 12 ? "am" : "pm", "A+": h.getHours() < 12 ? "AM" : "PM" };
for (var g in p)
if (new RegExp("(" + g + ")").test(c)) {
m = RegExp.$1;
var y = p[g] + "";
c = c.replace(m, m.length === 1 ? y : a.toTwoDigits(y));
}
if (/(g)/i.test(c)) {
var _ = h.toString().split(/\s+/).slice(5), me = c.includes("g");
c = c.replace(/g/i, me ? _[0] : _.join(" "));
}
return c;
}, r.toDate = E;
} }, s = {};
return function n(r) {
if (s[r])
return s[r].exports;
var o = s[r] = { exports: {} };
return i[r](o, o.exports, n), o.exports;
}(607);
})();
});
})(x);
function ae(t = "", e = "-") {
return t.replace(/[A-Z]/g, (i, s) => `${s > 0 ? e : ""}${i.toLowerCase()}`);
}
function Ke(t = "", e = !1) {
const i = t.replace(/[-_\s](\w)/g, (s, n) => n.toUpperCase());
return e ? i.replace(/^\w/, (s) => s.toUpperCase()) : i;
}
function le(t = {}, e = !1) {
const i = e ? Ke : ae, s = {};
for (const [n, r] of Object.entries(t))
s[i(n)] = oe(r) ? le(r, e) : r;
return s;
}
function ce(t, e = !1, i = 2) {
const s = ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"], n = e ? 1e3 : 1024;
let r = String(t), o = "Byte";
for (let a = 0, l = t / n; l > 1; l /= n, a++)
r = l.toFixed(i), o = s[a];
return e && (o = o.replace("i", "")), {
text: r.replace(/\.0+$/, "") + o,
value: +r,
unit: o,
bytes: t
};
}
function de(t, e = {}, i) {
const s = document.createElement(t);
for (const [n, r] of Object.entries(e))
s.setAttribute(ae(n), n === "style" && oe(r) ? Ze(r) : r);
return i && (Array.isArray(i) || (i = [i]), i.forEach((n) => {
if (typeof n == "string") {
const r = de("div");
r.innerHTML = n, s.append(...r.childNodes);
} else
s.append(n);
})), s;
}
function Ze(...t) {
const e = t.reduce((s, n) => G(G({}, s), le(n)), {}), i = [];
for (const [s, n] of Object.entries(e))
n === "" || typeof n > "u" || n === null || i.push(`${s}:${n}`);
return i.join(";");
}
function Je(t) {
return new Promise((e, i) => {
const s = new FileReader();
s.onload = (n) => {
var r;
const o = (r = n.target) == null ? void 0 : r.result;
o ? e(o) : i(new Error(`FileReader's result is null, ${n.target}`));
}, s.onerror = i, s.readAsDataURL(t);
});
}
function he(t) {
return (window.URL || window.webkitURL).createObjectURL(t);
}
function fe(t) {
const e = t.split(",");
let i = "";
return /data:(\w+\/\w+);base64/.test(e[0]) && (i = RegExp.$1), {
type: i,
data: e[1]
};
}
function ue(t, e) {
const i = fe(t), s = window.atob(i.data);
e = e || i.type;
const n = new Uint8Array(s.length);
for (let r = 0; r < s.length; r++)
n[r] = s.charCodeAt(r);
return new Blob([n], { type: e });
}
x.exports.formatDate;
x.exports.toDate;
x.exports.toTwoDigits;
const Qe = {
enableDevicePixelRatio: !1,
isForce: !1,
mimeType: "image/jpeg",
perResize: 500,
quality: 0.9,
width: 0,
height: 0,
longEdge: 0
}, Xe = /^data:(.+?);base64/, et = /^image\/.+/;
function tt(t, e) {
return new Promise((i, s) => {
var n;
const r = {
...Qe,
...e,
longEdge: (n = e == null ? void 0 : e.longEdge) != null ? n : e == null ? void 0 : e.longestSide
};
typeof t == "string" && Xe.test(t) ? V(t, r, i, s) : (t instanceof File || t instanceof Blob) && et.test(t.type) ? Je(t).then((o) => {
V(o, r, i, s);
}).catch(s) : s(new Error(`Invalid file, ${t}`));
});
}
function V(t, e, i, s) {
const { type: n } = fe(t), r = ue(t, n), o = new Image();
o.onload = () => {
const a = {
element: o,
blob: r,
data: t,
url: he(r),
width: o.naturalWidth || o.width,
height: o.naturalHeight || o.height,
type: n,
size: ce(r.size)
};
e.cropInfo && e.cropInfo.sw && e.cropInfo.sh ? q(a, e, i, s, {
...e.cropInfo,
dx: 0,
dy: 0,
dw: e.cropInfo.sw,
dh: e.cropInfo.sh
}) : e.width && e.height ? q(a, e, i, s, st(a, e)) : e.width || e.height || e.longEdge ? it(a, e, i, s) : P({ ...a, raw: a }, e, i);
}, o.onerror = s, o.src = t;
}
function q(t, e, i, s, n) {
try {
Object.prototype.hasOwnProperty.call(n, "enableDevicePixelRatio") || (n.enableDevicePixelRatio = e.enableDevicePixelRatio);
const r = R(t.element, {
enableDevicePixelRatio: e.enableDevicePixelRatio,
sx: n.sx,
sy: n.sy,
sw: n.sw,
sh: n.sh,
dx: 0,
dy: 0,
dw: n.sw,
dh: n.sh
});
!e.width && !e.height ? e.longEdge ? n.sw > n.sh ? (e.width = e.longEdge, e.height = n.sh * e.width / n.sw) : (e.height = e.longEdge, e.width = n.sw * e.height / n.sh) : (e.width = n.sw, e.height = n.sh) : e.width ? e.height = n.sh * e.width / n.sw : e.width = n.sw * e.height / n.sh, ge(
r,
t,
e,
{
...n,
sx: 0,
sy: 0,
sw: r.width,
sh: r.height
},
i
);
} catch (r) {
s(r);
}
}
function it(t, e, i, s) {
try {
e.longEdge && !e.width && !e.height && (t.width >= t.height ? e.width = e.longEdge : e.height = e.longEdge);
const n = {
enableDevicePixelRatio: e.enableDevicePixelRatio,
sx: 0,
sy: 0,
sw: t.width,
sh: t.height,
dx: 0,
dy: 0,
dw: e.width,
dh: e.height
};
if (e.width) {
if (t.width < e.width && !e.isForce) {
P({ ...t, raw: t }, e, i);
return;
}
n.dh = t.height * e.width / t.width, e.height = n.dh;
} else {
if (t.height < e.height && !e.isForce) {
P({ ...t, raw: t }, e, i);
return;
}
n.dw = t.width * e.height / t.height, e.width = n.dw;
}
ge(t.element, t, e, n, i);
} catch (n) {
s(n);
}
}
function P(t, e, i) {
t.type !== e.mimeType ? (t.type = e.mimeType, pe(
t.element,
t.raw,
e,
{
enableDevicePixelRatio: e.enableDevicePixelRatio,
sx: 0,
sy: 0,
sw: t.width,
sh: t.height,
dx: 0,
dy: 0,
dw: t.width,
dh: t.height
},
i
)) : i(t);
}
function ge(t, e, i, s, n) {
let r = e.width > e.height ? e.width - s.dw : e.height - s.dh;
if (r > i.perResize) {
const o = e.height / e.width;
for (; r > i.perResize; )
r -= i.perResize, s.sw = t.width, s.sh = t.height, s.dw = i.width + r, s.dh = s.dw * o, t = R(t, s);
}
s.sw = t.width, s.sh = t.height, s.dw = i.width, s.dh = i.height, pe(t, e, i, s, n);
}
function pe(t, e, i, s, n) {
const r = R(t, s), o = /^\w+\/\*$/.test(i.mimeType) || !i.mimeType ? e.type : i.mimeType, a = r.toDataURL(o, i.quality), l = ue(a, o);
n({
element: r,
type: o,
width: r.width,
height: r.height,
blob: l,
data: a,
url: he(l),
size: ce(l.size),
raw: e
});
}
function st(t, e) {
const { width: i, height: s } = t, { width: n, height: r } = e;
let o;
const a = s * n / r;
if (i > a)
o = {
sx: (i - a) / 2,
sy: 0,
sw: a,
sh: s
};
else {
const l = i * r / n;
o = {
sx: 0,
sy: (s - l) / 2,
sw: i,
sh: l
};
}
return {
...o,
dx: 0,
dy: 0,
dw: n,
dh: r
};
}
function R(t, e) {
const i = e.enableDevicePixelRatio && window.devicePixelRatio || 1, s = de("canvas");
s.width = e.dw * i, s.height = e.dh * i;
const n = s.getContext("2d");
return n.scale(i, i), n.drawImage(
t,
e.sx,
e.sy,
e.sw,
e.sh,
e.dx,
e.dy,
e.dw,
e.dh
), s;
}
const nt = {
imageMaxWidth: 750,
ignoreGif: !0,
forceImageResize: !1,
chooseFileMultiple: !0,
chooseFileAccept: "image/*"
};
class ot extends Ie {
constructor(i, s = {}) {
let n = null;
if (typeof i == "string" || i instanceof HTMLElement ? n = v(i) : (s = i || {}, typeof s.container == "string" && (n = v(s.container))), s = {
...nt,
...s
}, !n)
throw new Error(`Can't found '${i}' Node in document!`);
const r = f("div", { class: "sp-editor" });
super({
...s,
container: r
});
d(this, "$el");
d(this, "stylePanel");
d(this, "toolbar");
d(this, "fileInput", null);
d(this, "_inputChangeHandler");
n.append(r), this.$el = r, this.stylePanel = new Fe(s), this.use(this.stylePanel, this.$el), this.toolbar = new We(s), this.use(this.toolbar, this.$el), this._inputChangeHandler = (o) => {
const a = o.currentTarget;
this.handleImageFile(a.files).then((l) => {
l.forEach((E) => {
const u = /gif$/i.test(E.raw.type) && s.ignoreGif;
this.insert(`<img src="${u ? E.raw.data : E.data}">`);
});
}).catch((l) => {
this.emit("error", l);
});
}, this.on("toolbarButtonOnClick", (o) => {
switch (o) {
case "choose-picture":
if (typeof s.customPictureHandler == "function")
s.customPictureHandler();
else if (this.fileInput)
this.fileInput.click();
else {
const a = {
type: "file",
style: {
display: "none"
},
accept: s.chooseFileAccept
};
s.chooseFileMultiple && (a.multiple = !0), this.fileInput = f("input", a), this.$el.append(this.fileInput), this.fileInput.addEventListener("change", this._inputChangeHandler), this.fileInput.click();
}
break;
case "text-style":
this.stylePanel.show();
break;
}
});
}
/**
* @method handleImageFile(files)
* Image files handler.
* @param files `FileList | File[] | Blob[] | null` Image files.
* @returns `Promise<MediaFileHandlerData[]` [MediaFileHandlerData](https://github.com/capricorncd/image-process-tools#returns)
*/
handleImageFile(i) {
return i ? new Promise((s, n) => {
Promise.all(b(i).map(this._handleFile)).then((r) => {
s(r.sort((o, a) => o.index - a.index).map((o) => o.data));
}).catch(n);
}) : Promise.resolve([]);
}
_handleFile(i, s) {
return new Promise((n, r) => {
tt(i).then((o) => {
n({
data: o,
index: s
});
}).catch(r);
});
}
/**
* @method addToolbarButton(params, index)
* Add a custom button to `toolbar`.
* @param params `ButtonOptions` [ButtonOptions](#ButtonOptions)
* @param index? `number` New button insertion index.
* ```js
* // Add a button named 'custom-button-name' for toolbar.
* editor.addToolbarButton({
* name: 'custom-button-name',
* })
*
* // when the button is clicked
* editor.on('toolbarButtonOnClick', (name) => {
* if (name === 'custom-button-name') {
* // do something ...
* }
* })
* ```
*/
addToolbarButton(i, s) {
this.toolbar.addButton(i, s);
}
/**
* @method destroy()
* destroy events
*/
destroy() {
var i;
super.destroy(), this.stylePanel.destroy(), this.toolbar.destroy(), (i = this.fileInput) == null || i.removeEventListener("change", this._inputChangeHandler);
}
}
export {
se as ALLOWED_NODE_NAMES,
ot as SpEditor
};