fix: 协同与异步卡片加载会造成写入多个无意义节点
This commit is contained in:
parent
d9ccd8faa5
commit
ba73863f7c
|
@ -124,11 +124,14 @@ Download address: response.download || response.data && response.data.download T
|
|||
/**
|
||||
* Parse the uploaded Respone and return result: whether it is successful or not, data: success: file address, failure: error message
|
||||
*/
|
||||
parse?: (
|
||||
response: any,
|
||||
) => {
|
||||
parse?: (response: any) => {
|
||||
result: boolean;
|
||||
data: string;
|
||||
data: {
|
||||
url: string;
|
||||
preview?: string;
|
||||
download?: string;
|
||||
status?: string;
|
||||
} | string;
|
||||
};
|
||||
```
|
||||
|
||||
|
|
|
@ -124,11 +124,14 @@ limitSize?: number;
|
|||
/**
|
||||
* 解析上传后的Respone,返回 result:是否成功,data:成功:文件地址,失败:错误信息
|
||||
*/
|
||||
parse?: (
|
||||
response: any,
|
||||
) => {
|
||||
parse?: (response: any) => {
|
||||
result: boolean;
|
||||
data: string;
|
||||
data: {
|
||||
url: string;
|
||||
preview?: string;
|
||||
download?: string;
|
||||
status?: string;
|
||||
} | string;
|
||||
};
|
||||
```
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ class Button {
|
|||
const rootRect = root.get<HTMLElement>()!.getBoundingClientRect();
|
||||
const top = rangeRect.y - rootRect.y;
|
||||
this.#container.css('top', `${top}px`);
|
||||
this.#container.css('right', `16px`);
|
||||
this.#container.css('right', `-16px`);
|
||||
this.#container.show('flex');
|
||||
}
|
||||
|
||||
|
|
|
@ -125,7 +125,7 @@
|
|||
}
|
||||
|
||||
.editor-content .am-engine {
|
||||
padding: 0 0 60px;
|
||||
padding: 40px 0 60px;
|
||||
|
||||
@media @mobile {
|
||||
padding: 0;
|
||||
|
|
|
@ -333,9 +333,6 @@ const EditorComponent: React.FC<EditorProps> = ({
|
|||
<OTComponent members={members} />,
|
||||
headerOTMembersElement,
|
||||
);
|
||||
return () => {
|
||||
ReactDOM.unmountComponentAtNode(headerOTMembersElement);
|
||||
};
|
||||
}, [members, props.ot]);
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.data-toc-wrapper {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
min-width: 210px;
|
||||
max-width: 210px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
|
|
|
@ -361,9 +361,6 @@ abstract class CardEntry<T extends CardValue = {}> implements CardInterface {
|
|||
}
|
||||
didRender() {
|
||||
if (this.loading) this.find(`.${CARD_LOADING_KEY}`).remove();
|
||||
setTimeout(() => {
|
||||
this.root.removeAttributes(CARD_LOADING_KEY);
|
||||
}, 100);
|
||||
if (this.resize) {
|
||||
const container =
|
||||
typeof this.resize === 'function'
|
||||
|
|
|
@ -660,6 +660,7 @@ class CardModel implements CardModelInterface {
|
|||
this.removeComponent(card);
|
||||
}
|
||||
cardNode.attributes(CARD_LOADING_KEY, 'true');
|
||||
attributes[CARD_LOADING_KEY] = 'true';
|
||||
cardNode.empty();
|
||||
}
|
||||
//ready_card_key 待创建的需要重新生成节点,并替换当前待创建节点
|
||||
|
|
|
@ -441,7 +441,6 @@ class ChangeModel implements ChangeInterface {
|
|||
let node: NodeInterface | null = $(childNodes[0]);
|
||||
let prev: NodeInterface | null = null;
|
||||
const appendNodes = [];
|
||||
let startRangeNodeParent = startRange.node.parent();
|
||||
while (node && node.length > 0) {
|
||||
nodeApi.removeSide(node);
|
||||
const next: NodeInterface | null = node.next();
|
||||
|
@ -460,12 +459,8 @@ class ChangeModel implements ChangeInterface {
|
|||
range.select(node, true).collapse(false);
|
||||
}
|
||||
// 被删除了重新设置开始节点位置
|
||||
if (
|
||||
startRange &&
|
||||
(!startRangeNodeParent || startRangeNodeParent.length === 0)
|
||||
) {
|
||||
if (startRange && !startRange.node[0].parentNode) {
|
||||
const children = node.children();
|
||||
startRangeNodeParent = node.parent();
|
||||
startRange = {
|
||||
node: node,
|
||||
offset:
|
||||
|
|
|
@ -537,7 +537,9 @@ class NativeEvent {
|
|||
) => void,
|
||||
) {
|
||||
const { change } = this.engine;
|
||||
const fragment = new Paste(source, this.engine).normalize();
|
||||
const fragment = new Paste(source, this.engine).normalize(
|
||||
insert === undefined,
|
||||
);
|
||||
this.engine.trigger('paste:before', fragment);
|
||||
if (insert) insert(fragment, range, undefined, followActiveMark);
|
||||
else
|
||||
|
|
|
@ -297,7 +297,7 @@ export default class Paste {
|
|||
});
|
||||
}
|
||||
|
||||
normalize() {
|
||||
normalize(autoAppendCurrent: boolean = true) {
|
||||
const nodeApi = this.engine.node;
|
||||
let fragment = this.parser();
|
||||
this.elementNormalize(fragment);
|
||||
|
@ -376,6 +376,7 @@ export default class Paste {
|
|||
.shrinkToTextNode();
|
||||
const { startNode } = cloneRange;
|
||||
if (
|
||||
autoAppendCurrent &&
|
||||
startNode.inEditor() &&
|
||||
first &&
|
||||
first.name === 'p' &&
|
||||
|
|
|
@ -332,14 +332,15 @@ class Engine implements EngineInterface {
|
|||
|
||||
setHtml(html: string, callback?: (count: number) => void) {
|
||||
this.change.setHtml(html, (count) => {
|
||||
this.normalize();
|
||||
this.container.allChildren(true).forEach((child) => {
|
||||
if (this.node.isInline(child)) {
|
||||
this.inline.repairCursor(child);
|
||||
} else if (this.node.isMark(child)) {
|
||||
this.mark.repairCursor(child);
|
||||
}
|
||||
if (callback) callback(count);
|
||||
});
|
||||
if (callback) callback(count);
|
||||
});
|
||||
this.nodeId.generateAll(this.container);
|
||||
return this;
|
||||
|
|
|
@ -106,7 +106,7 @@ class HistoryModel implements HistoryInterface {
|
|||
console.error(error);
|
||||
}
|
||||
if (this.engine.isEmpty()) this.engine.change.initValue();
|
||||
this.engine.ot.startMutation();
|
||||
|
||||
if (isUndo) {
|
||||
//清除操作前记录的range
|
||||
this.engine.change.getRangePathBeforeCommand();
|
||||
|
@ -114,6 +114,7 @@ class HistoryModel implements HistoryInterface {
|
|||
this.engine.change.change();
|
||||
this.engine.trigger('undo');
|
||||
}
|
||||
this.engine.ot.startMutation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,7 +140,7 @@ class HistoryModel implements HistoryInterface {
|
|||
this.reset();
|
||||
console.error(error);
|
||||
}
|
||||
this.engine.ot.startMutation();
|
||||
|
||||
if (isRedo) {
|
||||
// 清除操作前记录的range
|
||||
this.engine.change.getRangePathBeforeCommand();
|
||||
|
@ -147,6 +148,7 @@ class HistoryModel implements HistoryInterface {
|
|||
this.engine.change.change();
|
||||
this.engine.trigger('redo');
|
||||
}
|
||||
this.engine.ot.startMutation();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,15 @@ class Consumer implements ConsumerInterface {
|
|||
endOffset: index,
|
||||
};
|
||||
const offset = index - JSON0_INDEX.ELEMENT;
|
||||
// 正在加载中的节点,直接渲染
|
||||
if (
|
||||
node.nodeType === Node.ELEMENT_NODE &&
|
||||
(node as HTMLElement).hasAttribute(CARD_LOADING_KEY)
|
||||
) {
|
||||
const { card } = this.engine;
|
||||
const cardComponent = card.find(node);
|
||||
if (cardComponent) card.renderComponent(cardComponent);
|
||||
}
|
||||
const childNode = Array.from(node.childNodes).filter((node) => {
|
||||
const childNode = $(node);
|
||||
return !isTransientElement(childNode);
|
||||
|
@ -51,7 +60,7 @@ class Consumer implements ConsumerInterface {
|
|||
1 === path.length ||
|
||||
pathOffset === JSON0_INDEX.TAG_NAME ||
|
||||
pathOffset === JSON0_INDEX.ATTRIBUTE ||
|
||||
childNode.nodeType === Node.TEXT_NODE
|
||||
(childNode && childNode.nodeType === Node.TEXT_NODE)
|
||||
) {
|
||||
return {
|
||||
startNode: childNode,
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
updateIndex,
|
||||
opsSort,
|
||||
} from './utils';
|
||||
import { escapeDots, escape } from '../utils/string';
|
||||
import { escapeDots, escape, decodeCardValue } from '../utils/string';
|
||||
import { toJSON0, getValue } from './utils';
|
||||
import { EngineInterface } from '../types/engine';
|
||||
import { Op, Path, StringInsertOp, StringDeleteOp, Doc } from 'sharedb';
|
||||
|
@ -22,15 +22,20 @@ import { NodeInterface } from '../types/node';
|
|||
import { DocInterface, RepairOp } from '../types/ot';
|
||||
import { $ } from '../node';
|
||||
import {
|
||||
CARD_ELEMENT_KEY,
|
||||
CARD_CENTER_SELECTOR,
|
||||
CARD_KEY,
|
||||
CARD_LOADING_KEY,
|
||||
CARD_SELECTOR,
|
||||
CARD_VALUE_KEY,
|
||||
DATA_ELEMENT,
|
||||
DATA_ID,
|
||||
DATA_TRANSIENT_ELEMENT,
|
||||
JSON0_INDEX,
|
||||
UI,
|
||||
UI_SELECTOR,
|
||||
} from '../constants';
|
||||
import { getDocument } from '../utils/node';
|
||||
import { CardValue } from 'src';
|
||||
import type { CardEntry } from '../types/card';
|
||||
|
||||
class Producer extends EventEmitter2 {
|
||||
private engine: EngineInterface;
|
||||
|
@ -519,18 +524,24 @@ class Producer extends EventEmitter2 {
|
|||
const tMapValue = cardMap.get(cardElement[0]);
|
||||
if (tMapValue === undefined && cardElement.isEditableCard()) {
|
||||
const cardName = cardElement.attributes(CARD_KEY);
|
||||
const cardValue = decodeCardValue(
|
||||
cardElement.attributes(CARD_VALUE_KEY),
|
||||
);
|
||||
const result = this.findCardForDoc(
|
||||
this.doc.data,
|
||||
cardName,
|
||||
(attriables) => {
|
||||
return (
|
||||
attriables[DATA_ID] ===
|
||||
cardElement.attributes(DATA_ID)
|
||||
// 卡片id一致
|
||||
const value = decodeCardValue(
|
||||
attriables[CARD_VALUE_KEY],
|
||||
);
|
||||
return value.id === cardValue.id;
|
||||
},
|
||||
);
|
||||
// 没有这个卡片节点,或者卡片内部已经渲染了才需要过滤
|
||||
if (
|
||||
!result?.rendered &&
|
||||
result &&
|
||||
!result.rendered &&
|
||||
cardElement.attributes(CARD_LOADING_KEY) !== 'remote'
|
||||
) {
|
||||
isTransient = false;
|
||||
|
@ -542,6 +553,83 @@ class Producer extends EventEmitter2 {
|
|||
} else if (tMapValue !== undefined) {
|
||||
isTransient = tMapValue;
|
||||
}
|
||||
// 标记节点为已处理
|
||||
const { addedNodes } = record;
|
||||
addedNodes.forEach((addNode) => {
|
||||
addNode['__card_rendered'] = true;
|
||||
});
|
||||
// 需要比对异步加载的卡片子节点(body -> center -> 非 ui 和 data-transient-element节点)是否已经处理完,处理完就移除掉卡片根节点的 CARD_LOADING_KEY 标记
|
||||
// card.root.removeAttributes(CARD_LOADING_KEY);
|
||||
// 判断卡片下面的节点
|
||||
const isRendered = cardElement.isEditableCard()
|
||||
? cardElement
|
||||
.find(CARD_CENTER_SELECTOR)
|
||||
.children()
|
||||
.toArray()
|
||||
.every((child) => {
|
||||
if (child.length === 0) return true;
|
||||
const attributes = child.attributes();
|
||||
if (
|
||||
attributes[DATA_ELEMENT] === UI ||
|
||||
!!attributes[DATA_TRANSIENT_ELEMENT]
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (child[0]['__card_rendered'] === true) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
: true;
|
||||
if (isRendered) {
|
||||
const handleEditableCard = (
|
||||
editableCard: NodeInterface,
|
||||
) => {
|
||||
const childAllLoaded = editableCard
|
||||
.find(CARD_SELECTOR)
|
||||
.toArray()
|
||||
.every((childCard) => {
|
||||
const childLoading =
|
||||
childCard.attributes(CARD_LOADING_KEY);
|
||||
if (!childLoading) {
|
||||
return true;
|
||||
}
|
||||
// 如果子卡片是懒加载的,则算已加载完成
|
||||
const cardComponent =
|
||||
this.engine.card.find(childCard);
|
||||
if (
|
||||
(cardComponent?.constructor as CardEntry)
|
||||
.lazyRender
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (childAllLoaded) {
|
||||
editableCard.removeAttributes(CARD_LOADING_KEY);
|
||||
}
|
||||
};
|
||||
// 可编辑卡片需要查看子卡片是否都渲染成功
|
||||
if (cardElement.isEditableCard()) {
|
||||
handleEditableCard(cardElement);
|
||||
} else {
|
||||
cardElement.removeAttributes(CARD_LOADING_KEY);
|
||||
// 非可编辑卡片需要判断当前是否在可编辑器卡内,加载成功需要判断父级的可编辑卡片是否都加载成功
|
||||
const cardParentElement = cardElement.parent();
|
||||
if (cardParentElement) {
|
||||
const parentEditableCard = this.engine.card.closest(
|
||||
cardParentElement,
|
||||
true,
|
||||
);
|
||||
if (
|
||||
parentEditableCard &&
|
||||
parentEditableCard.length > 0
|
||||
) {
|
||||
handleEditableCard(parentEditableCard);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
!isTransient &&
|
||||
|
@ -564,7 +652,6 @@ class Producer extends EventEmitter2 {
|
|||
return !isTransient;
|
||||
});
|
||||
this.clearCache();
|
||||
|
||||
//重置缓存
|
||||
this.cacheTransientElements = undefined;
|
||||
let ops = this.generateOps(records);
|
||||
|
|
|
@ -170,25 +170,8 @@ class Parser implements ParserInterface {
|
|||
});
|
||||
return;
|
||||
}
|
||||
root.traverse((node) => {
|
||||
if (
|
||||
node.equal(root) ||
|
||||
['style', 'script', 'meta'].includes(node.name)
|
||||
)
|
||||
return;
|
||||
if (node.isElement()) {
|
||||
const isCard = node.isCard();
|
||||
//转换标签
|
||||
if (conversion && (!schema.getType(node) || isCard)) {
|
||||
const newNode = this.convert(conversion, node, schema);
|
||||
if (newNode) {
|
||||
this.normalize(newNode, schema, conversion);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (isCard) return;
|
||||
//分割
|
||||
const filter = (node: NodeInterface) => {
|
||||
// 过滤分割
|
||||
const filter = (node: NodeInterface, type: string = 'mark') => {
|
||||
//获取节点属性样式
|
||||
const attributes = node.attributes();
|
||||
const style = getStyleMap(attributes.style || '');
|
||||
|
@ -225,12 +208,16 @@ class Parser implements ParserInterface {
|
|||
if (
|
||||
filterAttrCount === attrKeys.length &&
|
||||
filterStyleCount === styleKeys.length &&
|
||||
schema.getType(newNode) === 'mark'
|
||||
schema.getType(newNode) === type
|
||||
) {
|
||||
node.before(newNode);
|
||||
const children = node.children();
|
||||
newNode.append(
|
||||
children.length > 0 ? children : $('\u200b', null),
|
||||
children.length > 0
|
||||
? children
|
||||
: type === 'block'
|
||||
? '<br />'
|
||||
: $('\u200b', null),
|
||||
);
|
||||
node.remove();
|
||||
node = newNode;
|
||||
|
@ -240,8 +227,23 @@ class Parser implements ParserInterface {
|
|||
newNode.removeAttributes('style');
|
||||
return newNode;
|
||||
};
|
||||
//当前节点是 inline 节点,inline 节点不允许嵌套、不允许放入mark节点
|
||||
inlineApi.flat(node, schema);
|
||||
root.traverse((node) => {
|
||||
if (
|
||||
node.equal(root) ||
|
||||
['style', 'script', 'meta'].includes(node.name)
|
||||
)
|
||||
return;
|
||||
if (node.isElement()) {
|
||||
const isCard = node.isCard();
|
||||
//转换标签
|
||||
if (conversion && (!schema.getType(node) || isCard)) {
|
||||
const newNode = this.convert(conversion, node, schema);
|
||||
if (newNode) {
|
||||
this.normalize(newNode, schema, conversion);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (isCard) return;
|
||||
//当前节点是 mark 节点
|
||||
if (nodeApi.isMark(node, schema)) {
|
||||
//过滤掉当前mark节点属性样式并使用剩下的属性样式组成新的节点
|
||||
|
@ -285,6 +287,9 @@ class Parser implements ParserInterface {
|
|||
oldRules.push(rule);
|
||||
}
|
||||
}
|
||||
} else if (nodeApi.isInline(node)) {
|
||||
//当前节点是 inline 节点,inline 节点不允许嵌套、不允许放入mark节点
|
||||
inlineApi.flat(node, schema);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -913,15 +913,19 @@ Range.fromPath = (
|
|||
? root.find(`[${DATA_ID}="${path.start.id}"]`)
|
||||
: root;
|
||||
const startNode = getNode(
|
||||
path.start.bi > -1 ? startPath.slice(path.start.bi) : startPath,
|
||||
beginContext,
|
||||
path.start.bi > -1 && beginContext.length > 0
|
||||
? startPath.slice(path.start.bi)
|
||||
: startPath,
|
||||
beginContext.length > 0 ? beginContext : undefined,
|
||||
);
|
||||
const endContext = path.end.id
|
||||
? root.find(`[${DATA_ID}="${path.end.id}"]`)
|
||||
: root;
|
||||
const endNode = getNode(
|
||||
path.end.bi > -1 ? endPath.slice(path.end.bi) : endPath,
|
||||
endContext,
|
||||
path.end.bi > -1 && endContext.length > 0
|
||||
? endPath.slice(path.end.bi)
|
||||
: endPath,
|
||||
endContext.length > 0 ? endContext : undefined,
|
||||
);
|
||||
const range = Range.create(editor, document);
|
||||
setRange(
|
||||
|
|
|
@ -320,12 +320,9 @@ class Schema implements SchemaInterface {
|
|||
* @param rule 规则
|
||||
*/
|
||||
filterStyles(styles: { [k: string]: string }, rule: SchemaRule) {
|
||||
if (!rule.attributes?.style) {
|
||||
styles = {};
|
||||
return;
|
||||
}
|
||||
Object.keys(styles).forEach((styleName) => {
|
||||
if (
|
||||
!rule.attributes?.style ||
|
||||
!this.checkValue(
|
||||
rule.attributes!.style as SchemaAttributes,
|
||||
styleName,
|
||||
|
@ -342,12 +339,9 @@ class Schema implements SchemaInterface {
|
|||
* @param rule 规则
|
||||
*/
|
||||
filterAttributes(attributes: { [k: string]: string }, rule: SchemaRule) {
|
||||
if (!rule.attributes) {
|
||||
attributes = {};
|
||||
return;
|
||||
}
|
||||
Object.keys(attributes).forEach((attributesName) => {
|
||||
if (
|
||||
!rule.attributes ||
|
||||
!this.checkValue(
|
||||
rule.attributes as SchemaAttributes,
|
||||
attributesName,
|
||||
|
|
|
@ -196,7 +196,7 @@ class Scrollbar extends EventEmitter2 {
|
|||
left = getScrollLeft
|
||||
? getScrollLeft(-0) + element.scrollLeft - left
|
||||
: element.scrollLeft - left;
|
||||
|
||||
if (left < 0) left = 0;
|
||||
if (onScrollX) {
|
||||
const result = onScrollX(left);
|
||||
if (result > 0) element.scrollLeft = result;
|
||||
|
|
|
@ -313,6 +313,7 @@ export interface CardInterface {
|
|||
beforeRender?(): void;
|
||||
/**
|
||||
* 渲染卡片
|
||||
* @param args 渲染自定义参数
|
||||
*/
|
||||
render(...args: any): NodeInterface | string | void;
|
||||
/**
|
||||
|
@ -540,6 +541,12 @@ export interface CardModelInterface {
|
|||
callback?: (count: number) => void,
|
||||
lazyRender?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 渲染单个卡片
|
||||
* @param card 卡片实例
|
||||
* @param args 渲染自定义参数
|
||||
*/
|
||||
renderComponent(card: CardInterface, ...args: any): void;
|
||||
/**
|
||||
* 重新渲染卡片
|
||||
* @param cards 卡片集合
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { isMobile } from '../../utils';
|
||||
import Default from './default';
|
||||
|
||||
class At extends Default {
|
||||
hotkey = (event: KeyboardEvent) =>
|
||||
event.key === '@' ||
|
||||
(event.shiftKey && event.keyCode === 229 && event.code === 'Digit2');
|
||||
(event.shiftKey && event.keyCode === 229 && event.code === 'Digit2') ||
|
||||
(isMobile && event.keyCode === 229);
|
||||
}
|
||||
export default At;
|
||||
|
|
|
@ -23,6 +23,7 @@ class ToolbarComponent extends Card<{ data: Data }> {
|
|||
private placeholder?: NodeInterface;
|
||||
private component?: CollapseComponentInterface;
|
||||
#collapseData?: Data;
|
||||
#data?: any;
|
||||
|
||||
static get cardName() {
|
||||
return 'toolbar';
|
||||
|
@ -55,6 +56,10 @@ class ToolbarComponent extends Card<{ data: Data }> {
|
|||
});
|
||||
}
|
||||
|
||||
setData(_data: any) {
|
||||
this.#data = _data;
|
||||
}
|
||||
|
||||
getData(): Data {
|
||||
if (!isEngine(this.editor)) {
|
||||
return [];
|
||||
|
@ -74,9 +79,8 @@ class ToolbarComponent extends Card<{ data: Data }> {
|
|||
collapseItems.push(...group.items);
|
||||
});
|
||||
const value = this.getValue();
|
||||
if (!value || !value.data) return [];
|
||||
|
||||
value.data.forEach((group: any) => {
|
||||
(this.#data || (value ? value.data : []) || []).forEach(
|
||||
(group: any) => {
|
||||
const title = group.title;
|
||||
const items: Array<Omit<CollapseItemProps, 'engine'>> = [];
|
||||
group.items.forEach((item: any) => {
|
||||
|
@ -99,7 +103,8 @@ class ToolbarComponent extends Card<{ data: Data }> {
|
|||
title,
|
||||
items,
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -203,7 +208,8 @@ class ToolbarComponent extends Card<{ data: Data }> {
|
|||
else this.placeholder?.hide();
|
||||
}
|
||||
|
||||
render(): string | void | NodeInterface {
|
||||
render(data?: any): string | void | NodeInterface {
|
||||
this.setData(data);
|
||||
const editor = this.editor;
|
||||
if (!isEngine(editor) || isServer) return;
|
||||
const language = editor.language.get<{ placeholder: string }>(
|
||||
|
|
|
@ -84,10 +84,10 @@ class ToolbarPlugin extends Plugin<Options> {
|
|||
const data = this.options.config || defaultConfig(this.editor);
|
||||
const card = this.editor.card.insert(
|
||||
ToolbarComponent.cardName,
|
||||
{
|
||||
{},
|
||||
data,
|
||||
},
|
||||
);
|
||||
) as ToolbarComponent;
|
||||
card.setData(data);
|
||||
this.editor.card.activate(card.root);
|
||||
range = change.range.get();
|
||||
//选中关键词输入节点
|
||||
|
|
|
@ -96,7 +96,6 @@ const CollapseItem: React.FC<CollapseItemProps> = ({
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return prompt ? (
|
||||
<Popover
|
||||
placement={placement || 'right'}
|
||||
|
|
|
@ -21,6 +21,7 @@ class ToolbarComponent extends Card {
|
|||
private placeholder?: NodeInterface;
|
||||
private component?: CollapseComponentInterface;
|
||||
#collapseData?: Data;
|
||||
#data?: any;
|
||||
|
||||
static get cardName() {
|
||||
return 'toolbar';
|
||||
|
@ -53,6 +54,10 @@ class ToolbarComponent extends Card {
|
|||
});
|
||||
}
|
||||
|
||||
setData(_data: any) {
|
||||
this.#data = _data;
|
||||
}
|
||||
|
||||
getData(): Data {
|
||||
if (!isEngine(this.editor)) {
|
||||
return [];
|
||||
|
@ -72,9 +77,8 @@ class ToolbarComponent extends Card {
|
|||
collapseItems.push(...group.items);
|
||||
});
|
||||
const value = this.getValue();
|
||||
if (!value || !value['data']) return [];
|
||||
|
||||
value['data'].forEach((group: any) => {
|
||||
(this.#data || (value ? value['data'] : []) || []).forEach(
|
||||
(group: any) => {
|
||||
const title = group.title;
|
||||
const items: Array<Omit<CollapseItemProps, 'engine'>> = [];
|
||||
group.items.forEach((item: any) => {
|
||||
|
@ -97,7 +101,8 @@ class ToolbarComponent extends Card {
|
|||
title,
|
||||
items,
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -201,7 +206,8 @@ class ToolbarComponent extends Card {
|
|||
else this.placeholder?.hide();
|
||||
}
|
||||
|
||||
render(): string | void | NodeInterface {
|
||||
render(data?: any): string | void | NodeInterface {
|
||||
this.#data = data;
|
||||
const editor = this.editor;
|
||||
if (!isEngine(editor) || isServer) return;
|
||||
const language = editor.language.get('toolbar', 'component');
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
DATA_TRANSIENT_ELEMENT,
|
||||
EditorInterface,
|
||||
|
@ -86,10 +87,10 @@ class ToolbarPlugin extends Plugin<Options> {
|
|||
const data = this.options.config || defaultConfig(this.editor);
|
||||
const card = this.editor.card.insert(
|
||||
ToolbarComponent.cardName,
|
||||
{
|
||||
{},
|
||||
data,
|
||||
},
|
||||
);
|
||||
) as ToolbarComponent;
|
||||
card.setData(data);
|
||||
card.root.attributes(DATA_TRANSIENT_ELEMENT, 'true');
|
||||
this.editor.card.activate(card.root);
|
||||
range = change.range.get();
|
||||
|
|
|
@ -165,13 +165,15 @@ export default class extends Plugin {
|
|||
card?.getValue() ||
|
||||
decodeCardValue(node.attributes(CARD_VALUE_KEY));
|
||||
if (value?.url && value.status === 'done') {
|
||||
const html = `<a data-type="${
|
||||
const html = `<span data-type="${
|
||||
FileComponent.cardName
|
||||
}" data-value="${encodeCardValue(value)}" href="${
|
||||
}" data-value="${encodeCardValue(
|
||||
value,
|
||||
)}"><a target="_blank" href="${
|
||||
value.url
|
||||
}" style="word-wrap: break-word;color: #096DD9;touch-action: manipulation;background-color: rgba(0,0,0,0);text-decoration: none;outline: none;cursor: pointer;transition: color .3s;"><span style="font-size: 14px;">\ud83d\udcce</span>${
|
||||
value.name
|
||||
}</a>`;
|
||||
}</a></span>`;
|
||||
node.empty();
|
||||
node.replaceWith($(html));
|
||||
} else node.remove();
|
||||
|
|
|
@ -636,13 +636,17 @@ class TableComponent extends Card<TableValue> implements TableInterface {
|
|||
if (this.wrapper) {
|
||||
// 重新绘制列头部和行头部
|
||||
const colsHeader = this.wrapper.find(Template.COLS_HEADER_CLASS);
|
||||
if (value?.cols) {
|
||||
colsHeader.replaceWith(
|
||||
$(this.template.renderColsHeader(value?.cols || 0)),
|
||||
);
|
||||
}
|
||||
const rowsHeader = this.wrapper.find(Template.ROWS_HEADER_CLASS);
|
||||
if (value?.rows) {
|
||||
rowsHeader.replaceWith(
|
||||
$(this.template.renderRowsHeader(value?.rows || 0)),
|
||||
);
|
||||
}
|
||||
setTimeout(() => {
|
||||
// 找到所有可编辑节点,对没有 contenteditable 属性的节点添加contenteditable一下
|
||||
this.wrapper?.find(EDITABLE_SELECTOR).each((editableNode) => {
|
||||
|
|
|
@ -1090,6 +1090,7 @@ class TableSelection extends EventEmitter2 implements TableSelectionInterface {
|
|||
top += rect.top - (vRect?.top || 0) - 13;
|
||||
left += rect.left - (vRect?.left || 0);
|
||||
}
|
||||
|
||||
const sLeft =
|
||||
removeUnit(
|
||||
this.table.wrapper?.find('.data-scrollbar')?.css('left') || '0',
|
||||
|
|
|
@ -190,9 +190,9 @@ class Template implements TemplateInterface {
|
|||
noBorder === true ? " data-table-no-border='true'" : ''
|
||||
} ${DATA_TRANSIENT_ATTRIBUTES}="class">${colgroup}${trs}</table>`;
|
||||
|
||||
return `<div class="${TABLE_WRAPPER_CLASS_NAME} ${
|
||||
return `<div ${DATA_TRANSIENT_ATTRIBUTES}="*" class="${TABLE_WRAPPER_CLASS_NAME} ${
|
||||
overflow !== false ? TABLE_OVERFLOW_CLASS_NAME : ''
|
||||
}" ${DATA_TRANSIENT_ATTRIBUTES}="*">${tableHeader}<div class="${VIEWPORT}">${this.renderColsHeader(
|
||||
}" ${DATA_TRANSIENT_ATTRIBUTES}="*">${tableHeader}<div ${DATA_TRANSIENT_ATTRIBUTES}="*" class="${VIEWPORT}">${this.renderColsHeader(
|
||||
cols,
|
||||
)}${table}${placeholder}${tableHighlight}</div>${this.renderRowsHeader(
|
||||
rows,
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
{
|
||||
"id": "kAoP518hzaPYD9Mx9z",
|
||||
"title": "dsfdf",
|
||||
"status": "true",
|
||||
"status": "false",
|
||||
"children": [
|
||||
{
|
||||
"id": 6,
|
||||
|
@ -76,5 +76,44 @@
|
|||
"createdAt": 1639571978834
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "C7jyDdNy4pWlNZDDVC",
|
||||
"title": "fffffffffffff",
|
||||
"status": "false",
|
||||
"children": [
|
||||
{
|
||||
"id": 7,
|
||||
"username": "G-2",
|
||||
"content": "dgfg",
|
||||
"createdAt": 1639653974175
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "VGAleZwQS7WgYfxX4u",
|
||||
"title": "ffffffffff",
|
||||
"status": "false",
|
||||
"children": [
|
||||
{
|
||||
"id": 8,
|
||||
"username": "G-2",
|
||||
"content": "dfg",
|
||||
"createdAt": 1639653998574
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "753UwqwMh6Rr8HIeKW",
|
||||
"title": "ghjhgj[card:status,ezXxq]ghjhj",
|
||||
"status": "false",
|
||||
"children": [
|
||||
{
|
||||
"id": 9,
|
||||
"username": "G-2",
|
||||
"content": "ghjhgj",
|
||||
"createdAt": 1639662957734
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue