@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
JavaScript
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;
}