UNPKG

@textbus/core

Version:

Textbus is a rich text editor and framework that is highly customizable and extensible to achieve rich wysiwyg effects.

1,686 lines (1,674 loc) 270 kB
import { Subject, distinctUntilChanged, share, map, take, microTask } from '@tanbo/stream'; export * from '@tanbo/stream'; import { InjectionToken, Scope, ReflectiveInjector, NullInjector, normalizeProvider, Injector, Injectable, Inject, Prop } from '@viewfly/core'; function makeError(name) { return function textbusError(message) { const error = new Error(message); error.name = `[TextbusError: ${name}]`; error.stack = error.stack.replace(/\n.*?(?=\n)/, ''); return error; }; } function replaceEmpty(s) { const empty = '\u00a0'; return s.replace(/\s\s+/g, str => { return ' ' + Array.from({ length: str.length - 1 }).fill(empty).join(''); }).replace(/^\s|\s$/g, empty); } function createBidirectionalMapping(isA) { const a2b = new WeakMap(); const b2a = new WeakMap(); function set(key, value) { if (get(key)) { remove(key); } if (get(value)) { remove(value); } if (isA(key)) { a2b.set(key, value); b2a.set(value, key); } else { a2b.set(value, key); b2a.set(key, value); } } function get(key) { if (isA(key)) { return a2b.get(key); } return b2a.get(key); } function remove(key) { if (isA(key)) { const v = a2b.get(key); a2b.delete(key); b2a.delete(v); } else { const v = b2a.get(key); b2a.delete(key); a2b.delete(v); } } return { set, get, remove }; } /** * 根节点及原生根元素节点引用类 */ class RootComponentRef { } /** * @internal Textbus 组件列表注入 token */ const COMPONENT_LIST = new InjectionToken('COMPONENT_LIST'); /** * @internal Textbus 格式列表注入 token */ const FORMATTER_LIST = new InjectionToken('FORMATTER_LIST'); /** * @internal Textbus 插槽属性注入列表 */ const ATTRIBUTE_LIST = new InjectionToken('ATTRIBUTE_LIST'); /** * 开启 Zen Coding 支持 */ const ZEN_CODING_DETECT = new InjectionToken('ZEN_CODING_DETECT'); /** * 最大历史记录栈大小 */ const HISTORY_STACK_SIZE = new InjectionToken('HISTORY_STACK_SIZE'); /** * 是否只读 */ const READONLY = new InjectionToken('READONLY'); class FocusManager { } let firstRun = true; /** * Textbus 内容管理类 * Content 属于 Slot 的私有属性,在实际场景中,开发者不需在关注此类,也不需要访问或操作此类 */ class Content { constructor() { Object.defineProperty(this, "data", { enumerable: true, configurable: true, writable: true, value: [] }); } static get segmenter() { if (Content._segmenter) { return Content._segmenter; } if (Intl === null || Intl === void 0 ? void 0 : Intl.Segmenter) { Content._segmenter = new Intl.Segmenter(); return Content._segmenter; } if (firstRun) { console.warn('[Textbus: warning]: cannot found `Intl.Segmenter`, slot index will revert back to default mode.'); firstRun = false; } return null; } /** * 内容的长度 */ get length() { return this.data.reduce((p, n) => p + n.length, 0); } /** * 修复 index,由于 emoji 长度不固定,当 index 在 emoji 中时,操作数据会产生意外的数据 * @param index 当前的 index * @param toEnd 当需要变更 index 时,是向后还是向前移动 */ correctIndex(index, toEnd) { if (index <= 0 || index >= this.length || !Content.segmenter) { return index; } let i = 0; for (const item of this.data) { const itemLength = item.length; if (typeof item === 'string') { if (index > i && index < i + itemLength) { const segments = Content.segmenter.segment(item); let offsetIndex = 0; for (const item of segments) { const length = item.segment.length; const nextOffset = offsetIndex + length; if (nextOffset === index) { return index; } if (nextOffset > index) { if (toEnd) { return nextOffset + i; } return offsetIndex + i; } offsetIndex = nextOffset; } return index; } } i += itemLength; if (i >= index) { break; } } return index; } /** * 在指定下标位置插入内容 * @param index * @param content */ insert(index, content) { if (index >= this.length) { this.append(content); } else { let i = 0; // 当前内容下标 let ii = 0; // 当前数组元素下标 for (const el of this.data) { if (index >= i) { if (typeof el === 'string') { if (index >= i && index < i + el.length) { const cc = [el.slice(0, index - i), content, el.slice(index - i)].filter(i => i); if (typeof content === 'string') { this.data.splice(ii, 1, cc.join('')); } else { this.data.splice(ii, 1, ...cc); } break; } } else if (index === i) { const prev = this.data[ii - 1]; if (typeof prev === 'string' && typeof content === 'string') { this.data[ii - 1] = prev + content; } else if (i === 0) { this.data.unshift(content); } else { this.data.splice(ii, 0, content); } break; } } ii++; i += el.length; } } } /** * 把内容添加到最后 * @param content */ append(content) { const lastChildIndex = this.data.length - 1; const lastChild = this.data[lastChildIndex]; if (typeof lastChild === 'string' && typeof content === 'string') { this.data[lastChildIndex] = lastChild + content; } else { this.data.push(content); } } cut(startIndex = 0, endIndex = this.length) { if (endIndex <= startIndex) { return []; } const discardedContents = this.slice(startIndex, endIndex); const elements = this.slice(0, startIndex).concat(this.slice(endIndex, this.length)); this.data = []; elements.forEach(item => this.append(item)); return discardedContents; } slice(startIndex = 0, endIndex = this.length) { if (startIndex >= endIndex) { return []; } startIndex = this.correctIndex(startIndex, false); endIndex = this.correctIndex(endIndex, true); let index = 0; const result = []; for (const el of this.data) { const fragmentStartIndex = index; const len = el.length; const fragmentEndIndex = index + len; index += len; if (startIndex < fragmentEndIndex && endIndex > fragmentStartIndex) { if (typeof el === 'string') { const min = Math.max(0, startIndex - fragmentStartIndex); const max = Math.min(fragmentEndIndex, endIndex) - fragmentStartIndex; result.push(el.slice(min, max)); } else { result.push(el); } } } return result; } toJSON() { return this.data.map(i => { if (typeof i === 'string') { return i; } return i.toJSON(); }); } indexOf(element) { let index = 0; for (const item of this.data) { if (item === element) { return index; } index += item.length; } return -1; } getContentAtIndex(index) { return this.slice(index, index + 1)[0]; } toGrid() { const splitPoints = [0]; let index = 0; this.data.forEach(i => { index += i.length; splitPoints.push(index); }); return [...splitPoints]; } toString() { return this.data.map(i => { if (typeof i === 'string') { return i; } return i.toString(); }).join(''); } } Object.defineProperty(Content, "_segmenter", { enumerable: true, configurable: true, writable: true, value: null }); function isVoid(data) { return data === null || typeof data === 'undefined'; } /** * Textbus 格式管理类 * Format 类为 Slot 的私有属性,在实际场景中,开发者不需在关注此类,也不需要访问或操作此类 */ class Format { constructor(slot) { Object.defineProperty(this, "slot", { enumerable: true, configurable: true, writable: true, value: slot }); Object.defineProperty(this, "map", { enumerable: true, configurable: true, writable: true, value: new Map() }); } /** * 将新样式合并到现有样式中 * @param formatter * @param value * @param background */ merge(formatter, value, background = false) { let ranges = this.map.get(formatter); if (!ranges) { const v = value.value; if (isVoid(v)) { return this; } ranges = [value]; this.map.set(formatter, ranges); return this; } const newRanges = this.normalizeFormatRange(background, ranges, value); if (newRanges.length) { this.map.set(formatter, newRanges); } else { this.map.delete(formatter); } return this; } /** * 将 index 后的样式起始和结束位置均增加 count 大小 * @param index * @param count */ stretch(index, count) { this.map.forEach(values => { values.forEach(range => { if (range.endIndex < index) { return; } range.endIndex += count; if (range.startIndex >= index) { range.startIndex += count; } }); }); return this; } /** * 将指定 index 位置后的样式向后平移 distance 长度 * @param index * @param distance */ split(index, distance) { Array.from(this.map).forEach(([key, formatRanges]) => { const newRanges = []; formatRanges.forEach(range => { if (range.endIndex <= index) { newRanges.push(Object.assign({}, range)); return; } if (range.startIndex >= index) { newRanges.push({ startIndex: range.startIndex + distance, endIndex: range.endIndex + distance, value: range.value }); return; } newRanges.push({ startIndex: range.startIndex, endIndex: index, value: range.value }, { startIndex: index + distance, endIndex: distance + range.endIndex, value: range.value }); }); // console.log([key, formatRanges, JSON.parse(JSON.stringify(newRanges)), index, distance]) this.map.set(key, newRanges); }); return this; } /** * 从指定 index 位置的样式删除 count * @param startIndex * @param count */ shrink(startIndex, count) { this.map.forEach(values => { values.forEach(range => { if (range.endIndex <= startIndex) { return; } range.endIndex = Math.max(startIndex, range.endIndex - count); if (range.startIndex > startIndex) { range.startIndex = Math.max(startIndex, range.startIndex - count); } }); }); Array.from(this.map.keys()).forEach(key => { const oldRanges = this.map.get(key); const newRanges = this.normalizeFormatRange(false, oldRanges); if (newRanges.length) { this.map.set(key, newRanges); } else { this.map.delete(key); } }); return this; } /** * 提取指定范围内的样式 * @param startIndex * @param endIndex * @param formatter */ extract(startIndex, endIndex, formatter) { const format = new Format(this.slot); this.map.forEach((ranges, key) => { if (formatter && !formatter.includes(key)) { return; } const extractRanges = this.extractFormatRangesByFormatter(startIndex, endIndex, key); if (extractRanges.length) { format.map.set(key, extractRanges); } }); return format; } /** * 生成一个重置位置的 format * @param slot * @param startIndex * @param endIndex */ createFormatByRange(slot, startIndex, endIndex) { const format = new Format(slot); this.map.forEach((ranges, key) => { const extractRanges = this.extractFormatRangesByFormatter(startIndex, endIndex, key); if (extractRanges.length) { format.map.set(key, extractRanges.map(i => { i.startIndex -= startIndex; i.endIndex -= startIndex; return i; })); } }); return format; } /** * 通过 formatter 提取指定范围内的样式数据 * @param startIndex * @param endIndex * @param formatter */ extractFormatRangesByFormatter(startIndex, endIndex, formatter) { const extractRanges = []; const ranges = this.map.get(formatter) || []; ranges.forEach(range => { if (range.startIndex > endIndex || range.endIndex < startIndex) { return; } const s = Math.max(range.startIndex, startIndex); const n = Math.min(range.endIndex, endIndex); if (s < n) { extractRanges.push({ startIndex: s, endIndex: n, value: range.value }); } }); return extractRanges; } /** * 丢弃指定范围内的样式 * @param formatter * @param startIndex * @param endIndex */ discard(formatter, startIndex, endIndex) { const oldRanges = this.map.get(formatter); if (oldRanges) { this.normalizeFormatRange(false, oldRanges, { startIndex, endIndex, value: null }); } return this; } extractFormatsByIndex(index) { const formats = []; if (index === 0) { this.map.forEach((ranges, formatter) => { ranges.forEach(i => { if (i.startIndex === 0) { formats.push([ formatter, i.value ]); } }); }); } else { this.map.forEach((ranges, formatter) => { ranges.forEach(i => { if (i.startIndex < index && i.endIndex >= index) { formats.push([ formatter, i.value ]); } }); }); } return formats; } toGrid() { const splitPoints = new Set(); splitPoints.add(0); splitPoints.add(this.slot.length); this.map.forEach(ranges => { ranges.forEach(item => { splitPoints.add(item.startIndex); splitPoints.add(item.endIndex); }); }); return [...splitPoints].sort((a, b) => a - b); } toJSON() { const json = {}; this.map.forEach((value, formatter) => { json[formatter.name] = value.map(i => (Object.assign({}, i))); }); return json; } toTree(startIndex, endIndex) { const copyFormat = this.extract(startIndex, endIndex); const tree = { startIndex, endIndex, }; let nextStartIndex = endIndex; let nextEndIndex = startIndex; const formats = []; const columnedFormats = []; Array.from(copyFormat.map.keys()).forEach(formatter => { const ranges = copyFormat.map.get(formatter); ranges.forEach(range => { if (range.startIndex === startIndex && range.endIndex === endIndex) { if (formatter.columned) { columnedFormats.push(Object.assign({ formatter }, range)); } else { formats.push(Object.assign({ formatter }, range)); copyFormat.map.delete(formatter); } } else if (range.startIndex < nextStartIndex) { nextStartIndex = range.startIndex; nextEndIndex = range.endIndex; } else if (range.startIndex === nextStartIndex) { nextEndIndex = Math.max(nextEndIndex, range.endIndex); } }); }); const hasChildren = copyFormat.map.size > columnedFormats.length; if (hasChildren) { tree.children = []; if (startIndex < nextStartIndex) { if (columnedFormats.length) { const childTree = copyFormat.extract(startIndex, nextStartIndex).toTree(startIndex, nextStartIndex); tree.children.push(childTree); } else { tree.children.push({ startIndex, endIndex: nextStartIndex }); } } const push = function (tree, childTree) { if (childTree.formats) { tree.children.push(childTree); } else if (childTree.children) { tree.children.push(...childTree.children); } else { tree.children.push(childTree); } }; const nextTree = copyFormat.toTree(nextStartIndex, nextEndIndex); push(tree, nextTree); if (nextEndIndex < endIndex) { const afterFormat = copyFormat.extract(nextEndIndex, endIndex); const afterTree = afterFormat.toTree(nextEndIndex, endIndex); push(tree, afterTree); } } else { formats.push(...columnedFormats); } if (formats.length) { tree.formats = formats.sort((a, b) => { return a.formatter.priority - b.formatter.priority; }); } return tree; } toArray() { const list = []; Array.from(this.map).forEach(i => { const formatter = i[0]; i[1].forEach(range => { list.push(Object.assign(Object.assign({}, range), { formatter })); }); }); return list; } normalizeFormatRange(background, oldRanges, newRange) { const length = this.slot.length; oldRanges = oldRanges.filter(range => { range.endIndex = Math.min(range.endIndex, length); return range.startIndex < range.endIndex; }); if (newRange) { if (background) { oldRanges.unshift(newRange); } else { oldRanges.push(newRange); } } if (oldRanges.length === 0) { return []; } let mergedRanges = [oldRanges.at(0)]; for (let i = 1; i < oldRanges.length; i++) { const range = oldRanges[i]; mergedRanges = Format.mergeRanges(mergedRanges, range); } return mergedRanges.filter(range => { return !isVoid(range.value); }); } static equal(left, right) { if (left === right) { return true; } if (left === null || right === null) { return false; } if (typeof left === 'object' && typeof right === 'object') { const leftKeys = Object.keys(left); const rightKeys = Object.keys(right); if (leftKeys.length === rightKeys.length) { return leftKeys.every(key => { return rightKeys.includes(key) && right[key] === left[key]; }); } } return false; } static mergeRanges(ranges, newRange) { const results = []; let isMerged = false; for (let i = 0; i < ranges.length; i++) { const range = ranges[i]; if (isMerged) { results.push(range); continue; } if (range.endIndex < newRange.startIndex) { results.push(range); continue; } if (range.startIndex > newRange.endIndex) { results.push(newRange); results.push(range); isMerged = true; continue; } const before = range; let last = null; // if (before.endIndex <= newRange.endIndex) { // i++ // } for (; i < ranges.length; i++) { const next = ranges[i]; if (next.startIndex <= newRange.endIndex) { last = next; } else { i--; break; } } if (!last) { results.push(newRange); isMerged = true; continue; } if (Format.equal(before.value, newRange.value)) { newRange.startIndex = Math.min(before.startIndex, newRange.startIndex); newRange.endIndex = Math.max(before.endIndex, newRange.endIndex); } if (before.startIndex < newRange.startIndex) { results.push({ startIndex: before.startIndex, endIndex: newRange.startIndex, value: before.value }); } if (Format.equal(last.value, newRange.value)) { results.push({ startIndex: Math.min(last.startIndex, newRange.startIndex), endIndex: Math.max(last.endIndex, newRange.endIndex), value: newRange.value }); isMerged = true; continue; } results.push(newRange); if (newRange.endIndex < last.endIndex) { results.push({ startIndex: newRange.endIndex, endIndex: last.endIndex, value: last.value }); } isMerged = true; } if (!isMerged) { results.push(newRange); } return results; } } class EventCache { constructor() { Object.defineProperty(this, "listeners", { enumerable: true, configurable: true, writable: true, value: new Map() }); } add(eventType, callback) { let callbacks = this.listeners.get(eventType); if (!callbacks) { callbacks = []; this.listeners.set(eventType, callbacks); } callbacks.push(callback); } get(eventType) { return this.listeners.get(eventType) || []; } clean(eventType) { this.listeners.delete(eventType); } } const eventCacheMap = new WeakMap(); /** * Textbus 事件对象 */ class Event { get isPrevented() { return this._isPrevented; } constructor(target, data) { Object.defineProperty(this, "target", { enumerable: true, configurable: true, writable: true, value: target }); Object.defineProperty(this, "data", { enumerable: true, configurable: true, writable: true, value: data }); Object.defineProperty(this, "_isPrevented", { enumerable: true, configurable: true, writable: true, value: false }); } preventDefault() { this._isPrevented = true; } } class ContextMenuEvent extends Event { constructor(target, getMenus) { super(target, null); Object.defineProperty(this, "getMenus", { enumerable: true, configurable: true, writable: true, value: getMenus }); Object.defineProperty(this, "isStopped", { enumerable: true, configurable: true, writable: true, value: false }); } get stopped() { return this.isStopped; } stopPropagation() { this.isStopped = true; } useMenus(menus) { this.getMenus(menus); } } class GetRangesEvent extends Event { constructor(target, getRanges) { super(target, null); Object.defineProperty(this, "getRanges", { enumerable: true, configurable: true, writable: true, value: getRanges }); } useRanges(ranges) { this.getRanges(ranges); } } function invokeListener(target, eventType, event) { if (typeof target !== 'object' || target === null) { return; } const cache = eventCacheMap.get(target); if (cache) { const callbacks = cache.get(eventType); callbacks.forEach(fn => { return fn(event); }); if (eventType === 'onDetach') { eventCacheMap.delete(target); } } } function makeEventHook(type) { return function (listener) { const context = getCurrentContext(); if (context) { context.eventCache.add(type, listener); } }; } /** * 根据组件触发上下文菜单 * @param component */ function triggerContextMenu(component) { var _a; let comp = component; const menuItems = []; while (comp) { const event = new ContextMenuEvent(comp, (menus) => { menuItems.push(menus); }); invokeListener(comp, 'onContextMenu', event); if (event.stopped) { break; } comp = ((_a = comp.parent) === null || _a === void 0 ? void 0 : _a.parent) || null; } return menuItems; } /** * 当已选中组件未选中或选区不只选中已选中组件时触发 */ const onUnselect = makeEventHook('onUnselect'); /** * 当选区刚好选中一个组件 */ const onSelected = makeEventHook('onSelected'); /** * 当光标从前面进入组件 */ const onSelectionFromFront = makeEventHook('onSelectionFromFront'); /** * 当光标从后面进入组件 */ const onSelectionFromEnd = makeEventHook('onSelectionFromEnd'); /** * 组件获取焦点事件的勾子 */ const onFocus = makeEventHook('onFocus'); /** * 组件失去焦点事件的勾子 */ const onBlur = makeEventHook('onBlur'); /** * 组件或子组件获取焦点事件的勾子 */ const onFocusIn = makeEventHook('onFocusIn'); /** * 组件或子组件失去焦点事件的勾子 */ const onFocusOut = makeEventHook('onFocusOut'); /** * 组件内粘贴事件勾子 */ const onPaste = makeEventHook('onPaste'); /** * 组件右键菜单事件勾子 */ const onContextMenu = makeEventHook('onContextMenu'); /** * 组件子插槽内容删除时的勾子 */ const onContentDelete = makeEventHook('onContentDelete'); /** * 组件子插槽内容删除完成时的勾子 */ const onContentDeleted = makeEventHook('onContentDeleted'); /** * 组件子插槽换行时的勾子 */ const onBreak = makeEventHook('onBreak'); /** * 组件子插槽插入内容时的勾子 */ const onContentInsert = makeEventHook('onContentInsert'); /** * 组件子插槽插入内容后时的勾子 */ const onContentInserted = makeEventHook('onContentInserted'); /** * 当组件为选区公共父组件时的勾子 */ const onGetRanges = makeEventHook('onGetRanges'); /** * 当插槽组合输入前触发 */ const onCompositionStart = makeEventHook('onCompositionStart'); /** * 当插槽组合输入时触发 */ const onCompositionUpdate = makeEventHook('onCompositionUpdate'); /** * 当插槽组合输入结束触发 */ const onCompositionEnd = makeEventHook('onCompositionEnd'); /** * 当组件的父插槽数据发生更新后触发 */ const onParentSlotUpdated = makeEventHook('onParentSlotUpdated'); /** * 当组件插槽设置属性时触发 */ const onSlotSetAttribute = makeEventHook('onSlotSetAttribute'); /** * 当组件插槽应用格式时触发 */ const onSlotApplyFormat = makeEventHook('onSlotApplyFormat'); /** * 当组件从数据模型上剥离时触发 */ const onDetach = makeEventHook('onDetach'); const componentErrorFn = makeError('Component'); const contextStack = []; function getCurrentContext() { const current = contextStack[contextStack.length - 1]; if (!current) { throw componentErrorFn('cannot be called outside the component!'); } return current; } function setup(textbus, component) { if (component.textbus) { return; } const context = { textbus, componentInstance: component, eventCache: new EventCache(), }; contextStack.push(context); component.textbus = textbus; onDetach(() => { eventCacheMap.delete(component); }); if (typeof component.setup === 'function') { component.setup(); } component.slots.forEach(slot => { slot.sliceContent().forEach(i => { if (i instanceof Component) { setup(textbus, i); } }); }); eventCacheMap.set(component, context.eventCache); contextStack.pop(); } function isModel(value) { return value instanceof Slot || value instanceof Component || proxyToRawCache.has(value); } function getObserver(v) { return rawToProxyCache.get(v) || null; } function getChangeMarker(target) { if (isModel(target)) { return target.__changeMarker__; } if (target instanceof ChangeMarker) { return target; } const observer = getObserver(target); if (observer) { return observer.__changeMarker__; } return null; } const attachErrorFn = makeError('attachError'); function attachModel(parentModel, subModel) { var _a; if (!isModel(subModel)) { return; } const subCM = subModel.__changeMarker__; if (subCM.parentModel === parentModel) { return; } if (subCM.parentModel !== null) { throw attachErrorFn('A data model cannot appear on two nodes.'); } subCM.parentModel = parentModel; if (subModel instanceof Slot) { const textbus = (_a = subModel.parent) === null || _a === void 0 ? void 0 : _a.textbus; if (textbus) { subModel.sliceContent().forEach(i => { if (i instanceof Component) { setup(textbus, i); } }); } } } function detachModel(...models) { models.forEach(item => { const changeMarker = getChangeMarker(item); if (changeMarker) { changeMarker.parentModel = null; changeMarker.detach(); } }); } let onewayUpdate = false; /** * 用来标识数据模型的数据变化 */ class ChangeMarker { get irrevocableUpdate() { return this._irrevocableUpdate; } get dirty() { return this._dirty; } get changed() { return this._changed; } constructor(host) { Object.defineProperty(this, "host", { enumerable: true, configurable: true, writable: true, value: host }); Object.defineProperty(this, "onForceChange", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "onChange", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "onSelfChange", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "parentModel", { enumerable: true, configurable: true, writable: true, value: null }); Object.defineProperty(this, "detachCallbacks", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "_irrevocableUpdate", { enumerable: true, configurable: true, writable: true, value: false }); Object.defineProperty(this, "_dirty", { enumerable: true, configurable: true, writable: true, value: true }); Object.defineProperty(this, "_changed", { enumerable: true, configurable: true, writable: true, value: true }); Object.defineProperty(this, "changeEvent", { enumerable: true, configurable: true, writable: true, value: new Subject() }); Object.defineProperty(this, "selfChangeEvent", { enumerable: true, configurable: true, writable: true, value: new Subject() }); Object.defineProperty(this, "childComponentRemovedEvent", { enumerable: true, configurable: true, writable: true, value: new Subject() }); Object.defineProperty(this, "forceChangeEvent", { enumerable: true, configurable: true, writable: true, value: new Subject() }); this.onChange = this.changeEvent.asObservable(); this.onSelfChange = this.selfChangeEvent.asObservable(); this.onForceChange = this.forceChangeEvent.asObservable(); } addDetachCallback(callback) { this.detachCallbacks.push(callback); } getPaths() { const path = this.getPathInParent(); if (path !== null) { const parentPaths = this.parentModel.__changeMarker__.getPaths(); return [...parentPaths, path]; } return []; } forceMarkDirtied(source) { if (this._dirty) { return; } this._dirty = true; this.forceMarkChanged(source); } forceMarkChanged(source) { if (this._changed) { return; } this._changed = true; this.forceChangeEvent.next(); if (this.parentModel) { if (!source) { source = this.host instanceof Component ? this.host : source; } if (source) { this.parentModel.__changeMarker__.forceMarkChanged(source); } else { this.parentModel.__changeMarker__.forceMarkDirtied(source); } } } markAsDirtied(operation) { this._dirty = true; operation.irrevocable = onewayUpdate; this._irrevocableUpdate = onewayUpdate; if (operation.paths.length === 0) { this.selfChangeEvent.next([...operation.apply]); } this.markAsChanged(operation); this._irrevocableUpdate = false; } markAsChanged(operation) { this._changed = true; this.changeEvent.next(operation); if (this.parentModel) { const path = this.getPathInParent(); if (path !== null) { operation.paths.unshift(path); if (operation.source) { this.parentModel.__changeMarker__.markAsChanged(operation); } else if (this.host instanceof Component) { operation.source = this.host; this.parentModel.__changeMarker__.markAsChanged(operation); } else { this.parentModel.__changeMarker__.markAsDirtied(operation); } } } } rendered() { this._dirty = this._changed = false; } reset() { this._changed = this._dirty = true; } detach() { this.detachCallbacks.forEach(i => i()); if (this.host instanceof Slot) { this.host.sliceContent().forEach(i => { if (i instanceof Component) { i.changeMarker.detach(); } }); } else if (Array.isArray(this.host)) { this.host.forEach(i => { const proxy = getObserver(i); if (proxy) { proxy.__changeMarker__.detach(); } }); } else if (isType(this.host, 'Object')) { const state = this.host instanceof Component ? this.host.state : this.host; const values = Object.values(state); for (const value of values) { if (value instanceof Slot) { value.__changeMarker__.detach(); } else { const proxy = getObserver(toRaw(value)); if (proxy) { proxy.__changeMarker__.detach(); } } } if (this.host instanceof Component) { invokeListener(this.host, 'onDetach'); } } this.detachCallbacks = []; } getPathInParent() { const parentModel = this.parentModel; if (!parentModel) { return null; } if (parentModel instanceof Slot) { return parentModel.indexOf(this.host); } if (Array.isArray(parentModel)) { return parentModel.__changeMarker__.host.indexOf(this.host); } if (isType(parentModel, 'Object')) { const host = parentModel.__changeMarker__.host; const raw = host instanceof Component ? host.state : host; const entries = Object.entries(raw); for (const [key, value] of entries) { if (toRaw(value) === this.host) { return key; } } } return null; } } /** * 在回调函数内改变组件状态时,将更改的状态标记为不可撤回的 * @param fn */ function irrevocableUpdate(fn) { onewayUpdate = true; fn(); onewayUpdate = false; } /** * Textbus 虚拟 DOM 文本节点 */ class VTextNode { constructor(textContent = '') { Object.defineProperty(this, "textContent", { enumerable: true, configurable: true, writable: true, value: textContent }); Object.defineProperty(this, "location", { enumerable: true, configurable: true, writable: true, value: null }); } } function append(children, node) { if (node instanceof VElement || node instanceof Component) { children.push(node); } else if (node instanceof VTextNode) { if (node.textContent) { children.push(node); } } else if (typeof node === 'string' && node.length > 0) { children.push(new VTextNode(node)); } else if (Array.isArray(node)) { for (const item of node.flat()) { append(children, item); } } else if (node !== false && node !== true && node !== null && typeof node !== 'undefined') { children.push(new VTextNode(String(node))); } } function createVNode(tagName, attrs, children) { return new VElement(tagName, attrs, children); } /** * Textbus 虚拟 DOM 元素节点 */ class VElement { constructor(tagName, attrs, children) { Object.defineProperty(this, "tagName", { enumerable: true, configurable: true, writable: true, value: tagName }); Object.defineProperty(this, "children", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "location", { enumerable: true, configurable: true, writable: true, value: null }); Object.defineProperty(this, "attrs", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "styles", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "classes", { enumerable: true, configurable: true, writable: true, value: new Set() }); Object.defineProperty(this, "listeners", { enumerable: true, configurable: true, writable: true, value: {} }); if (attrs) { Object.keys(attrs).forEach(key => { if (key === 'class') { const className = (attrs.class || '').trim(); this.classes = new Set(className ? className.split(/\s+/g) : []); } else if (key === 'style') { const style = attrs.style || ''; if (typeof style === 'string') { style.split(';').map(s => s.split(':')).forEach(v => { if (!v[0] || !v[1]) { return; } this.styles.set(v[0].trim(), v[1].trim()); }); } else if (typeof style === 'object') { Object.keys(style).forEach(key => { this.styles.set(key, style[key]); }); } // } else if (/^on[A-Z]/.test(key)) { // const listener = attrs![key] // if (typeof listener === 'function') { // this.listeners[key.replace(/^on/, '').toLowerCase()] = listener // } } else { this.attrs.set(key, attrs[key]); } }); } if (children) { children.flat(2).forEach(i => { append(this.children, i); }); } } /** * 在最后位置添加一个子节点。 * @param newNodes */ appendChild(...newNodes) { this.children.push(...newNodes); } } const slotError = makeError('Slot'); var ContentType; (function (ContentType) { ContentType[ContentType["Text"] = 1] = "Text"; ContentType[ContentType["InlineComponent"] = 2] = "InlineComponent"; ContentType[ContentType["BlockComponent"] = 3] = "BlockComponent"; })(ContentType || (ContentType = {})); class DeltaLite extends Array { constructor() { super(...arguments); Object.defineProperty(this, "attributes", { enumerable: true, configurable: true, writable: true, value: new Map() }); } } /** * Textbus 插槽类,用于管理组件、文本及格式的增删改查 */ class Slot { static get emptyPlaceholder() { // return this.schema.includes(ContentType.BlockComponent) ? '\n' : '\u200b' return '\n'; } /** 插槽所属的组件 */ get parent() { let parentModel = this.__changeMarker__.parentModel; while (parentModel) { if (parentModel.__changeMarker__.host instanceof Component) { return parentModel.__changeMarker__.host; } parentModel = parentModel.__changeMarker__.parentModel; } return null; } get parentSlot() { var _a; return ((_a = this.parent) === null || _a === void 0 ? void 0 : _a.changeMarker.parentModel) || null; } /** 插槽内容长度 */ get length() { return this.content.length; } /** 插槽内容是否为空 */ get isEmpty() { return this.length === 1 && this.getContentAtIndex(0) === Slot.emptyPlaceholder; } /** 插槽当前下标位置 */ get index() { return this.isEmpty ? 0 : this._index; } constructor(schema) { /** 插槽变更标记器 */ Object.defineProperty(this, "__changeMarker__", { enumerable: true, configurable: true, writable: true, value: new ChangeMarker(this) }); Object.defineProperty(this, "changeMarker", { enumerable: true, configurable: true, writable: true, value: this.__changeMarker__ }); Object.defineProperty(this, "onContentChange", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "schema", { enumerable: true, configurable: true, writable: true, value: void 0 }); /** * @internal * 插槽的 id,用于优化 diff 算法 */ Object.defineProperty(this, "id", { enumerable: true, configurable: true, writable: true, value: Math.random() }); Object.defineProperty(this, "_index", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "content", { enumerable: true, configurable: true, writable: true, value: new Content() }); Object.defineProperty(this, "format", { enumerable: true, configurable: true, writable: true, value: new Format(this) }); Object.defineProperty(this, "attributes", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "contentChangeEvent", { enumerable: true, configurable: true, writable: true, value: new Subject() }); Object.defineProperty(this, "applyFormatCoverChild", { enumerable: true, configurable: true, writable: true, value: false }); this.schema = schema.sort(); this.onContentChange = this.contentChangeEvent.asObservable(); this.content.append(Slot.emptyPlaceholder); this._index = 0; } /** * 设置属性 * @param attribute * @param value * @param canSet */ setAttribute(attribute, value, canSet) { if (typeof canSet === 'function' && !canSet(this, attribute, value)) { return; } if (!attribute.checkHost(this, value)) { return; } const has = this.attributes.has(attribute); const v = this.attributes.get(attribute); this.attributes.set(attribute, value); const applyActions = [{ type: 'attrSet', name: attribute.name, value }]; if (!attribute.onlySelf) { this.sliceContent().forEach(item => { if (typeof item !== 'string') { item.slots.forEach(slot => { slot.setAttribute(attribute, value); }); } }); } this.__changeMarker__.markAsDirtied({ paths: [], apply: applyActions, unApply: [has ? { type: 'attrSet', name: attribute.name, value: v } : { type: 'attrDelete', name: attribute.name }] }); this.contentChangeEvent.next(applyActions); } /** * 获取属性 * @param attribute */ getAttribute(attribute) { var _a; return (_a = this.attributes.get(attribute)) !== null && _a !== void 0 ? _a : null; } /** * 获取所有属性 */ getAttributes() { return Array.from(this.attributes.entries()); } /** * 删除属性 * @param attribute * @param canRemove */ removeAttribute(attribute, canRemove) { if (typeof canRemove === 'function' && !canRemove(this, attribute)) { return; } this.sliceContent().forEach(item => { if (typeof item !== 'string') { item.slots.forEach(slot => { slot.removeAttribute(attribute); }); } }); const has = this.attributes.has(attribute); if (!has) { return; }