@textbus/editor
Version:
Textbus is a rich text editor and framework that is highly customizable and extensible to achieve rich wysiwyg effects.
1,382 lines (1,359 loc) • 1.08 MB
JavaScript
import { useContext, useSelf, Selection, Renderer, onViewInit, onDestroy, defineComponent, ContentType, useSlots, Slot, VElement, RenderMode, Commander, onBreak, onGetRanges, useState, onContextMenu, Controller, useRef, onFocus, onBlur, useDynamicShortcut, onPaste, onSlotRemove, RootComponentRef, triggerContextMenu, Keyboard, QueryStateType, Query, Registry, History, makeError, onContentInsert, onCompositionStart } from '@textbus/core';
import { createElement, VIEW_CONTAINER, createTextNode, VIEW_DOCUMENT, CollaborateSelectionAwarenessDelegate, Parser, SelectionBridge, isMac, EDITOR_OPTIONS, Viewer, Input } from '@textbus/platform-browser';
import { fromEvent, merge, debounceTime, Subject, Subscription, delay, Observable, take, auditTime, fromPromise, of } from '@tanbo/stream';
import { CubicBezier } from '@tanbo/bezier';
import { Injectable } from '@tanbo/di';
import { languages, tokenize, Token } from 'prismjs';
import classNames from 'classnames';
import { createPicker } from '@tanbo/color-picker';
import { parseCss, rgb2Hex, hsl2Hex } from '@tanbo/color';
const text = document.createElement('div');
const handlers = [];
for (let i = 0; i < 8; i++) {
const button = document.createElement('button');
button.type = 'button';
handlers.push(button);
}
const mask = createElement('div', {
classes: ['textbus-image-video-resize'],
children: [
...handlers,
text
]
});
let currentRef = null;
function useDragResize(ref, callback) {
const context = useContext();
const componentInstance = useSelf();
const selection = context.get(Selection);
const docContainer = context.get(VIEW_CONTAINER);
const renderer = context.get(Renderer);
const self = useSelf();
let isFocus = false;
const subs = [];
subs.push(renderer.onViewUpdated.subscribe(() => {
if (isFocus && currentRef) {
updateStyle(currentRef.current, docContainer.getBoundingClientRect());
}
}), selection.onChange.subscribe(() => {
var _a, _b;
const index = (_a = self.parent) === null || _a === void 0 ? void 0 : _a.indexOf(self);
if (selection.startSlot !== self.parent ||
selection.endSlot !== self.parent ||
selection.startOffset !== index ||
selection.endOffset !== index + 1) {
isFocus = false;
(_b = mask.parentNode) === null || _b === void 0 ? void 0 : _b.removeChild(mask);
}
}), fromEvent(mask, 'mousedown').subscribe(ev => {
if (currentRef !== ref || !(currentRef === null || currentRef === void 0 ? void 0 : currentRef.current)) {
return;
}
docContainer.style.pointerEvents = 'none';
const startRect = ref.current.getBoundingClientRect();
const startX = ev.clientX;
const startY = ev.clientY;
const startWidth = startRect.width;
const startHeight = startRect.height;
const startHypotenuse = Math.sqrt(startWidth * startWidth + startHeight * startHeight);
let endWidth = startWidth;
let endHeight = startHeight;
const index = handlers.indexOf(ev.target);
const unMove = fromEvent(document, 'mousemove').subscribe(ev => {
const moveX = ev.clientX;
const moveY = ev.clientY;
const offsetX = moveX - startX;
const offsetY = moveY - startY;
let gainHypotenuse;
let proportion;
let sideX;
let sideY;
switch (index) {
case 0:
case 4:
sideX = startWidth + offsetX;
sideY = startHeight + offsetY;
gainHypotenuse = Math.sqrt(sideX * sideX + sideY * sideY);
proportion = gainHypotenuse / startHypotenuse;
if (index === 0) {
proportion = 1 - (proportion - 1);
}
endWidth = startWidth * proportion;
endHeight = startHeight * proportion;
break;
case 2:
sideX = startWidth + offsetX;
sideY = startHeight - offsetY;
gainHypotenuse = Math.sqrt(sideX * sideX + sideY * sideY);
proportion = gainHypotenuse / startHypotenuse;
endWidth = startWidth * proportion;
endHeight = startHeight * proportion;
break;
case 6:
sideX = startWidth - offsetX;
sideY = startHeight + offsetY;
gainHypotenuse = Math.sqrt(sideX * sideX + sideY * sideY);
gainHypotenuse = Math.sqrt(sideX * sideX + sideY * sideY);
proportion = gainHypotenuse / startHypotenuse;
endWidth = startWidth * proportion;
endHeight = startHeight * proportion;
break;
case 1:
endHeight = startHeight - offsetY;
break;
case 5:
endHeight = startHeight + offsetY;
break;
case 3:
endWidth = startWidth + offsetX;
break;
case 7:
endWidth = startWidth - offsetX;
break;
}
currentRef.current.style.width = endWidth + 'px';
currentRef.current.style.height = endHeight + 'px';
updateStyle(currentRef.current, docContainer.getBoundingClientRect());
});
const unUp = fromEvent(document, 'mouseup').subscribe(() => {
callback({
width: endWidth + 'px',
height: endHeight + 'px'
});
docContainer.style.pointerEvents = '';
unMove.unsubscribe();
unUp.unsubscribe();
});
}));
onViewInit(() => {
subs.push(fromEvent(ref.current, 'click').subscribe((ev) => {
currentRef = ref;
isFocus = true;
selection.selectComponent(componentInstance, true);
updateStyle(ref.current, docContainer.getBoundingClientRect());
docContainer.appendChild(mask);
ev.stopPropagation();
}));
});
onDestroy(() => {
var _a;
isFocus = false;
(_a = mask.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(mask);
subs.forEach(i => i.unsubscribe());
});
}
function updateStyle(nativeElement, offsetRect) {
const rect = nativeElement.getBoundingClientRect();
// eslint-disable-next-line max-len
mask.style.cssText = `left: ${rect.left - offsetRect.left}px; top: ${rect.top - offsetRect.top}px; width: ${rect.width}px; height: ${rect.height}px;`;
text.innerText = `${Math.round(rect.width)}px * ${Math.round(rect.height)}px`;
}
const paragraphComponent = defineComponent({
type: ContentType.BlockComponent,
name: 'ParagraphComponent',
setup(data) {
const injector = useContext();
const slots = useSlots((data === null || data === void 0 ? void 0 : data.slots) || [new Slot([
ContentType.Text,
ContentType.InlineComponent
])]);
if (!slots.length) {
slots.push(new Slot([
ContentType.Text,
ContentType.InlineComponent
]));
}
useEnterBreaking(injector, slots);
return {
render(slotRender) {
return slotRender(slots.get(0), (children) => {
return (VElement.createElement("p", null, children));
});
}
};
}
});
const paragraphComponentLoader = {
match(element) {
return element.tagName === 'P';
},
read(element, injector, slotParser) {
const slot = slotParser(new Slot([
ContentType.Text,
ContentType.InlineComponent
]), element);
return paragraphComponent.createInstance(injector, {
slots: [slot]
});
}
};
class Matcher {
constructor(target, rule) {
this.target = target;
this.rule = rule;
this.validators = [];
this.excludeValidators = [];
if (rule.tags) {
this.validators.push(this.makeTagsMatcher(rule.tags));
}
if (rule.styles) {
this.validators.push(this.makeStyleMatcher(rule.styles));
}
if (rule.attrs) {
this.validators.push(this.makeAttrsMatcher(rule.attrs));
}
if (rule.excludeStyles) {
this.excludeValidators.push(this.makeStyleMatcher(rule.excludeStyles));
}
if (rule.excludeAttrs) {
this.excludeValidators.push(this.makeAttrsMatcher(rule.excludeAttrs));
}
}
match(element) {
if (this.rule.filter) {
const b = this.rule.filter(element);
if (!b) {
return false;
}
}
const exclude = this.excludeValidators.map(fn => fn(element)).includes(true);
if (exclude) {
return false;
}
return this.validators.map(fn => fn(element)).includes(true);
}
extractFormatData(node, config) {
const attrs = {};
if (config.attrs) {
config.attrs.forEach(key => {
attrs[key] = node.getAttribute(key);
});
}
const style = {};
if (config.styleName) {
(Array.isArray(config.styleName) ? config.styleName : [config.styleName]).forEach(name => {
const v = node.style[name];
if (v) {
style[name] = v;
}
});
}
return {
tag: config.tag ? node.nodeName.toLowerCase() : null,
attrs: Object.keys(attrs).length ? attrs : null,
styles: style
};
}
makeTagsMatcher(tags) {
return (node) => {
const tagName = node.nodeName.toLowerCase();
return Array.isArray(tags) ? tags.includes(tagName) : tags.test(tagName);
};
}
makeAttrsMatcher(attrs) {
return (node) => {
return attrs.map(attr => {
if (attr.value) {
return node.getAttribute(attr.key) === attr.value;
}
return node.hasAttribute(attr.key);
}).includes(true);
};
}
makeStyleMatcher(styles) {
return (node) => {
return !Object.keys(styles).map(key => {
const optionValue = (Array.isArray(styles[key]) ?
styles[key] :
[styles[key]]);
let styleValue = node.style[key];
if (key === 'fontFamily' && typeof styleValue === 'string') {
styleValue = styleValue.replace(/['"]/g, '');
}
if (styleValue) {
return optionValue.map(v => {
if (v instanceof RegExp) {
return v.test(styleValue);
}
return v === styleValue;
}).includes(true);
}
return false;
}).includes(false);
};
}
}
class LinkFormatLoader extends Matcher {
constructor(formatter) {
super(formatter, {
tags: ['a']
});
}
read(element) {
return {
formatter: this.target,
value: this.extractFormatData(element, {
attrs: ['target', 'href', 'data-href']
}).attrs
};
}
}
class LinkFormatter {
constructor() {
this.priority = -1;
this.name = 'link';
this.columned = false;
}
render(children, formatValue, renderMode) {
if (renderMode !== RenderMode.Editing) {
return new VElement('a', {
target: formatValue.target,
href: formatValue.href || formatValue['data-href']
}, children);
}
return new VElement('a', {
target: formatValue.target,
'data-href': formatValue.href || formatValue['data-href']
}, children);
}
}
const linkFormatter = new LinkFormatter();
const linkFormatLoader = new LinkFormatLoader(linkFormatter);
/**
* 本换行方法只为段落、标题和块组件设计,主要作用是在实现换行功能同时,可以跳出两层组件。
* 如,在引用块里的段落,当尾部的两个段落都为空,且再次触发换行时,则把新的段落插入在引用块后
* @param injector
* @param slots
*/
function useEnterBreaking(injector, slots) {
const selection = injector.get(Selection);
const commander = injector.get(Commander);
const self = useSelf();
onBreak(ev => {
var _a;
const parentSlot = self.parent;
const index = parentSlot.indexOf(self);
parentSlot.retain(index + 1);
const currentSlot = slots.get(0);
const delta = currentSlot.cut(ev.data.index).toDelta();
const nextSlot = new Slot([
ContentType.Text,
ContentType.InlineComponent
]);
let i = 0;
while (i < delta.length) {
const item = delta[i];
if (nextSlot.insert(item.insert, item.formats)) {
i++;
continue;
}
break;
}
if (nextSlot.isEmpty) {
nextSlot.applyFormat(linkFormatter, {
startIndex: 0,
endIndex: 1,
value: null
});
}
const component = paragraphComponent.createInstance(injector, {
slots: [nextSlot]
});
const beforeComponent = parentSlot.getContentAtIndex(index - 1);
if (index === parentSlot.length - 1 &&
beforeComponent &&
typeof beforeComponent !== 'string' &&
['BlockComponent', 'ParagraphComponent', 'HeadingComponent'].includes(beforeComponent.name) &&
((_a = beforeComponent.slots.get(0)) === null || _a === void 0 ? void 0 : _a.isEmpty) &&
currentSlot.isEmpty &&
nextSlot.isEmpty) {
// 当当前插槽为空,且新换行的插槽和前一个组件的插槽都为空,则删除当前组件和前一个组件,同时跳出上层组件,并且把新的段落插入在上层组件之后。
const host = parentSlot.parentSlot;
if (host) {
const index2 = host.indexOf(self.parentComponent);
parentSlot.retain(parentSlot.index - 2);
parentSlot.delete(2);
host.retain(index2 + 1);
host.insert(component);
}
}
if (!component.parent) {
parentSlot.insert(component);
}
selection.selectLastPosition(component);
while (i < delta.length) {
const item = delta[i];
i++;
commander.insert(item.insert, item.formats);
}
selection.setPosition(component.slots.get(0), 0);
ev.preventDefault();
});
}
function createCell(colspan = 1, rowspan = 1) {
return new Slot([
ContentType.InlineComponent,
ContentType.BlockComponent,
ContentType.Text
], {
rowspan,
colspan
});
}
function findCellPosition(cell, cellMatrix) {
let minRow, maxRow, minColumn, maxColumn;
forA: for (let rowIndex = 0; rowIndex < cellMatrix.length; rowIndex++) {
const cells = cellMatrix[rowIndex].cellsPosition;
for (let colIndex = 0; colIndex < cells.length; colIndex++) {
if (cells[colIndex].cell === cell) {
minRow = rowIndex;
minColumn = colIndex;
break forA;
}
}
}
forB: for (let rowIndex = cellMatrix.length - 1; rowIndex > -1; rowIndex--) {
const cells = cellMatrix[rowIndex].cellsPosition;
for (let colIndex = cells.length - 1; colIndex > -1; colIndex--) {
if (cells[colIndex].cell === cell) {
maxRow = rowIndex;
maxColumn = colIndex;
break forB;
}
}
}
return {
minRow,
maxRow,
minColumn,
maxColumn
};
}
function selectCellsByRange(minRow, minColumn, maxRow, maxColumn, cellMatrix) {
const x1 = -Math.max(...cellMatrix.slice(minRow, maxRow + 1).map(row => row.cellsPosition[minColumn].offsetColumn));
const x2 = Math.max(...cellMatrix.slice(minRow, maxRow + 1).map(row => {
return row.cellsPosition[maxColumn].cell.state.colspan - (row.cellsPosition[maxColumn].offsetColumn + 1);
}));
const y1 = -Math.max(...cellMatrix[minRow].cellsPosition.slice(minColumn, maxColumn + 1).map(cell => cell.offsetRow));
const y2 = Math.max(...cellMatrix[maxRow].cellsPosition.slice(minColumn, maxColumn + 1).map(cell => {
return cell.cell.state.rowspan - (cell.offsetRow + 1);
}));
if (x1 || y1 || x2 || y2) {
return selectCellsByRange(minRow + y1, minColumn + x1, maxRow + y2, maxColumn + x2, cellMatrix);
}
const startCellPosition = cellMatrix[minRow].cellsPosition[minColumn];
const endCellPosition = cellMatrix[maxRow].cellsPosition[maxColumn];
const selectedCells = cellMatrix.slice(startCellPosition.rowIndex, endCellPosition.rowIndex + 1).map(row => {
return row.cellsPosition.slice(startCellPosition.columnIndex, endCellPosition.columnIndex + 1);
}).reduce((a, b) => {
return a.concat(b);
}).map(item => item.cell);
return {
selectedCells: Array.from(new Set(selectedCells)),
startPosition: startCellPosition,
endPosition: endCellPosition
};
}
function autoComplete(table) {
const newTable = [];
table.forEach((tr, rowIndex) => {
if (!newTable[rowIndex]) {
newTable[rowIndex] = [];
}
const row = newTable[rowIndex];
let startColumnIndex = 0;
tr.forEach(td => {
while (row[startColumnIndex]) {
startColumnIndex++;
}
let maxColspan = 1;
while (maxColspan < td.state.colspan) {
if (!row[startColumnIndex + maxColspan]) {
maxColspan++;
}
else {
break;
}
}
td.updateState(draft => {
draft.rowspan = td.state.rowspan;
draft.colspan = maxColspan;
});
for (let i = rowIndex, len = td.state.rowspan + rowIndex; i < len; i++) {
if (!newTable[i]) {
newTable[i] = [];
}
const row = newTable[i];
for (let j = startColumnIndex, max = startColumnIndex + maxColspan; j < max; j++) {
row[j] = td;
}
}
startColumnIndex += maxColspan;
});
});
const maxColumns = Math.max(...newTable.map(i => i.length));
newTable.forEach(tr => {
for (let i = 0; i < maxColumns; i++) {
if (!tr[i]) {
tr[i] = createCell();
}
}
});
const recordCells = [];
return newTable.map(tr => {
return tr.filter(td => {
const is = recordCells.includes(td);
if (is) {
return false;
}
recordCells.push(td);
return true;
});
});
}
function slotsToTable(slots, columnSize) {
const table = [];
let rowIndex = 0;
let columnIndex = 0;
for (let index = 0; index < slots.length; index++) {
const slot = slots[index];
const state = slot.state;
const row = table[rowIndex];
if (row) {
let isFull = true;
for (let i = 0; i < columnSize; i++) {
if (!row[i]) {
columnIndex = i;
isFull = false;
break;
}
}
if (isFull) {
columnIndex = 0;
rowIndex++;
index--;
continue;
}
}
for (let j = rowIndex; j < state.rowspan + rowIndex; j++) {
if (!table[j]) {
table[j] = [];
}
const row = table[j];
for (let i = columnIndex; i < state.colspan + columnIndex; i++) {
row[i] = slot;
}
}
columnIndex = state.colspan + columnIndex - 1;
if (columnIndex === columnSize - 1) {
columnIndex = 0;
rowIndex++;
}
}
const recordCells = [];
return table.map(tr => {
return tr.filter(td => {
const is = recordCells.includes(td);
if (is) {
return false;
}
recordCells.push(td);
return true;
});
});
}
function serialize(bodies) {
const rows = [];
for (let i = 0; i < bodies.length; i++) {
const cells = [];
bodies[i].forEach((cell, index) => {
cells.push({
row: bodies[i],
beforeCell: bodies[i][index - 1],
afterCell: bodies[i][index + 1],
offsetColumn: 0,
offsetRow: 0,
columnIndex: index,
rowIndex: i,
cell
});
});
rows.push({
beforeRow: bodies[i - 1] || null,
afterRow: bodies[i + 1] || null,
cellsPosition: cells,
cells: bodies[i]
});
}
let stop = false;
let columnIndex = 0;
const marks = [];
do {
let rowIndex = 0;
stop = false;
while (rowIndex < rows.length) {
const row = rows[rowIndex];
const cellPosition = row.cellsPosition[columnIndex];
if (cellPosition) {
let mark;
cellPosition.rowIndex = rowIndex;
cellPosition.columnIndex = columnIndex;
if (cellPosition.offsetColumn + 1 < cellPosition.cell.state.colspan) {
mark = `${rowIndex}*${columnIndex + 1}`;
if (marks.indexOf(mark) === -1) {
row.cellsPosition.splice(columnIndex + 1, 0, {
beforeCell: cellPosition.beforeCell,
afterCell: cellPosition.afterCell,
cell: cellPosition.cell,
row: row.cells,
rowIndex,
columnIndex,
offsetColumn: cellPosition.offsetColumn + 1,
offsetRow: cellPosition.offsetRow
});
marks.push(mark);
}
}
if (cellPosition.offsetRow + 1 < cellPosition.cell.state.rowspan) {
mark = `${rowIndex + 1}*${columnIndex}`;
if (marks.indexOf(mark) === -1) {
let nextRow = rows[rowIndex + 1];
if (!nextRow) {
nextRow = Object.assign(Object.assign({}, row), { cells: [], cellsPosition: [] });
rows.push(nextRow);
}
const newRowBeforeColumn = nextRow.cellsPosition[columnIndex - 1];
const newRowAfterColumn = nextRow.cellsPosition[columnIndex];
nextRow.cellsPosition.splice(columnIndex, 0, {
beforeCell: newRowBeforeColumn ? newRowBeforeColumn.cell : null,
afterCell: newRowAfterColumn ? newRowAfterColumn.cell : null,
row: nextRow.cells,
cell: cellPosition.cell,
offsetColumn: cellPosition.offsetColumn,
offsetRow: cellPosition.offsetRow + 1,
rowIndex,
columnIndex,
});
marks.push(mark);
}
}
stop = true;
}
rowIndex++;
}
columnIndex++;
} while (stop);
return rows;
}
function findFocusCell(componentInstance, slot) {
var _a;
while (slot) {
if (componentInstance.slots.has(slot)) {
return slot;
}
slot = (_a = slot.parent) === null || _a === void 0 ? void 0 : _a.parent;
}
return null;
}
function selectCells(startCell, endCell, componentInstance, columnCount) {
const serializedCells = serialize(slotsToTable(componentInstance.slots.toArray(), columnCount));
const slots = componentInstance.slots;
if (startCell === slots.first && endCell === slots.last) {
const last = serializedCells[serializedCells.length - 1].cellsPosition;
const cells = serializedCells.map(i => i.cellsPosition).flat().map(i => i.cell);
return {
startPosition: serializedCells[0].cellsPosition[0],
endPosition: last[last.length - 1],
selectedCells: Array.from(new Set(cells))
};
}
const p1 = findCellPosition(startCell, serializedCells);
const p2 = findCellPosition(endCell, serializedCells);
const minRow = Math.min(p1.minRow, p2.minRow);
const minColumn = Math.min(p1.minColumn, p2.minColumn);
const maxRow = Math.max(p1.maxRow, p2.maxRow);
const maxColumn = Math.max(p1.maxColumn, p2.maxColumn);
return selectCellsByRange(minRow, minColumn, maxRow, maxColumn, serializedCells);
}
function useTableMultipleRange(slots, stateController, config, callback) {
const injector = useContext();
const renderer = injector.get(Renderer);
const selection = injector.get(Selection);
const editorContainer = injector.get(VIEW_CONTAINER);
const animateBezier = new CubicBezier(0.25, 0.1, 0.25, 0.1);
const self = useSelf();
const subs = [
stateController.onChange.subscribe(s => {
config = s;
})
];
const mask = createElement('div', {
classes: ['textbus-table-editor-mask'],
});
let insertMask = false;
let animateId;
function addMask() {
editorContainer.appendChild(mask);
insertMask = true;
}
function removeMask() {
var _a;
insertMask = false;
(_a = mask.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(mask);
}
function animate(start, target) {
cancelAnimationFrame(animateId);
function toInt(n) {
return n < 0 ? Math.ceil(n) : Math.floor(n);
}
let step = 0;
const maxStep = 6;
const animateFn = () => {
step++;
const ratio = animateBezier.update(step / maxStep).y;
const left = start.left + toInt((target.left - start.left) * ratio);
const top = start.top + toInt((target.top - start.top) * ratio);
const width = start.width + toInt((target.width - start.width) * ratio);
const height = start.height + toInt((target.height - start.height) * ratio);
mask.style.left = left + 'px';
mask.style.top = top + 'px';
mask.style.width = width + 'px';
mask.style.height = height + 'px';
if (step < maxStep) {
animateId = requestAnimationFrame(animateFn);
}
};
animateId = requestAnimationFrame(animateFn);
}
function setSelectedCellsAndUpdateMaskStyle(startSlot, endSlot, offsetRect) {
const tableRange = selectCells(startSlot, endSlot, self, config.columnCount);
callback(tableRange);
const startPosition = tableRange.startPosition;
const endPosition = tableRange.endPosition;
const startCell = renderer.getNativeNodeByVNode(renderer.getVNodeBySlot(startPosition.cell));
const endCell = renderer.getNativeNodeByVNode(renderer.getVNodeBySlot(endPosition.cell));
if (!startCell || !endCell) {
return tableRange;
}
const startRect = startCell.getBoundingClientRect();
const endRect = endCell.getBoundingClientRect();
const maskRect = mask.getBoundingClientRect();
if (startSlot === endSlot) {
mask.style.background = 'none';
}
else {
mask.style.background = '';
}
if (insertMask) {
animate({
left: maskRect.left - offsetRect.left,
top: maskRect.top - offsetRect.top,
width: maskRect.width,
height: maskRect.height
}, {
left: startRect.left - offsetRect.left,
top: startRect.top - offsetRect.top,
width: endRect.left + endRect.width - startRect.left,
height: endRect.top + endRect.height - startRect.top
});
}
else {
addMask();
mask.style.left = startRect.left - offsetRect.left + 'px';
mask.style.top = startRect.top - offsetRect.top + 'px';
mask.style.width = endRect.left + endRect.width - startRect.left + 'px';
mask.style.height = endRect.top + endRect.height - startRect.top + 'px';
}
return tableRange;
}
function updateMaskEffect(event) {
const commonAncestorComponent = selection.commonAncestorComponent;
if (commonAncestorComponent === self) {
const containerRect = editorContainer.getBoundingClientRect();
const startCell = findFocusCell(self, selection.startSlot);
const endCell = findFocusCell(self, selection.endSlot);
if (startCell && endCell) {
const range = setSelectedCellsAndUpdateMaskStyle(startCell, endCell, containerRect);
if (startCell !== endCell) {
event === null || event === void 0 ? void 0 : event.useRanges(range.selectedCells.map(i => {
return {
slot: i,
startIndex: 0,
endIndex: i.length
};
}));
}
}
}
else {
removeMask();
}
}
onGetRanges(event => {
updateMaskEffect(event);
});
subs.push(merge(selection.onChange, renderer.onViewUpdated).pipe(debounceTime(1)).subscribe(() => {
updateMaskEffect();
}));
onDestroy(() => {
subs.forEach(i => i.unsubscribe());
removeMask();
});
}
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol */
function __decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function __metadata(metadataKey, metadataValue) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
var I18n_1;
/**
* Textbus 国际化方案类
*/
let I18n = I18n_1 = class I18n {
constructor(defaultConfig, customConfig) {
this.defaultConfig = defaultConfig;
this.customConfig = customConfig;
}
/**
* 通过 path 获取 i18n 配置中的字段,如果没有自定义配置,则返回默认配置,
* 如果获取到的值不为字符串,则返回空字符串
* @param path 访问路径,支持如: a.b、a['b'].c、a[0] 等格式
*/
get(path) {
const tokens = this.parse(path);
const customValue = this.getLabelByTokens(this.customConfig, tokens);
if (typeof customValue === 'string') {
return customValue;
}
const value = this.getLabelByTokens(this.defaultConfig, tokens);
return typeof value === 'string' ? value : '';
}
/**
* 通过 path 获取 i18n 配置中的上下文,并返回一个新的 i18n 实例
* @param path 访问路径,支持如: a.b、a['b'].c、a[0] 等格式
*/
getContext(path) {
const tokens = this.parse(path);
const customConfig = this.getLabelByTokens(this.customConfig, tokens) || {};
const defaultConfig = this.getLabelByTokens(this.defaultConfig, tokens) || {};
return new I18n_1(defaultConfig, customConfig);
}
/**
* 用于连接模板字符串,模板字符串占位符为: {number},其中 number 为占位符索引,
* 如: template string {0} is {1}.
* @param template 模板字符串
* @param values 替换占位符的值,根据参数下标位置替换模板字符串的点位符
*/
joinTemplate(template, ...values) {
return template.replace(/{\d+}/g, str => {
return values[str.replace(/{\s*|\s*}/g, '')] || str;
});
}
parse(path) {
return path.split(/[.\[\]'"]+/g).map(i => i.trim()).filter(i => i);
}
getLabelByTokens(config, tokens) {
if (!config || tokens.length === 0) {
return null;
}
let value = config;
for (let i = 0; i < tokens.length; i++) {
value = value[tokens[i]];
if (typeof value === 'undefined') {
return null;
}
}
return value;
}
};
I18n = I18n_1 = __decorate([
Injectable(),
__metadata("design:paramtypes", [Object, Object])
], I18n);
const alertComponent = defineComponent({
type: ContentType.BlockComponent,
name: 'AlertComponent',
setup(initData) {
const slots = useSlots((initData === null || initData === void 0 ? void 0 : initData.slots) || []);
let state = (initData === null || initData === void 0 ? void 0 : initData.state) || {
type: 'primary',
fill: false
};
const stateController = useState(state);
const injector = useContext();
const i18n = injector.get(I18n);
const sub = stateController.onChange.subscribe(newState => {
state = newState;
});
onDestroy(() => {
sub.unsubscribe();
});
if (slots.length === 0) {
slots.push(new Slot([
ContentType.InlineComponent,
ContentType.Text
]));
}
const childI18n = i18n.getContext('components.alertComponent.contextMenu');
onContextMenu(ev => {
ev.useMenus([{
label: state.fill ? childI18n.get('noFill') : childI18n.get('fill'),
onClick() {
stateController.update(draft => {
draft.fill = !state.fill;
});
}
}, {
label: childI18n.get('type'),
submenu: 'default,primary,info,success,warning,danger,dark,gray'.split(',').map(i => {
return {
label: i,
onClick() {
stateController.update(draft => {
draft.type = i;
});
}
};
})
}]);
});
return {
render(slotRender) {
const classes = ['tb-alert'];
if (state.fill) {
classes.push('tb-alert-fill');
}
if (state.type) {
classes.push('tb-alert-' + state.type);
}
return (VElement.createElement("tb-alert", { "data-type": state.type, class: classes.join(' ') }, slotRender(slots.get(0), children => {
return VElement.createElement("div", null, children);
})));
}
};
}
});
const alertComponentLoader = {
match(element) {
return element.tagName.toLowerCase() === 'tb-alert';
},
read(element, context, slotParser) {
return alertComponent.createInstance(context, {
state: {
fill: element.classList.contains('tb-alert-fill'),
type: element.dataset.type || ''
},
slots: [
slotParser(new Slot([
ContentType.InlineComponent,
ContentType.Text
]), element.children[0] || document.createElement('div'))
]
});
}
};
const audioComponent = defineComponent({
name: 'AudioComponent',
type: ContentType.InlineComponent,
setup(data) {
let state = (data === null || data === void 0 ? void 0 : data.state) || {
src: '',
autoplay: false,
controls: true,
};
const controller = useState(state);
controller.onChange.subscribe(s => {
state = s;
});
return {
render() {
return (VElement.createElement("audio", { src: state.src, autoplay: state.autoplay, controls: state.controls }));
},
toJSON() {
return Object.assign({}, state);
},
mergeProps(props) {
state = controller.update(draft => {
Object.assign(draft, props);
});
}
};
}
});
const audioComponentLoader = {
match(element) {
return element.nodeName.toLowerCase() === 'audio';
},
read(element, context) {
return audioComponent.createInstance(context, {
state: {
src: element.src,
autoplay: element.autoplay,
controls: element.controls
}
});
},
};
const blockComponent = defineComponent({
type: ContentType.BlockComponent,
name: 'BlockComponent',
setup(data) {
const injector = useContext();
const slots = useSlots((data === null || data === void 0 ? void 0 : data.slots) || [new Slot([
ContentType.Text,
ContentType.InlineComponent,
ContentType.BlockComponent
])]);
if (!slots.length) {
slots.push(new Slot([
ContentType.Text,
ContentType.InlineComponent,
ContentType.BlockComponent
]));
}
useEnterBreaking(injector, slots);
return {
render(slotRender) {
return slotRender(slots.get(0), children => {
return VElement.createElement("div", null, children);
});
}
};
}
});
const blockComponentLoader = {
match(element) {
return element.tagName === 'DIV';
},
read(element, injector, slotParser) {
const slot = slotParser(new Slot([
ContentType.Text,
ContentType.BlockComponent,
ContentType.InlineComponent
]), element);
const content = slot.sliceContent();
const isAllContent = content.some(i => {
return typeof i === 'string' || i.type === ContentType.InlineComponent;
});
if (isAllContent) {
return blockComponent.createInstance(injector, {
slots: [slot]
});
}
return slot;
},
};
const blockquoteComponent = defineComponent({
type: ContentType.BlockComponent,
name: 'BlockquoteComponent',
zenCoding: {
key: ' ',
match: /^>$/,
generateInitData() {
return {
slots: [new Slot([
ContentType.Text,
ContentType.InlineComponent,
ContentType.BlockComponent
])]
};
}
},
setup(data) {
const slots = useSlots((data === null || data === void 0 ? void 0 : data.slots) || [new Slot([
ContentType.Text,
ContentType.InlineComponent,
ContentType.BlockComponent
])]);
if (!slots.length) {
slots.push(new Slot([
ContentType.Text,
ContentType.InlineComponent,
ContentType.BlockComponent
]));
}
return {
render(slotRender) {
return slotRender(slots.get(0), children => {
return VElement.createElement("blockquote", { class: "tb-blockquote" }, children);
});
}
};
}
});
const blockquoteComponentLoader = {
match(element) {
return element.tagName === 'BLOCKQUOTE' || element.tagName === 'DIV' && element.className === 'tb-blockquote';
},
read(element, injector, slotParser) {
const slot = slotParser(new Slot([
ContentType.Text,
ContentType.BlockComponent,
ContentType.InlineComponent
]), element);
return blockquoteComponent.createInstance(injector, {
slots: [slot]
});
},
};
const headingComponent = defineComponent({
type: ContentType.BlockComponent,
name: 'HeadingComponent',
zenCoding: {
key: ' ',
match(content) {
return /^#{1,6}$/.test(content);
},
generateInitData(content) {
return {
state: 'h' + content.length
};
}
},
setup(data) {
const injector = useContext();
const slots = useSlots((data === null || data === void 0 ? void 0 : data.slots) || [new Slot([
ContentType.Text,
ContentType.InlineComponent
])]);
if (!slots.length) {
slots.push(new Slot([
ContentType.Text,
ContentType.InlineComponent
]));
}
useEnterBreaking(injector, slots);
return {
type: (data === null || data === void 0 ? void 0 : data.state) || 'h1',
render(slotRender) {
return slotRender(slots.get(0), children => {
const Tag = (data === null || data === void 0 ? void 0 : data.state) || 'h1';
return VElement.createElement(Tag, null, children);
});
}
};
}
});
const headingComponentLoader = {
match(element) {
return /^h[1-6]$/i.test(element.tagName);
},
read(element, injector, slotParser) {
const slot = slotParser(new Slot([
ContentType.Text,
ContentType.InlineComponent,
]), element);
return headingComponent.createInstance(injector, {
slots: [slot],
state: element.tagName.toLowerCase()
});
},
};
class Form {
constructor(config) {
this.config = config;
this.completeEvent = new Subject();
this.cancelEvent = new Subject();
this.onComplete = this.completeEvent.asObservable();
this.onCancel = this.cancelEvent.asObservable();
this.elementRef = createElement('form', {
classes: [config.mini ? 'textbus-form-mini' : 'textbus-form'],
attrs: {
novalidate: true,
autocomplete: 'off'
}
});
if (config.title) {
this.elementRef.appendChild(createElement('h3', {
classes: ['textbus-form-title'],
children: [createTextNode(config.title)]
}));
}
this.elementRef.appendChild(this.body = createElement('div', {
attrs: {
novalidate: 'novalidate'
},
classes: config.mini ? [] : ['textbus-form-body'],
children: config.items.map(item => {
return item.elementRef;
})
}));
const btns = config.mini ? [
createElement('button', {
attrs: {
type: 'submit'
},
classes: ['textbus-btn', 'textbus-btn-block', 'textbus-btn-primary'],
children: [createTextNode(this.config.confirmBtnText || '确定')]
})
] : [
createElement('button', {
attrs: {
type: 'submit'
},
classes: ['textbus-btn', 'textbus-btn-primary'],
children: [createTextNode(this.config.confirmBtnText || '确定')]
}),
(() => {
const cancelBtn = createElement('button', {
classes: ['textbus-btn', 'textbus-btn-default'],
attrs: {
type: 'button'
},
children: [createTextNode(this.config.cancelBtnText || '取消')]
});
cancelBtn.addEventListener('click', () => {
this.cancelEvent.next();
});
return cancelBtn;
})()
];
this.elementRef.appendChild(this.footer = createElement('div', {
classes: ['textbus-form-footer'],
children: btns
}));
this.elementRef.addEventListener('submit', (ev) => {
ev.preventDefault();
const values = {};
for (const item of config.items) {
if (!item.validate()) {
return;
}
const i = item.getAttr();
if (i) {
values[i.name] = i.value;
}
}
this.completeEvent.next(values);
});
}
addItem(item, index) {
if (typeof index === 'number') {
const next = this.config.items[index];
if (next) {
this.config.items.splice(index, 0, item);
this.elementRef.insertBefore(item.elementRef, next.elementRef);
return;
}
}
this.config.items.push(item);
this.body.appendChild(item.elementRef);
}
removeItem(item) {
var _a;
const index = this.config.items.indexOf(item);
if (index > -1) {
this.config.items.splice(index, 1);
(_a = item.elementRef.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(item.elementRef);
}
}
reset() {
this.config.items.forEach(item => {
item.reset();
});
}
update(value) {
Object.keys(value).forEach(key => {
this.config.items.forEach(item => {
if (item.name === key) {
item.update(value[key]);
}
});
});
}
}
class FormButton {
constructor(config) {
this.config = config;
this.elementRef = createElement('div', {
classes: ['textbus-form-group'],
children: [
createElement('div', {
classes: ['textbus-control-label'],
children: [createTextNode(this.config.label)]
}),
createElement('div', {
classes: ['textbus-control-value'],
children: [
createElement('button', {
classes: ['textbus-btn', 'textbus-btn-dark'],
attrs: {
type: 'button'
},
on: {
click: () => {
this.config.onClick();
}
},
children: [
createElement('span', {
classes: this.config.iconClasses
}),
createTextNode(' ' + this.config.btnText)
]
})
]
})
]
});
this.name = this.config.name;
}
reset() {
//
}
update() {
// this.value = value;
}
getAttr() {
return {
name: this.name,
value: this.config.value
};
}
validate() {
return true;
}
}
class FormHidden {
constructor(config) {
this.config = config;
this.elementRef = document.createElement('input');
this.name = config.name;
this.value = config.value;
this.elementRef.type = 'hidden';
this.elementRef.value = config.value + '';
}
reset() {
//
}
update() {
// this.value = value;
}
getAttr() {
return {
name: this.name,
value: this.value
};
}
validate() {