feat(): 优化了一点性能问题
This commit is contained in:
parent
759ba6de57
commit
c1cf65a254
|
@ -144,24 +144,6 @@ Is it an engine
|
|||
|
||||
Accepted object: `EditorInterface`
|
||||
|
||||
### `getWindow`
|
||||
|
||||
Get the window object from the node
|
||||
|
||||
If window is undefined, it will try to get the window object from global['__amWindow']
|
||||
|
||||
```ts
|
||||
(node?: Node): Window & typeof globalThis
|
||||
```
|
||||
|
||||
### `getDocument`
|
||||
|
||||
Get the document object from the node
|
||||
|
||||
```ts
|
||||
getDocument(node?: Node): Document
|
||||
```
|
||||
|
||||
### `combinText`
|
||||
|
||||
Remove empty text nodes and connect adjacent text nodes
|
||||
|
|
|
@ -144,24 +144,6 @@
|
|||
|
||||
接受对象:`EditorInterface`
|
||||
|
||||
### `getWindow`
|
||||
|
||||
从节点中获取 window 对象
|
||||
|
||||
如果 window 是 undefined 会尝试从 global['__amWindow'] 中获取 window 对象
|
||||
|
||||
```ts
|
||||
(node?: Node): Window & typeof globalThis
|
||||
```
|
||||
|
||||
### `getDocument`
|
||||
|
||||
从节点中获取 document 对象
|
||||
|
||||
```ts
|
||||
getDocument(node?: Node): Document
|
||||
```
|
||||
|
||||
### `combinText`
|
||||
|
||||
移除空的文本节点,并连接相邻的文本节点
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
span[data-comment-preview], span[data-comment-id] {
|
||||
display: inline-block;
|
||||
text-indent: 0;
|
||||
}
|
||||
|
||||
[data-comment-preview]::before, [data-comment-id]::before{
|
||||
|
|
|
@ -117,12 +117,12 @@ const EditorComponent: React.FC<EditorProps> = ({
|
|||
// 获取编辑器的值
|
||||
console.log(`value ${trigger} update:`, value);
|
||||
// 获取当前所有at插件中的名单
|
||||
console.log(
|
||||
'mention:',
|
||||
engine.current?.command.executeMethod('mention', 'getList'),
|
||||
);
|
||||
// console.log(
|
||||
// 'mention:',
|
||||
// engine.current?.command.executeMethod('mention', 'getList'),
|
||||
// );
|
||||
// 获取编辑器的html
|
||||
console.log('html:', engine.current?.getHtml());
|
||||
//console.log('html:', engine.current?.getHtml());
|
||||
},
|
||||
[loading, autoSave, props.onChange],
|
||||
),
|
||||
|
@ -132,13 +132,11 @@ const EditorComponent: React.FC<EditorProps> = ({
|
|||
const userSave = useCallback(() => {
|
||||
if (!engine.current) return;
|
||||
//获取异步的值,有些组件可能还在处理中,比如正在上传
|
||||
console.time('value-async');
|
||||
engine.current
|
||||
.getValueAsync(false, (pluginName, card) => {
|
||||
console.log(`${pluginName} 正在等待...`, card?.getValue());
|
||||
})
|
||||
.then((value) => {
|
||||
console.timeEnd('value-async');
|
||||
setValue(value);
|
||||
save();
|
||||
})
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
PluginEntry,
|
||||
} from '../types';
|
||||
import { BlockInterface, BlockModelInterface } from '../types/block';
|
||||
import { getDocument, getWindow, isEngine } from '../utils';
|
||||
import { getDocument, isEngine } from '../utils';
|
||||
import { Backspace, Enter } from './typing';
|
||||
import { $ } from '../node';
|
||||
import { isBlockPlugin } from '../plugin';
|
||||
|
@ -865,13 +865,13 @@ class Block implements BlockModelInterface {
|
|||
let startNode = sc;
|
||||
let endNode = ec;
|
||||
|
||||
if (sc.nodeType === getWindow().Node.ELEMENT_NODE) {
|
||||
if (sc.nodeType === Node.ELEMENT_NODE) {
|
||||
if (sc.childNodes[so]) {
|
||||
startNode = sc.childNodes[so] || sc;
|
||||
}
|
||||
}
|
||||
|
||||
if (ec.nodeType === getWindow().Node.ELEMENT_NODE) {
|
||||
if (ec.nodeType === Node.ELEMENT_NODE) {
|
||||
if (eo > 0 && ec.childNodes[eo - 1]) {
|
||||
endNode = ec.childNodes[eo - 1] || sc;
|
||||
}
|
||||
|
@ -1232,13 +1232,15 @@ class Block implements BlockModelInterface {
|
|||
* @param block 节点
|
||||
*/
|
||||
brToBlock(block: NodeInterface) {
|
||||
const first = block.first();
|
||||
// 没有子节点
|
||||
if (!block.first()) {
|
||||
if (!first) {
|
||||
return;
|
||||
}
|
||||
const children = block.children();
|
||||
// 只有一个节点
|
||||
if (block.children().length === 1) {
|
||||
const node = block.first();
|
||||
if (children.length === 1) {
|
||||
const node = first;
|
||||
//\n换成 br
|
||||
if (node && node.isText() && /^\n+$/g.test(node.text())) {
|
||||
this.editor.node.replace(node, $('<br />'));
|
||||
|
@ -1248,16 +1250,18 @@ class Block implements BlockModelInterface {
|
|||
if ('li' === block.name) return;
|
||||
// 只有一个节点(有光标标记节点)
|
||||
if (
|
||||
(block.children().length === 2 &&
|
||||
block.first()?.attributes(DATA_ELEMENT) === CURSOR) ||
|
||||
(children.length === 2 &&
|
||||
first?.attributes(DATA_ELEMENT) === CURSOR) ||
|
||||
block.last()?.attributes(DATA_ELEMENT) === CURSOR
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (block.find('br').length === 0) return;
|
||||
|
||||
let container;
|
||||
let prevContainer;
|
||||
let node = block.first();
|
||||
let node: NodeInterface | null = first;
|
||||
while (node) {
|
||||
const next = node.next();
|
||||
if (!container || node.name === 'br') {
|
||||
|
@ -1337,7 +1341,7 @@ class Block implements BlockModelInterface {
|
|||
cloneRange.enlargeFromTextNode();
|
||||
if (
|
||||
this.isLastOffset(range, 'end') ||
|
||||
(cloneRange.endNode.type === getWindow().Node.ELEMENT_NODE &&
|
||||
(cloneRange.endNode.type === Node.ELEMENT_NODE &&
|
||||
block.children().length > 0 &&
|
||||
cloneRange.endContainer.childNodes[cloneRange.endOffset] ===
|
||||
block.last()?.get() &&
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { EngineInterface, RangeInterface } from '../../types';
|
||||
import { getWindow } from '../../utils';
|
||||
|
||||
class Backspace {
|
||||
private engine: EngineInterface;
|
||||
|
@ -43,16 +42,14 @@ class Backspace {
|
|||
.shrinkToElementNode()
|
||||
.shrinkToTextNode();
|
||||
if (
|
||||
cloneRange.startContainer.nodeType ===
|
||||
getWindow().Node.TEXT_NODE &&
|
||||
cloneRange.startContainer.nodeType === Node.TEXT_NODE &&
|
||||
(function (range: RangeInterface) {
|
||||
const { commonAncestorContainer } = range;
|
||||
if (
|
||||
range.collapsed &&
|
||||
1 === range.startOffset &&
|
||||
range.startContainer === commonAncestorContainer &&
|
||||
commonAncestorContainer.nodeType ===
|
||||
getWindow().Node.TEXT_NODE
|
||||
commonAncestorContainer.nodeType === Node.TEXT_NODE
|
||||
) {
|
||||
range = range.cloneRange();
|
||||
if (
|
||||
|
|
|
@ -176,14 +176,11 @@ abstract class CardEntry<T extends CardValue = {}> implements CardInterface {
|
|||
const body = this.root.first() || $([]);
|
||||
if (key === 'body' || body.length === 0) return body;
|
||||
const children = body.children();
|
||||
if (['center', 'left', 'right'].includes(key))
|
||||
return (
|
||||
children
|
||||
.toArray()
|
||||
.find(
|
||||
(child) => child.attributes(CARD_ELEMENT_KEY) === key,
|
||||
) || $([])
|
||||
);
|
||||
const index = ['center', 'left', 'right'].indexOf(key);
|
||||
if (index > -1) {
|
||||
const child = children.eq(index);
|
||||
if (child?.attributes(CARD_ELEMENT_KEY) === key) return child;
|
||||
}
|
||||
const tag = this.type === CardType.BLOCK ? 'div' : 'span';
|
||||
return this.find(`${tag}[${CARD_ELEMENT_KEY}=${key}]`);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import { RangeInterface, RangePath } from '../types/range';
|
|||
import ChangeEvent from './event';
|
||||
import Parser from '../parser';
|
||||
import { ANCHOR_SELECTOR, CURSOR_SELECTOR, FOCUS_SELECTOR } from '../constants';
|
||||
import { combinText, getWindow } from '../utils';
|
||||
import { combinText } from '../utils';
|
||||
import { TRIGGER_CARD_ID } from '../constants/card';
|
||||
import { DATA_ID, EDITABLE_SELECTOR, UI_SELECTOR } from '../constants/root';
|
||||
import { SelectionInterface } from '../types/selection';
|
||||
|
@ -26,7 +26,7 @@ class ChangeModel implements ChangeInterface {
|
|||
valueCached: string | null = null;
|
||||
onChange: (value: string, trigger: 'remote' | 'local' | 'both') => void;
|
||||
onRealtimeChange: (trigger: 'remote' | 'local') => void;
|
||||
onSelect: () => void;
|
||||
onSelect: (range?: RangeInterface) => void;
|
||||
onSetValue: () => void;
|
||||
rangePathBeforeCommand?: { start: RangePath; end: RangePath };
|
||||
marks: Array<NodeInterface> = [];
|
||||
|
@ -43,14 +43,23 @@ class ChangeModel implements ChangeInterface {
|
|||
|
||||
this.onChange = this.options.onChange || function () {};
|
||||
this.onRealtimeChange = this.options.onRealtimeChange || function () {};
|
||||
this.onSelect = this.options.onSelect || function () {};
|
||||
this.onSetValue = this.options.onSetValue || function () {};
|
||||
this.range = new ChangeRange(engine, {
|
||||
onSelect: (range) => {
|
||||
|
||||
this.onSelect = (range?: RangeInterface) => {
|
||||
if (selectTimeout) clearTimeout(selectTimeout);
|
||||
selectTimeout = setTimeout(() => {
|
||||
const { mark, block, inline } = engine;
|
||||
range = range || this.range.get();
|
||||
this.marks = mark.findMarks(range);
|
||||
this.blocks = block.findBlocks(range);
|
||||
this.inlines = inline.findInlines(range);
|
||||
if (this.options.onSelect) this.options.onSelect();
|
||||
}, 50);
|
||||
};
|
||||
this.onSetValue = this.options.onSetValue || function () {};
|
||||
let selectTimeout: null | NodeJS.Timeout = null;
|
||||
this.range = new ChangeRange(engine, {
|
||||
onSelect: (range) => {
|
||||
this.onSelect(range);
|
||||
},
|
||||
});
|
||||
this.#nativeEvent = new NativeEvent(engine);
|
||||
|
@ -271,8 +280,8 @@ class ChangeModel implements ChangeInterface {
|
|||
const { container, node, schema } = this.engine;
|
||||
const tags = schema.getAllowInTags();
|
||||
return (
|
||||
node.isEmptyWithTrim(container) &&
|
||||
container.children().length === 1 &&
|
||||
node.isEmptyWithTrim(container) &&
|
||||
!container.allChildren().some((child) => tags.includes(child.name))
|
||||
);
|
||||
}
|
||||
|
@ -392,7 +401,9 @@ class ChangeModel implements ChangeInterface {
|
|||
.enlargeToElementNode(true)
|
||||
.collapse(false);
|
||||
const startNode =
|
||||
cloneRange.startContainer.childNodes[range.startOffset - 1];
|
||||
cloneRange.startContainer.childNodes[
|
||||
range.startOffset === 0 ? 0 : range.startOffset - 1
|
||||
];
|
||||
const endNode = cloneRange.startContainer.childNodes[range.startOffset];
|
||||
|
||||
if (childNodes.length !== 0) {
|
||||
|
@ -633,8 +644,7 @@ class ChangeModel implements ChangeInterface {
|
|||
if (!isEmptyNode && startNode.length > 0 && startNode.inEditor()) {
|
||||
if (
|
||||
startNode[0].childNodes.length === 1 &&
|
||||
startNode[0].firstChild?.nodeType ===
|
||||
getWindow().Node.ELEMENT_NODE &&
|
||||
startNode[0].firstChild?.nodeType === Node.ELEMENT_NODE &&
|
||||
nodeApi.isCustomize(startNode) &&
|
||||
startNode.first()?.isCard()
|
||||
)
|
||||
|
|
|
@ -242,8 +242,72 @@ class NativeEvent {
|
|||
}
|
||||
}
|
||||
|
||||
handleSelectionChange() {
|
||||
const { change, container, card } = this.engine;
|
||||
if (change.isComposing()) return;
|
||||
const { window } = container;
|
||||
const selection = window?.getSelection();
|
||||
|
||||
if (selection && selection.anchorNode) {
|
||||
const range = Range.from(this.engine, selection)!;
|
||||
// 判断当前光标是否包含卡片或者在卡片内部
|
||||
let containsCard =
|
||||
range.containsCard() ||
|
||||
(range.commonAncestorNode.closest(CARD_SELECTOR).length > 0 &&
|
||||
range.startNode.closest(
|
||||
`${CARD_LEFT_SELECTOR},${CARD_RIGHT_SELECTOR},${UI_SELECTOR}`,
|
||||
).length === 0);
|
||||
|
||||
let isSingle = range.collapsed;
|
||||
if (!isSingle) {
|
||||
const { startNode, endNode, startOffset, endOffset } = range;
|
||||
const startElement =
|
||||
startNode.isElement() && !startNode.isCard()
|
||||
? startNode.children().eq(startOffset)
|
||||
: startNode;
|
||||
const endElement =
|
||||
endNode.isElement() && !endNode.isCard()
|
||||
? endNode.children().eq(endOffset - 1)
|
||||
: endNode;
|
||||
if (
|
||||
startElement &&
|
||||
endElement &&
|
||||
startElement.isCard() &&
|
||||
startElement.equal(endElement)
|
||||
) {
|
||||
isSingle = true;
|
||||
}
|
||||
}
|
||||
card.each((card) => {
|
||||
const center = card.getCenter();
|
||||
if (center && center.length > 0) {
|
||||
let isSelect = selection.containsNode(center[0]);
|
||||
if (!isSelect && containsCard && selection.focusNode) {
|
||||
const focusCard = this.engine.card.find(
|
||||
selection.focusNode,
|
||||
);
|
||||
if (focusCard) {
|
||||
isSingle =
|
||||
!selection.anchorNode ||
|
||||
focusCard.root.contains(selection.anchorNode);
|
||||
if (isSingle && card.root.equal(focusCard.root)) {
|
||||
isSelect = true;
|
||||
}
|
||||
}
|
||||
// 找到一次其它的就不需要再去比对了
|
||||
if (isSelect && isSingle) containsCard = false;
|
||||
}
|
||||
const { autoSelected } = card.constructor as CardEntry;
|
||||
card.select(
|
||||
isSelect && (!isSingle || autoSelected !== false),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
const { change, container, card, clipboard } = this.engine;
|
||||
const { change, card, clipboard } = this.engine;
|
||||
|
||||
change.event.onInput((event: InputEvent) => {
|
||||
const range = change.range.get();
|
||||
|
@ -252,75 +316,13 @@ class NativeEvent {
|
|||
change.onSelect();
|
||||
change.change();
|
||||
});
|
||||
|
||||
let selectionChangeTimeout: NodeJS.Timeout | undefined = undefined;
|
||||
change.event.onDocument('selectionchange', () => {
|
||||
if (change.isComposing()) return;
|
||||
const { window } = container;
|
||||
const selection = window?.getSelection();
|
||||
|
||||
if (selection && selection.anchorNode) {
|
||||
const range = Range.from(this.engine, selection)!;
|
||||
// 判断当前光标是否包含卡片或者在卡片内部
|
||||
let containsCard =
|
||||
range.containsCard() ||
|
||||
(range.commonAncestorNode.closest(CARD_SELECTOR).length >
|
||||
0 &&
|
||||
range.startNode.closest(
|
||||
`${CARD_LEFT_SELECTOR},${CARD_RIGHT_SELECTOR},${UI_SELECTOR}`,
|
||||
).length === 0);
|
||||
|
||||
let isSingle = range.collapsed;
|
||||
if (!isSingle) {
|
||||
const { startNode, endNode, startOffset, endOffset } =
|
||||
range;
|
||||
const startElement =
|
||||
startNode.isElement() && !startNode.isCard()
|
||||
? startNode.children().eq(startOffset)
|
||||
: startNode;
|
||||
const endElement =
|
||||
endNode.isElement() && !endNode.isCard()
|
||||
? endNode.children().eq(endOffset - 1)
|
||||
: endNode;
|
||||
if (
|
||||
startElement &&
|
||||
endElement &&
|
||||
startElement.isCard() &&
|
||||
startElement.equal(endElement)
|
||||
) {
|
||||
isSingle = true;
|
||||
}
|
||||
}
|
||||
card.each((card) => {
|
||||
const center = card.getCenter();
|
||||
if (center && center.length > 0) {
|
||||
let isSelect = selection.containsNode(center[0]);
|
||||
if (!isSelect && containsCard && selection.focusNode) {
|
||||
const focusCard = this.engine.card.find(
|
||||
selection.focusNode,
|
||||
);
|
||||
if (focusCard) {
|
||||
isSingle =
|
||||
!selection.anchorNode ||
|
||||
focusCard.root.contains(
|
||||
selection.anchorNode,
|
||||
);
|
||||
if (
|
||||
isSingle &&
|
||||
card.root.equal(focusCard.root)
|
||||
) {
|
||||
isSelect = true;
|
||||
}
|
||||
}
|
||||
// 找到一次其它的就不需要再去比对了
|
||||
if (isSelect && isSingle) containsCard = false;
|
||||
}
|
||||
const { autoSelected } = card.constructor as CardEntry;
|
||||
card.select(
|
||||
isSelect && (!isSingle || autoSelected !== false),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (selectionChangeTimeout) clearTimeout(selectionChangeTimeout);
|
||||
selectionChangeTimeout = setTimeout(
|
||||
() => this.handleSelectionChange(),
|
||||
50,
|
||||
);
|
||||
});
|
||||
change.event.onSelect((event: any) => {
|
||||
const range = change.range.get();
|
||||
|
|
|
@ -4,6 +4,7 @@ import { READY_CARD_KEY, READY_CARD_SELECTOR } from '../constants/card';
|
|||
import Parser from '../parser';
|
||||
import { EngineInterface } from '../types/engine';
|
||||
import { $ } from '../node';
|
||||
import { DATA_ID } from '../constants';
|
||||
|
||||
export default class Paste {
|
||||
protected source: string;
|
||||
|
@ -26,77 +27,27 @@ export default class Paste {
|
|||
}
|
||||
|
||||
getDefaultStyle() {
|
||||
const defaultStyle = [
|
||||
{
|
||||
color: tinycolor2(this.engine.container.css('color')).toHex(),
|
||||
},
|
||||
{
|
||||
'background-color': tinycolor2('white').toHex(),
|
||||
},
|
||||
{
|
||||
'font-size': this.engine.container.css('font-size'),
|
||||
},
|
||||
];
|
||||
const defaultStyle = {
|
||||
color: tinycolor2(this.engine.container.css('color')).toHex(),
|
||||
'background-color': tinycolor2('white').toHex(),
|
||||
'font-size': this.engine.container.css('font-size'),
|
||||
};
|
||||
return defaultStyle;
|
||||
}
|
||||
|
||||
elementNormalize(fragment: DocumentFragment) {
|
||||
const defaultStyle = this.getDefaultStyle();
|
||||
const defautlStyleKeys = Object.keys(defaultStyle);
|
||||
const { inline } = this.engine;
|
||||
const nodeApi = this.engine.node;
|
||||
|
||||
$(fragment).traverse((node) => {
|
||||
// 跳过Card
|
||||
if (node.isCard() || fragment === node.fragment) {
|
||||
let parent = node.parent();
|
||||
// 跳过已被删除的节点
|
||||
if (!parent || node.isCard() || node.fragment === fragment) {
|
||||
return;
|
||||
}
|
||||
// 删除与默认样式一样的样式
|
||||
if (node.isElement()) {
|
||||
defaultStyle.forEach((style) => {
|
||||
const key = Object.keys(style)[0];
|
||||
const defaultValue = style[key];
|
||||
let value = node.get<HTMLElement>()?.style[key];
|
||||
if (value) {
|
||||
if (/color$/.test(key)) {
|
||||
value = tinycolor2(value).toHex();
|
||||
}
|
||||
if (value === defaultValue) {
|
||||
node.css(key, '');
|
||||
}
|
||||
}
|
||||
});
|
||||
//处理后如果不是一个有效的节点就移除包裹
|
||||
if (!this.schema.getType(node)) nodeApi.unwrap(node);
|
||||
|
||||
nodeApi.removeMinusStyle(node, 'text-indent');
|
||||
if (nodeApi.isList(node)) {
|
||||
node.css('padding-left', '');
|
||||
}
|
||||
// 删除空 style 属性
|
||||
if (!node.attributes('style')) {
|
||||
node.removeAttributes('style');
|
||||
}
|
||||
// 删除空 span
|
||||
let first: NodeInterface | null = null;
|
||||
if (
|
||||
node.name === 'span' &&
|
||||
Object.keys(node.attributes()).length === 0 &&
|
||||
Object.keys(node.css()).length === 0 &&
|
||||
(node.text().trim() === '' ||
|
||||
(first =
|
||||
node.first() &&
|
||||
first &&
|
||||
(nodeApi.isMark(first, this.schema) ||
|
||||
nodeApi.isBlock(first, this.schema))))
|
||||
) {
|
||||
nodeApi.unwrap(node);
|
||||
return;
|
||||
}
|
||||
|
||||
// br 换行改成正常段落
|
||||
if (nodeApi.isBlock(node, this.schema)) {
|
||||
this.engine.block.brToBlock(node);
|
||||
}
|
||||
} else {
|
||||
if (node.isText()) {
|
||||
let text = node.text();
|
||||
if (/\s/.test(text)) {
|
||||
text = text.replace(/\s/g, ' ');
|
||||
|
@ -120,14 +71,41 @@ export default class Paste {
|
|||
node.text(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
$(fragment).traverse((node) => {
|
||||
let parent = node.parent();
|
||||
// 跳过已被删除的节点
|
||||
if (!parent || node.fragment === fragment) {
|
||||
return;
|
||||
}
|
||||
const styles = node.css();
|
||||
defautlStyleKeys.forEach((key) => {
|
||||
const value = styles[key];
|
||||
if (!value) return;
|
||||
if (value === defaultStyle[key]) {
|
||||
node.css(key, '');
|
||||
}
|
||||
});
|
||||
//处理后如果不是一个有效的节点就移除包裹
|
||||
if (!this.schema.getType(node)) {
|
||||
nodeApi.unwrap(node);
|
||||
return;
|
||||
}
|
||||
nodeApi.removeMinusStyle(node, 'text-indent');
|
||||
if (nodeApi.isList(node)) {
|
||||
node.css('padding-left', '');
|
||||
}
|
||||
// 删除空 style 属性
|
||||
if (node.attributes('style').trim() === '') {
|
||||
node.removeAttributes('style');
|
||||
}
|
||||
|
||||
// br 换行改成正常段落
|
||||
if (nodeApi.isBlock(node, this.schema)) {
|
||||
this.engine.block.brToBlock(node);
|
||||
}
|
||||
// 删除空 span
|
||||
while (node.name === 'span' && nodeApi.isEmpty(node)) {
|
||||
parent = node.parent();
|
||||
node.remove();
|
||||
if (!parent) return;
|
||||
node = parent;
|
||||
}
|
||||
// 删除 google docs 根节点
|
||||
// <b style="font-weight:flat;" id="docs-internal-guid-e0280780-7fff-85c2-f58a-6e615d93f1f2">
|
||||
if (/^docs-internal-guid-/.test(node.attributes('id'))) {
|
||||
|
@ -138,12 +116,15 @@ export default class Paste {
|
|||
if (node.attributes(READY_CARD_KEY)) {
|
||||
return;
|
||||
}
|
||||
const nodeIsBlock = nodeApi.isBlock(node, this.schema);
|
||||
const nodeIsVoid = nodeApi.isVoid(node, this.schema);
|
||||
let parentIsBlock = nodeApi.isBlock(parent, this.schema);
|
||||
// 删除零高度的空行
|
||||
if (
|
||||
nodeApi.isBlock(node, this.schema) &&
|
||||
nodeIsBlock &&
|
||||
node.attributes('data-type') !== 'p' &&
|
||||
!nodeApi.isVoid(node, this.schema) &&
|
||||
!nodeApi.isBlock(parent, this.schema) &&
|
||||
!nodeIsVoid &&
|
||||
!parentIsBlock &&
|
||||
//!node.isSolid() &&
|
||||
nodeApi.html(node) === ''
|
||||
) {
|
||||
|
@ -154,19 +135,24 @@ export default class Paste {
|
|||
if (node.attributes('data-type') === 'p') {
|
||||
node.removeAttributes('data-type');
|
||||
}
|
||||
if (nodeApi.isBlock(node, this.schema) && parent?.name === 'p') {
|
||||
nodeApi.unwrap(node);
|
||||
if (nodeIsBlock && parent?.name === 'p') {
|
||||
nodeApi.unwrap(parent);
|
||||
parent = node.parent();
|
||||
if (parent?.fragment === fragment) parent = undefined;
|
||||
parentIsBlock = parent
|
||||
? nodeApi.isBlock(parent, this.schema)
|
||||
: false;
|
||||
}
|
||||
const parentIsList = parent ? nodeApi.isList(parent) : false;
|
||||
// 补齐 ul 或 ol
|
||||
if (node.name === 'li' && parent && !nodeApi.isList(parent)) {
|
||||
if (node.name === 'li' && parent && !parentIsList) {
|
||||
const ul = $('<ul />');
|
||||
node.before(ul);
|
||||
ul.append(node);
|
||||
return;
|
||||
}
|
||||
// 补齐 li
|
||||
if (node.name !== 'li' && parent && nodeApi.isList(parent)) {
|
||||
if (node.name !== 'li' && parentIsList) {
|
||||
const li = $('<li />');
|
||||
node.before(li);
|
||||
li.append(node);
|
||||
|
@ -202,14 +188,14 @@ export default class Paste {
|
|||
return;
|
||||
}
|
||||
// p 改成 li
|
||||
if (node.name === 'p' && parent && nodeApi.isList(parent)) {
|
||||
if (node.name === 'p' && parentIsList) {
|
||||
nodeApi.replace(node, $('<li />'));
|
||||
return;
|
||||
}
|
||||
// 处理空 Block
|
||||
if (
|
||||
nodeApi.isBlock(node, this.schema) &&
|
||||
!nodeApi.isVoid(node, this.schema) &&
|
||||
nodeIsBlock &&
|
||||
!nodeIsVoid &&
|
||||
nodeApi.html(node).trim() === ''
|
||||
) {
|
||||
// <p></p> to <p><br /></p>
|
||||
|
@ -221,7 +207,7 @@ export default class Paste {
|
|||
}
|
||||
}
|
||||
// <li><p>foo</p></li>
|
||||
if (nodeApi.isBlock(node, this.schema) && parent?.name === 'li') {
|
||||
if (nodeIsBlock && parent?.name === 'li') {
|
||||
// <li><p><br /></p></li>
|
||||
if (
|
||||
node.children().length === 1 &&
|
||||
|
@ -235,9 +221,10 @@ export default class Paste {
|
|||
return;
|
||||
}
|
||||
if (
|
||||
!nodeIsBlock &&
|
||||
nodeApi.isInline(node) &&
|
||||
!node.isCard() &&
|
||||
!nodeApi.isVoid(node, this.schema)
|
||||
!nodeIsVoid
|
||||
) {
|
||||
const isVoid = node
|
||||
.allChildren()
|
||||
|
@ -334,7 +321,7 @@ export default class Paste {
|
|||
|
||||
$(fragment).traverse((node) => {
|
||||
if (node.fragment === fragment) return;
|
||||
if (node.length > 0 && node.parent())
|
||||
if (node.length > 0 && node[0].parentNode)
|
||||
this.engine.trigger('paste:each', node);
|
||||
// 删除非block节点的换行 \r\n\r\n<span
|
||||
if (node.isText()) {
|
||||
|
@ -348,13 +335,6 @@ export default class Paste {
|
|||
)
|
||||
node.remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
$(fragment).traverse((node) => {
|
||||
if (node.fragment === fragment) return;
|
||||
this.engine.trigger('paste:each-after', node);
|
||||
if (node.isText()) {
|
||||
const text = node.text();
|
||||
const match = /((\n)+)/.exec(text);
|
||||
if (match && !text.endsWith('\n') && !text.startsWith('\n')) {
|
||||
const nextReg = node.get<Text>()!.splitText(match.index);
|
||||
|
@ -364,13 +344,14 @@ export default class Paste {
|
|||
}
|
||||
}
|
||||
// 删除包含Card的 pre 标签
|
||||
if (
|
||||
else if (
|
||||
node.name === 'pre' &&
|
||||
node.find(READY_CARD_SELECTOR).length > 0
|
||||
) {
|
||||
nodeApi.unwrap(node);
|
||||
}
|
||||
});
|
||||
this.engine.trigger('paste:each-after', $(fragment));
|
||||
|
||||
const node = nodeApi.normalize($(fragment));
|
||||
if (node.fragment) fragment = node.fragment;
|
||||
|
@ -391,14 +372,12 @@ export default class Paste {
|
|||
nodeApi.unwrap(first);
|
||||
}
|
||||
fragmentNode = $(fragment);
|
||||
fragmentNode.each((_, index) => {
|
||||
const children = fragmentNode.eq(index);
|
||||
children?.find('ul,ol').each((_, index) => {
|
||||
const child = children.eq(index);
|
||||
if (child && nodeApi.isList(child)) {
|
||||
this.engine.list.addStart(child);
|
||||
}
|
||||
});
|
||||
const children = fragmentNode.find('ul,ol');
|
||||
children.each((_, index) => {
|
||||
const child = children.eq(index);
|
||||
if (child && nodeApi.isList(child)) {
|
||||
this.engine.list.addStart(child);
|
||||
}
|
||||
});
|
||||
this.engine.nodeId.generateAll($(fragment), true);
|
||||
return fragment;
|
||||
|
|
|
@ -244,9 +244,14 @@ class ChangeRange implements ChangeRangeInterface {
|
|||
) {
|
||||
endNode.append('<br />');
|
||||
}
|
||||
const startChildren = startNode.children();
|
||||
// 列表节点没有子节点
|
||||
if (node.isList(startNode) && startNode.children().length === 0) {
|
||||
if (
|
||||
node.isList(startNode) &&
|
||||
(startChildren.length === 0 || startChildren[0].nodeName === 'BR')
|
||||
) {
|
||||
const newNode = $('<p><br /></p>');
|
||||
this.engine.nodeId.create(newNode);
|
||||
startNode.before(newNode);
|
||||
startNode.remove();
|
||||
startNode = newNode;
|
||||
|
|
|
@ -3,7 +3,7 @@ import Parser from './parser';
|
|||
import { ClipboardInterface } from './types/clipboard';
|
||||
import { EditorInterface, EngineInterface } from './types/engine';
|
||||
import { RangeInterface } from './types/range';
|
||||
import { getWindow, isEngine, isSafari } from './utils';
|
||||
import { isEngine, isSafari } from './utils';
|
||||
import { $ } from './node';
|
||||
import Range from './range';
|
||||
import { NodeInterface } from './types';
|
||||
|
@ -145,7 +145,7 @@ export default class Clipboard implements ClipboardInterface {
|
|||
root.name === '#text' ? [document.createElement('span')] : [];
|
||||
card = root.closest(`[${CARD_KEY}]`, (node) => {
|
||||
if ($(node).isEditable()) return;
|
||||
if (node.nodeType === getWindow().Node.ELEMENT_NODE) {
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
const display = window
|
||||
.getComputedStyle(node as Element)
|
||||
.getPropertyValue('display');
|
||||
|
@ -170,60 +170,57 @@ export default class Clipboard implements ClipboardInterface {
|
|||
} else {
|
||||
// 修复自定义列表选择范围
|
||||
let customizeStartItem: NodeInterface | undefined;
|
||||
if (range.startNode.isText()) {
|
||||
const li = range.startNode.closest('li');
|
||||
const li = range.startNode.closest('li');
|
||||
|
||||
if (li && node.isCustomize(li)) {
|
||||
const endLi = range.endNode.closest('li');
|
||||
if (
|
||||
!li.equal(endLi) ||
|
||||
(list.isLast(range) && list.isFirst(range))
|
||||
) {
|
||||
if (range.startOffset === 0) {
|
||||
const ul = li.parent();
|
||||
const index = li.getIndex();
|
||||
if (ul)
|
||||
range.setStart(ul, index < 0 ? 0 : index);
|
||||
} else {
|
||||
const ul = li.parent();
|
||||
// 选在列表项靠后的节点,把剩余节点拼接成完成的列表项
|
||||
const selection = range.createSelection();
|
||||
const rightNode = selection.getNode(
|
||||
li,
|
||||
'center',
|
||||
true,
|
||||
);
|
||||
selection.anchor?.remove();
|
||||
selection.focus?.remove();
|
||||
if (isEngine(this.editor))
|
||||
this.editor.change.combinText();
|
||||
if (rightNode.length > 0) {
|
||||
let isRemove = false;
|
||||
rightNode.each((_, index) => {
|
||||
const item = rightNode.eq(index);
|
||||
if (!isRemove && item?.name === 'li') {
|
||||
isRemove = true;
|
||||
return;
|
||||
}
|
||||
if (isRemove) item?.remove();
|
||||
});
|
||||
const card = li.first();
|
||||
const component = card
|
||||
? this.editor.card.find(card)
|
||||
: undefined;
|
||||
if (component) {
|
||||
customizeStartItem = rightNode;
|
||||
this.editor.list.addCardToCustomize(
|
||||
customizeStartItem,
|
||||
component.name,
|
||||
component.getValue(),
|
||||
);
|
||||
if (ul)
|
||||
node.wrap(
|
||||
customizeStartItem,
|
||||
ul?.clone(),
|
||||
);
|
||||
if (li && node.isCustomize(li)) {
|
||||
const endLi = range.endNode.closest('li');
|
||||
if (
|
||||
!li.equal(endLi) ||
|
||||
(list.isLast(range) && list.isFirst(range))
|
||||
) {
|
||||
if (list.isFirst(range)) {
|
||||
const ul = li.parent();
|
||||
const index = li.getIndex();
|
||||
if (ul) range.setStart(ul, index < 0 ? 0 : index);
|
||||
} else {
|
||||
const ul = li.parent();
|
||||
// 选在列表项靠后的节点,把剩余节点拼接成完成的列表项
|
||||
const selection = range.createSelection();
|
||||
const rightNode = selection.getNode(
|
||||
li,
|
||||
'center',
|
||||
true,
|
||||
);
|
||||
selection.anchor?.remove();
|
||||
selection.focus?.remove();
|
||||
if (isEngine(this.editor))
|
||||
this.editor.change.combinText();
|
||||
if (rightNode.length > 0) {
|
||||
let isRemove = false;
|
||||
rightNode.each((_, index) => {
|
||||
const item = rightNode.eq(index);
|
||||
if (!isRemove && item?.name === 'li') {
|
||||
isRemove = true;
|
||||
return;
|
||||
}
|
||||
if (isRemove) item?.remove();
|
||||
});
|
||||
const card = li.first();
|
||||
const component = card
|
||||
? this.editor.card.find(card)
|
||||
: undefined;
|
||||
if (component) {
|
||||
customizeStartItem = rightNode;
|
||||
this.editor.list.addCardToCustomize(
|
||||
customizeStartItem,
|
||||
component.name,
|
||||
component.getValue(),
|
||||
);
|
||||
if (ul)
|
||||
node.wrap(
|
||||
customizeStartItem,
|
||||
ul?.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -235,15 +232,13 @@ export default class Clipboard implements ClipboardInterface {
|
|||
contents.prepend(customizeStartItem[0]);
|
||||
}
|
||||
const listMergeBlocks: NodeInterface[] = [];
|
||||
contents.childNodes.forEach((child) => {
|
||||
let childElement = $(child);
|
||||
if (childElement.name !== 'li') return;
|
||||
|
||||
contents.querySelectorAll('li').forEach((child) => {
|
||||
const childElement = $(child);
|
||||
const dataId = childElement.attributes(DATA_ID);
|
||||
if (!dataId) return;
|
||||
const curentElement = document.querySelector(
|
||||
`[${DATA_ID}=${dataId}]`,
|
||||
);
|
||||
const curentElement = this.editor.container
|
||||
.get<HTMLElement>()
|
||||
?.querySelector(`[${DATA_ID}=${dataId}]`);
|
||||
// 补充自定义列表丢失的卡片
|
||||
if (
|
||||
node.isCustomize(childElement) &&
|
||||
|
|
|
@ -9,21 +9,14 @@ import { HotkeyInterface } from './types/hotkey';
|
|||
class Hotkey implements HotkeyInterface {
|
||||
private engine: EngineInterface;
|
||||
private disabled: boolean = false;
|
||||
#matchTimeout?: NodeJS.Timeout;
|
||||
constructor(engine: EngineInterface) {
|
||||
this.engine = engine;
|
||||
//绑定事件
|
||||
this.engine.container.on('keydown', (event) => this.trigger(event));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理按键按下事件
|
||||
* @param e 事件
|
||||
*/
|
||||
trigger(e: KeyboardEvent) {
|
||||
//禁用快捷键不处理
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
match(e: KeyboardEvent) {
|
||||
//遍历插件
|
||||
Object.keys(this.engine.plugin.components).every((name) => {
|
||||
const plugin = this.engine.plugin.components[name];
|
||||
|
@ -81,6 +74,21 @@ class Hotkey implements HotkeyInterface {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理按键按下事件
|
||||
* @param e 事件
|
||||
*/
|
||||
trigger(e: KeyboardEvent) {
|
||||
//禁用快捷键不处理
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
if (this.#matchTimeout) {
|
||||
clearTimeout(this.#matchTimeout);
|
||||
}
|
||||
this.#matchTimeout = setTimeout(() => this.match(e), 50);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用快捷键
|
||||
*/
|
||||
|
|
|
@ -8,12 +8,13 @@ import { EditorInterface, EngineInterface } from '../types/engine';
|
|||
import { InlineModelInterface } from '../types/inline';
|
||||
import { NodeInterface } from '../types/node';
|
||||
import { RangeInterface } from '../types/range';
|
||||
import { getDocument, getWindow, isEngine } from '../utils';
|
||||
import { getDocument, isEngine } from '../utils';
|
||||
import { Backspace, Left, Right } from './typing';
|
||||
import { $ } from '../node';
|
||||
import { isNode } from '../node/utils';
|
||||
import { isInlinePlugin } from '../plugin/inline';
|
||||
import { isRangeInterface } from '../range';
|
||||
import { SchemaInterface } from 'src';
|
||||
|
||||
class Inline implements InlineModelInterface {
|
||||
private editor: EditorInterface;
|
||||
|
@ -197,7 +198,7 @@ class Inline implements InlineModelInterface {
|
|||
this.split(safeRange);
|
||||
let { commonAncestorNode } = safeRange;
|
||||
if (
|
||||
commonAncestorNode.type === getWindow().Node.TEXT_NODE ||
|
||||
commonAncestorNode.type === Node.TEXT_NODE ||
|
||||
node.isMark(commonAncestorNode)
|
||||
) {
|
||||
commonAncestorNode = commonAncestorNode.parent()!;
|
||||
|
@ -808,13 +809,13 @@ class Inline implements InlineModelInterface {
|
|||
let startNode = sc;
|
||||
let endNode = ec;
|
||||
|
||||
if (sc.nodeType === getWindow().Node.ELEMENT_NODE) {
|
||||
if (sc.nodeType === Node.ELEMENT_NODE) {
|
||||
if (sc.childNodes[so]) {
|
||||
startNode = sc.childNodes[so] || sc;
|
||||
}
|
||||
}
|
||||
|
||||
if (ec.nodeType === getWindow().Node.ELEMENT_NODE) {
|
||||
if (ec.nodeType === Node.ELEMENT_NODE) {
|
||||
if (eo > 0 && ec.childNodes[eo - 1]) {
|
||||
endNode = ec.childNodes[eo - 1] || sc;
|
||||
}
|
||||
|
@ -999,7 +1000,7 @@ class Inline implements InlineModelInterface {
|
|||
}
|
||||
}
|
||||
|
||||
flat(node: NodeInterface | RangeInterface) {
|
||||
flat(node: NodeInterface | RangeInterface, schema?: SchemaInterface) {
|
||||
if (isRangeInterface(node)) {
|
||||
const selection = node.shrinkToElementNode().createSelection();
|
||||
const inlines = this.findInlines(node);
|
||||
|
@ -1014,19 +1015,28 @@ class Inline implements InlineModelInterface {
|
|||
const nodeApi = this.editor.node;
|
||||
const markApi = this.editor.mark;
|
||||
//当前节点是 inline 节点,inline 节点不允许嵌套、不允许放入mark节点
|
||||
if (nodeApi.isInline(node) && node.name !== 'br') {
|
||||
if (nodeApi.isInline(node, schema) && node.name !== 'br') {
|
||||
const parentInline = this.closest(node);
|
||||
//不允许嵌套
|
||||
if (!parentInline.equal(node) && nodeApi.isInline(parentInline)) {
|
||||
if (
|
||||
!parentInline.equal(node) &&
|
||||
nodeApi.isInline(parentInline, schema)
|
||||
) {
|
||||
nodeApi.unwrap(node);
|
||||
}
|
||||
//不允许放入mark
|
||||
else {
|
||||
const parentMark = markApi.closest(node);
|
||||
if (!parentMark.equal(node) && nodeApi.isMark(parentMark)) {
|
||||
let parentMark: NodeInterface | undefined =
|
||||
markApi.closest(node);
|
||||
while (
|
||||
parentMark &&
|
||||
!parentMark.equal(node) &&
|
||||
nodeApi.isMark(parentMark, schema)
|
||||
) {
|
||||
const cloneMark = parentMark.clone();
|
||||
const inlineMark = node.clone();
|
||||
parentMark.children().each((markChild) => {
|
||||
const children = parentMark.children();
|
||||
children.each((markChild) => {
|
||||
// 零宽字符的文本跳过
|
||||
if (
|
||||
markChild.nodeType === 3 &&
|
||||
|
@ -1034,17 +1044,21 @@ class Inline implements InlineModelInterface {
|
|||
) {
|
||||
return;
|
||||
}
|
||||
if (node.equal(markChild)) {
|
||||
nodeApi.wrap(
|
||||
nodeApi.replace(node, cloneMark),
|
||||
if ((node as NodeInterface).equal(markChild)) {
|
||||
node = nodeApi.wrap(
|
||||
nodeApi.replace(
|
||||
node as NodeInterface,
|
||||
cloneMark,
|
||||
),
|
||||
inlineMark,
|
||||
);
|
||||
this.repairBoth(inlineMark);
|
||||
this.repairBoth(node);
|
||||
} else {
|
||||
nodeApi.wrap(markChild, cloneMark);
|
||||
}
|
||||
});
|
||||
nodeApi.unwrap(parentMark);
|
||||
parentMark = markApi.closest(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
RangeInterface,
|
||||
} from '../types';
|
||||
import { ListInterface, ListModelInterface } from '../types/list';
|
||||
import { getDocument, getWindow, isEngine, removeUnit } from '../utils';
|
||||
import { getDocument, isEngine, removeUnit } from '../utils';
|
||||
import { Enter, Backspace } from './typing';
|
||||
import { $ } from '../node';
|
||||
import { isNode } from '../node/utils';
|
||||
|
@ -716,12 +716,12 @@ class List implements ListModelInterface {
|
|||
return;
|
||||
}
|
||||
//文本
|
||||
if (child.type === getWindow().Node.TEXT_NODE) {
|
||||
if (child.type === Node.TEXT_NODE) {
|
||||
if (child.text() !== '') return;
|
||||
child = child.prev();
|
||||
}
|
||||
//节点
|
||||
else if (child.type === getWindow().Node.ELEMENT_NODE) {
|
||||
else if (child.type === Node.ELEMENT_NODE) {
|
||||
if (!nodeApi.isMark(child) || child.text() !== '')
|
||||
return;
|
||||
child = node.prev();
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
} from '../constants';
|
||||
import { EditorInterface, NodeInterface, RangeInterface } from '../types';
|
||||
import { MarkInterface, MarkModelInterface } from '../types/mark';
|
||||
import { getDocument, getWindow, isEngine } from '../utils';
|
||||
import { getDocument, isEngine } from '../utils';
|
||||
import { Backspace } from './typing';
|
||||
import { $ } from '../node';
|
||||
import { isNode } from '../node/utils';
|
||||
|
@ -541,7 +541,7 @@ class Mark implements MarkModelInterface {
|
|||
}
|
||||
}
|
||||
};
|
||||
if (at.nodeType === getWindow().Node.TEXT_NODE) {
|
||||
if (at.nodeType === Node.TEXT_NODE) {
|
||||
const { textContent } = at;
|
||||
atText = textContent!;
|
||||
atTextLen = atText.length;
|
||||
|
@ -758,7 +758,7 @@ class Mark implements MarkModelInterface {
|
|||
if (!node.isMark(mark)) return;
|
||||
let { commonAncestorNode } = safeRange;
|
||||
|
||||
if (commonAncestorNode.type === getWindow().Node.TEXT_NODE) {
|
||||
if (commonAncestorNode.type === Node.TEXT_NODE) {
|
||||
commonAncestorNode = commonAncestorNode.parent()!;
|
||||
}
|
||||
const card = this.editor.card.find(commonAncestorNode, true);
|
||||
|
@ -849,7 +849,7 @@ class Mark implements MarkModelInterface {
|
|||
if (!isEditable) {
|
||||
this.split(safeRange);
|
||||
commonAncestorNode = safeRange.commonAncestorNode;
|
||||
if (commonAncestorNode.type === getWindow().Node.TEXT_NODE) {
|
||||
if (commonAncestorNode.type === Node.TEXT_NODE) {
|
||||
commonAncestorNode = commonAncestorNode.parent()!;
|
||||
}
|
||||
nodes[0] = commonAncestorNode;
|
||||
|
@ -1135,7 +1135,7 @@ class Mark implements MarkModelInterface {
|
|||
}
|
||||
let { commonAncestorNode } = safeRange;
|
||||
|
||||
if (commonAncestorNode.type === getWindow().Node.TEXT_NODE) {
|
||||
if (commonAncestorNode.type === Node.TEXT_NODE) {
|
||||
commonAncestorNode = commonAncestorNode.parent()!;
|
||||
}
|
||||
const card = this.editor.card.find(commonAncestorNode, true);
|
||||
|
@ -1153,7 +1153,7 @@ class Mark implements MarkModelInterface {
|
|||
if (!isEditable) {
|
||||
this.split(safeRange, safeRange.collapsed ? removeMark : undefined);
|
||||
commonAncestorNode = safeRange.commonAncestorNode;
|
||||
if (commonAncestorNode.type === getWindow().Node.TEXT_NODE) {
|
||||
if (commonAncestorNode.type === Node.TEXT_NODE) {
|
||||
commonAncestorNode = commonAncestorNode.parent()!;
|
||||
}
|
||||
nodes[0] = commonAncestorNode;
|
||||
|
@ -1405,14 +1405,11 @@ class Mark implements MarkModelInterface {
|
|||
const eo = cloneRange.endOffset;
|
||||
let startNode = sc;
|
||||
let endNode = ec;
|
||||
if (
|
||||
sc.nodeType === getWindow().Node.ELEMENT_NODE &&
|
||||
sc.childNodes[so]
|
||||
) {
|
||||
if (sc.nodeType === Node.ELEMENT_NODE && sc.childNodes[so]) {
|
||||
startNode = sc.childNodes[so] || sc;
|
||||
}
|
||||
if (
|
||||
ec.nodeType === getWindow().Node.ELEMENT_NODE &&
|
||||
ec.nodeType === Node.ELEMENT_NODE &&
|
||||
eo > 0 &&
|
||||
ec.childNodes[eo - 1]
|
||||
) {
|
||||
|
@ -1433,10 +1430,7 @@ class Mark implements MarkModelInterface {
|
|||
const findNodes = (node: NodeInterface) => {
|
||||
let nodes: Array<NodeInterface> = [];
|
||||
while (node) {
|
||||
if (
|
||||
node.type === getWindow().Node.ELEMENT_NODE &&
|
||||
node.isEditable()
|
||||
) {
|
||||
if (node.type === Node.ELEMENT_NODE && node.isEditable()) {
|
||||
break;
|
||||
}
|
||||
if (
|
||||
|
|
|
@ -13,9 +13,7 @@ import {
|
|||
toCamelCase,
|
||||
getStyleMap,
|
||||
getComputedStyle,
|
||||
getAttrMap,
|
||||
getDocument,
|
||||
getWindow,
|
||||
} from '../utils';
|
||||
import { Path } from 'sharedb';
|
||||
import {
|
||||
|
@ -49,25 +47,24 @@ class NodeEntry implements NodeInterface {
|
|||
|
||||
constructor(nodes: Node | NodeList | Array<Node>, context?: Context) {
|
||||
if (isNode(nodes)) {
|
||||
if (nodes.nodeType === getWindow().Node.DOCUMENT_FRAGMENT_NODE) {
|
||||
if (nodes.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
||||
this.fragment = nodes as DocumentFragment;
|
||||
}
|
||||
nodes = [nodes];
|
||||
}
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
this[i] = nodes[i];
|
||||
this.events[i] = new DOMEvent(); // 初始化事件对象
|
||||
}
|
||||
nodes.forEach((node, index) => {
|
||||
this[index] = node;
|
||||
this.events[index] = new DOMEvent(); // 初始化事件对象
|
||||
});
|
||||
|
||||
this.length = nodes.length;
|
||||
|
||||
if (this.length > 0) {
|
||||
if (this[0]) {
|
||||
this.document = getDocument(context);
|
||||
this.context = context;
|
||||
this.name = this[0].nodeName ? this[0].nodeName.toLowerCase() : '';
|
||||
this.name = this[0].nodeName.toLowerCase();
|
||||
this.type = this[0].nodeType;
|
||||
this.window = getWindow(this[0]);
|
||||
this.window = this.document.defaultView || window;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,7 +74,7 @@ class NodeEntry implements NodeInterface {
|
|||
* @param selector 选择器
|
||||
*/
|
||||
isMatchesSelector(element: ElementInterface, selector: string) {
|
||||
if (element.nodeType !== getWindow().Node.ELEMENT_NODE || !selector) {
|
||||
if (element.nodeType !== Node.ELEMENT_NODE || !selector) {
|
||||
return false;
|
||||
}
|
||||
const defaultMatches = (element: Element, selector: string) => {
|
||||
|
@ -107,10 +104,13 @@ class NodeEntry implements NodeInterface {
|
|||
each(
|
||||
callback: (node: Node, index: number) => boolean | void,
|
||||
): NodeInterface {
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
if (callback(this[i], i) === false) {
|
||||
let i = 0,
|
||||
node;
|
||||
while ((node = this[i])) {
|
||||
if (callback(node, i) === false) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ class NodeEntry implements NodeInterface {
|
|||
* @return {boolean}
|
||||
*/
|
||||
isElement(): boolean {
|
||||
return this.type === getWindow().Node.ELEMENT_NODE;
|
||||
return this.type === Node.ELEMENT_NODE;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -140,7 +140,7 @@ class NodeEntry implements NodeInterface {
|
|||
* @return {boolean}
|
||||
*/
|
||||
isText(): boolean {
|
||||
return this.type === getWindow().Node.TEXT_NODE;
|
||||
return this.type === Node.TEXT_NODE;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -167,7 +167,11 @@ class NodeEntry implements NodeInterface {
|
|||
* @returns
|
||||
*/
|
||||
isEditableCard() {
|
||||
return this.find(EDITABLE_SELECTOR).length > 0;
|
||||
return (
|
||||
this.attributes(DATA_ELEMENT) === EDITABLE ||
|
||||
(this.isElement() &&
|
||||
!!(this[0] as Element).querySelector(EDITABLE_SELECTOR))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -224,7 +228,7 @@ class NodeEntry implements NodeInterface {
|
|||
let prev = this.get()?.previousSibling;
|
||||
let index = 0;
|
||||
|
||||
while (prev && prev.nodeType === getWindow().Node.ELEMENT_NODE) {
|
||||
while (prev && prev.nodeType === Node.ELEMENT_NODE) {
|
||||
index++;
|
||||
prev = prev.previousSibling;
|
||||
}
|
||||
|
@ -372,8 +376,8 @@ class NodeEntry implements NodeInterface {
|
|||
return false;
|
||||
}
|
||||
if (
|
||||
this.get()!.nodeType === getWindow().Node.DOCUMENT_NODE &&
|
||||
domNode?.nodeType !== getWindow().Node.DOCUMENT_NODE
|
||||
this.get()!.nodeType === Node.DOCUMENT_NODE &&
|
||||
domNode?.nodeType !== Node.DOCUMENT_NODE
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
@ -395,7 +399,9 @@ class NodeEntry implements NodeInterface {
|
|||
*/
|
||||
find(selector: string): NodeInterface {
|
||||
if (this.length > 0 && this.isElement()) {
|
||||
const nodeList = this.get<Element>()?.querySelectorAll(selector);
|
||||
const nodeList = (
|
||||
this.fragment ? this.fragment : this.get<Element>()
|
||||
)?.querySelectorAll(selector);
|
||||
return new NodeEntry(nodeList || []);
|
||||
}
|
||||
return new NodeEntry([]);
|
||||
|
@ -526,8 +532,18 @@ class NodeEntry implements NodeInterface {
|
|||
val?: string | number,
|
||||
): NodeEntry | { [k: string]: string } | string {
|
||||
if (key === undefined) {
|
||||
const element = this.clone(false).get<Element>();
|
||||
return getAttrMap(element?.outerHTML || '');
|
||||
const element = this.get<Element>();
|
||||
if (!element) return {};
|
||||
const attrs = {};
|
||||
const elementAttributes = element.attributes;
|
||||
let i = 0,
|
||||
item = null;
|
||||
while ((item = elementAttributes[i])) {
|
||||
const { name, value } = item;
|
||||
attrs[name] = value;
|
||||
i++;
|
||||
}
|
||||
return attrs;
|
||||
}
|
||||
|
||||
if (typeof key === 'object') {
|
||||
|
@ -560,6 +576,7 @@ class NodeEntry implements NodeInterface {
|
|||
*/
|
||||
removeAttributes(key: string): NodeInterface {
|
||||
this.each((node) => {
|
||||
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
||||
const element = <Element>node;
|
||||
element.removeAttribute(key);
|
||||
});
|
||||
|
@ -573,13 +590,14 @@ class NodeEntry implements NodeInterface {
|
|||
*/
|
||||
hasClass(className: string): boolean {
|
||||
if (this.length === 0) return false;
|
||||
const element = this.get<Element>()!;
|
||||
if (element.classList) {
|
||||
for (let i = 0; i < element.classList.length; i++) {
|
||||
if (element.classList[i] === className) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const element = this.get<Element>();
|
||||
if (!element) return false;
|
||||
let i = 0,
|
||||
name = null;
|
||||
const classList = element.classList || {};
|
||||
while ((name = classList[i])) {
|
||||
if (name === className) return true;
|
||||
i++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -689,8 +707,17 @@ class NodeEntry implements NodeInterface {
|
|||
html(html: string): NodeEntry;
|
||||
html(html?: string): NodeEntry | string {
|
||||
if (html !== undefined) {
|
||||
const children = $(html);
|
||||
this.each((node) => {
|
||||
(node as Element).innerHTML = html;
|
||||
let child = node.firstChild;
|
||||
while (child) {
|
||||
const next = child.nextSibling;
|
||||
node.removeChild(child);
|
||||
child = next;
|
||||
}
|
||||
children.forEach((child) => {
|
||||
(node as Element).append(child.cloneNode(true));
|
||||
});
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
@ -772,12 +799,8 @@ class NodeEntry implements NodeInterface {
|
|||
this.each((node) => {
|
||||
let child = node.firstChild;
|
||||
while (child) {
|
||||
if (!node.parentNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const next = child.nextSibling;
|
||||
child.parentNode?.removeChild(child);
|
||||
node.removeChild(child);
|
||||
child = next;
|
||||
}
|
||||
});
|
||||
|
@ -809,9 +832,10 @@ class NodeEntry implements NodeInterface {
|
|||
*/
|
||||
prepend(selector: Selector): NodeInterface {
|
||||
const nodes = $(selector, this.context);
|
||||
const isClone = typeof selector === 'string' && /<.+>/.test(selector);
|
||||
this.each((node) => {
|
||||
for (let i = nodes.length - 1; i >= 0; i--) {
|
||||
const child = nodes[i];
|
||||
const child = isClone ? nodes[i].cloneNode(true) : nodes[i];
|
||||
if (node.firstChild) {
|
||||
node.insertBefore(child, node.firstChild);
|
||||
} else {
|
||||
|
@ -829,11 +853,12 @@ class NodeEntry implements NodeInterface {
|
|||
*/
|
||||
append(selector: Selector): NodeInterface {
|
||||
const nodes = $(selector, this.context);
|
||||
const isClone = typeof selector === 'string' && /<.+>/.test(selector);
|
||||
this.each((node) => {
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const child = nodes[i];
|
||||
const child = isClone ? nodes[i].cloneNode(true) : nodes[i];
|
||||
if (typeof selector === 'string') {
|
||||
node.appendChild(child.cloneNode(true));
|
||||
node.appendChild(child);
|
||||
} else {
|
||||
node.appendChild(child);
|
||||
}
|
||||
|
@ -848,10 +873,14 @@ class NodeEntry implements NodeInterface {
|
|||
* @return 当前实例
|
||||
*/
|
||||
before(selector: Selector): NodeInterface {
|
||||
const nodes = $(selector, this.context);
|
||||
const isClone = typeof selector === 'string' && /<.+>/.test(selector);
|
||||
this.each((node) => {
|
||||
const nodes = $(selector, this.context);
|
||||
const parentNode = node.parentNode;
|
||||
if (!parentNode) return;
|
||||
nodes.forEach((child) => {
|
||||
node.parentNode?.insertBefore(child, node);
|
||||
if (isClone) child = child.cloneNode(true);
|
||||
parentNode.insertBefore(child, node);
|
||||
node = child;
|
||||
});
|
||||
});
|
||||
|
@ -864,14 +893,18 @@ class NodeEntry implements NodeInterface {
|
|||
* @return 当前实例
|
||||
*/
|
||||
after(selector: Selector): NodeInterface {
|
||||
const nodes = $(selector, this.context);
|
||||
const isClone = typeof selector === 'string' && /<.+>/.test(selector);
|
||||
this.each((node) => {
|
||||
const nodes = $(selector, this.context);
|
||||
const parentNode = node.parentNode;
|
||||
if (!parentNode) return;
|
||||
nodes.forEach((child) => {
|
||||
if (isClone) child = child.cloneNode(true);
|
||||
if (node.nextSibling) {
|
||||
node.parentNode?.insertBefore(child, node.nextSibling);
|
||||
parentNode.insertBefore(child, node.nextSibling);
|
||||
node = child;
|
||||
} else {
|
||||
node.parentNode?.appendChild(child);
|
||||
parentNode.appendChild(child);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -885,10 +918,13 @@ class NodeEntry implements NodeInterface {
|
|||
*/
|
||||
replaceWith(selector: Selector): NodeInterface {
|
||||
const newNodes: Array<Node> = [];
|
||||
const nodes = $(selector, this.context);
|
||||
const isClone = typeof selector === 'string' && /<.+>/.test(selector);
|
||||
this.each((node) => {
|
||||
const nodes = $(selector, this.context);
|
||||
const newNode = nodes[0];
|
||||
node.parentNode?.replaceChild(newNode, node);
|
||||
const parentNode = node.parentNode;
|
||||
if (!parentNode) return;
|
||||
const newNode = isClone ? nodes[0].cloneNode(true) : nodes[0];
|
||||
parentNode.replaceChild(newNode, node);
|
||||
newNodes.push(newNode);
|
||||
});
|
||||
return new NodeEntry(newNodes);
|
||||
|
@ -914,7 +950,7 @@ class NodeEntry implements NodeInterface {
|
|||
}
|
||||
|
||||
if (result !== true) {
|
||||
if (child.isEditableCard() && includeEditableCard) {
|
||||
if (includeEditableCard && child.isEditableCard()) {
|
||||
const editableElements = child.find(EDITABLE_SELECTOR);
|
||||
editableElements.each((_, index) => {
|
||||
const editableElement = editableElements.eq(index);
|
||||
|
@ -1014,7 +1050,7 @@ class NodeEntry implements NodeInterface {
|
|||
|
||||
inViewport(node: NodeInterface, view: NodeInterface) {
|
||||
let viewNode = null;
|
||||
if (view.type !== getWindow().Node.ELEMENT_NODE) {
|
||||
if (view.type !== Node.ELEMENT_NODE) {
|
||||
if (!view.document) return false;
|
||||
viewNode = view.document.createElement('span');
|
||||
if (view.next()) {
|
||||
|
@ -1045,7 +1081,7 @@ class NodeEntry implements NodeInterface {
|
|||
if (typeof view.document?.body.scrollIntoView === 'function') {
|
||||
let viewElement = null;
|
||||
if (
|
||||
view.type !== getWindow().Node.ELEMENT_NODE ||
|
||||
view.type !== Node.ELEMENT_NODE ||
|
||||
view.name.toLowerCase() === 'br'
|
||||
) {
|
||||
viewElement = view.document.createElement('span');
|
||||
|
|
|
@ -49,14 +49,10 @@ class Event implements EventInterface {
|
|||
const listeners = this.listeners[eventType];
|
||||
if (listeners) {
|
||||
let result;
|
||||
|
||||
for (var i = 0; i < listeners.length; i++) {
|
||||
result = listeners[i](...args);
|
||||
|
||||
if (result === false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
listeners.every((listener) => {
|
||||
result = listener(...args);
|
||||
return result !== false;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -41,6 +41,8 @@ export const uuid = (len: number, radix: number = 16): string => {
|
|||
return uuid.join('');
|
||||
};
|
||||
|
||||
const valueCaches = new Map<string, string>();
|
||||
|
||||
export default (
|
||||
value: string | NodeInterface | Node,
|
||||
unique: boolean = true,
|
||||
|
@ -48,24 +50,27 @@ export default (
|
|||
let prefix = '';
|
||||
if (isNode(value)) value = $(value);
|
||||
if (isNodeEntry(value)) {
|
||||
const attributes = value.attributes();
|
||||
const styles = attributes['style'];
|
||||
delete attributes['style'];
|
||||
const attributes = value.attributes() || {};
|
||||
delete attributes[DATA_ID];
|
||||
prefix = value.name.substring(0, 1);
|
||||
value = `${value.name}_${Object.keys(attributes || {}).join(
|
||||
',',
|
||||
)}_${Object.values(attributes || {}).join(',')}_${Object.keys(
|
||||
styles || {},
|
||||
).join(',')}_${Object.values(styles || {}).join(',')}`;
|
||||
value = `${value.name}_${JSON.stringify(attributes)}`;
|
||||
}
|
||||
const md5Value = md5(value);
|
||||
let hash =
|
||||
prefix + md5Value.substr(0, 4) + md5Value.substr(md5Value.length - 3);
|
||||
const cachePerfix = valueCaches.get(value);
|
||||
if (!cachePerfix) {
|
||||
const md5Value = md5(value);
|
||||
prefix =
|
||||
prefix +
|
||||
md5Value.substr(0, 4) +
|
||||
md5Value.substr(md5Value.length - 3);
|
||||
valueCaches.set(value, prefix);
|
||||
} else {
|
||||
prefix = cachePerfix;
|
||||
}
|
||||
const hash = prefix;
|
||||
if (unique) {
|
||||
const counter = _counters[hash] || 0;
|
||||
_counters[hash] = counter + 1;
|
||||
hash = `${hash}-${uuid(8, 48 + _counters[hash])}`;
|
||||
return `${hash}-${uuid(8, 48 + _counters[hash])}`;
|
||||
}
|
||||
|
||||
return hash;
|
||||
|
|
|
@ -68,14 +68,12 @@ class NodeId implements NodeIdInterface {
|
|||
if (isNodeEntry(root) && root.fragment) {
|
||||
root = root.fragment;
|
||||
}
|
||||
const element = (isNode(root) ? root : root.get<Element>()) as Element;
|
||||
if (element.nodeType === Node.TEXT_NODE) return;
|
||||
const nodes =
|
||||
(isNode(root) ? root : root.get<Element>())?.querySelectorAll(
|
||||
tagNames,
|
||||
) || [];
|
||||
nodes.forEach((child) => {
|
||||
const node = $(child);
|
||||
const node = isNode(root) ? root : root.get<Node>();
|
||||
if (!node || node.nodeType === Node.TEXT_NODE) return;
|
||||
const nodes = (isNode(root) ? $(root) : root)?.find(tagNames);
|
||||
nodes.each((_, index) => {
|
||||
const node = nodes.eq(index);
|
||||
if (!node) return;
|
||||
// 有ID不再生成
|
||||
if (!force && node.attributes(DATA_ID)) return;
|
||||
|
||||
|
@ -95,7 +93,8 @@ class NodeId implements NodeIdInterface {
|
|||
// 不符合规则
|
||||
const nodeRules = rules[node.name];
|
||||
if (
|
||||
(nodeRules && nodeRules.length === 0) ||
|
||||
!nodeRules ||
|
||||
nodeRules.length === 0 ||
|
||||
!nodeRules.some((rule) =>
|
||||
this.editor.schema.checkNode(
|
||||
node as NodeInterface,
|
||||
|
|
|
@ -21,7 +21,7 @@ import {
|
|||
READY_CARD_KEY,
|
||||
READY_CARD_SELECTOR,
|
||||
} from '../constants';
|
||||
import { getDocument, getStyleMap, getWindow, isEngine } from '../utils';
|
||||
import { getDocument, getStyleMap, isEngine } from '../utils';
|
||||
import $ from './query';
|
||||
import getHashId from './hash';
|
||||
import { isNode, isNodeEntry } from './utils';
|
||||
|
@ -168,9 +168,9 @@ class NodeModel implements NodeModelInterface {
|
|||
if (childNodes.length === 0) return true;
|
||||
for (let i = 0; i < childNodes.length; i++) {
|
||||
const child = childNodes[i];
|
||||
if (child.nodeType === getWindow().Node.TEXT_NODE) {
|
||||
if (child.nodeType === Node.TEXT_NODE) {
|
||||
if (child['data'].replace(/\u200b/g, '') !== '') return false;
|
||||
} else if (child.nodeType === getWindow().Node.ELEMENT_NODE) {
|
||||
} else if (child.nodeType === Node.ELEMENT_NODE) {
|
||||
if (
|
||||
child.nodeName.toLowerCase() === 'li' &&
|
||||
!this.editor.list.isEmptyItem($(child))
|
||||
|
@ -446,8 +446,12 @@ class NodeModel implements NodeModelInterface {
|
|||
* @param source 旧节点
|
||||
* @param target 新节点
|
||||
*/
|
||||
replace(source: NodeInterface, target: NodeInterface) {
|
||||
const clone = this.clone(target, false, false);
|
||||
replace(
|
||||
source: NodeInterface,
|
||||
target: NodeInterface,
|
||||
copyId: boolean = false,
|
||||
) {
|
||||
const clone = this.clone(target, false, copyId);
|
||||
let childNode =
|
||||
this.isCustomize(source) &&
|
||||
source.name === 'li' &&
|
||||
|
@ -499,7 +503,6 @@ class NodeModel implements NodeModelInterface {
|
|||
if (!isEngine(editor)) return;
|
||||
const { change, block, schema, mark } = editor;
|
||||
range = range || change.range.get();
|
||||
const nodeApi = editor.node;
|
||||
const { startNode, startOffset } = range
|
||||
.cloneRange()
|
||||
.shrinkToTextNode();
|
||||
|
@ -512,8 +515,8 @@ class NodeModel implements NodeModelInterface {
|
|||
//零宽字符前面还有其它字符。或者节点前面还有节点,不能是inline节点。或者前面没有节点了,并且父级不是inline节点
|
||||
if (
|
||||
text.length > 1 ||
|
||||
(prev && !nodeApi.isInline(prev)) ||
|
||||
(!prev && parent && !nodeApi.isInline(parent))
|
||||
(prev && !this.isInline(prev)) ||
|
||||
(!prev && parent && !this.isInline(parent))
|
||||
) {
|
||||
startNode
|
||||
.get<Text>()!
|
||||
|
@ -534,8 +537,16 @@ class NodeModel implements NodeModelInterface {
|
|||
}
|
||||
}
|
||||
|
||||
if (nodeApi.isBlock(node)) {
|
||||
const splitNode = block.split(range);
|
||||
if (this.isBlock(node)) {
|
||||
// 如果当前光标位置的block节点是空节点,就不用分割
|
||||
const { commonAncestorNode } = range;
|
||||
let splitNode = null;
|
||||
if (
|
||||
this.isBlock(commonAncestorNode) &&
|
||||
this.isEmpty(commonAncestorNode)
|
||||
) {
|
||||
splitNode = commonAncestorNode;
|
||||
} else splitNode = block.split(range);
|
||||
let blockNode = block.closest(
|
||||
range.startNode.isEditable()
|
||||
? range
|
||||
|
@ -683,9 +694,12 @@ class NodeModel implements NodeModelInterface {
|
|||
* @param style 样式名称
|
||||
*/
|
||||
removeMinusStyle(node: NodeInterface, style: string) {
|
||||
if (this.isBlock(node)) {
|
||||
const val = parseInt(node.css(style), 10) || 0;
|
||||
if (val < 0) node.css(style, '');
|
||||
if (node.isElement()) {
|
||||
const styles = node.css();
|
||||
if (styles[style]) {
|
||||
const val = parseInt(styles[style] || '0', 10) || 0;
|
||||
if (val < 0) node.css(style, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -835,29 +849,29 @@ class NodeModel implements NodeModelInterface {
|
|||
}
|
||||
this.removeSide(cloneNode);
|
||||
block.flat(cloneNode, $(rootElement || []));
|
||||
const children = cloneNode.children().toArray();
|
||||
if (
|
||||
cloneNode.name === 'p' &&
|
||||
cloneNode
|
||||
.children()
|
||||
.toArray()
|
||||
.filter((node) => !node.isCursor()).length === 0
|
||||
children.filter((node) => !node.isCursor()).length === 0
|
||||
) {
|
||||
cloneNode.append($('<br />'));
|
||||
}
|
||||
if (
|
||||
this.editor.node.isBlock(cloneNode) &&
|
||||
this.isEmptyWithTrim(cloneNode)
|
||||
) {
|
||||
cloneNode.html('<br />');
|
||||
}
|
||||
}
|
||||
const children = childNode.children().toArray();
|
||||
if (
|
||||
childNode.name === 'p' &&
|
||||
childNode
|
||||
.children()
|
||||
.toArray()
|
||||
.filter((node) => !node.isCursor()).length === 0
|
||||
children.filter((node) => !node.isCursor()).length === 0
|
||||
) {
|
||||
childNode.append($('<br />'));
|
||||
}
|
||||
if (
|
||||
this.editor.node.isBlock(childNode) &&
|
||||
childNode.children().length === 1 &&
|
||||
childNode.first()?.isText() &&
|
||||
this.isEmptyWithTrim(childNode)
|
||||
) {
|
||||
childNode.html('<br />');
|
||||
|
@ -956,7 +970,7 @@ class NodeModel implements NodeModelInterface {
|
|||
removeZeroWidthSpace(node: NodeInterface) {
|
||||
node.traverse((child) => {
|
||||
const node = child[0];
|
||||
if (node.nodeType !== getWindow().Node.TEXT_NODE) {
|
||||
if (node.nodeType !== Node.TEXT_NODE) {
|
||||
return;
|
||||
}
|
||||
const text = node.nodeValue;
|
||||
|
@ -968,7 +982,7 @@ class NodeModel implements NodeModelInterface {
|
|||
if (
|
||||
text.charCodeAt(1) === 0x200b &&
|
||||
next &&
|
||||
next.nodeType === getWindow().Node.ELEMENT_NODE &&
|
||||
next.nodeType === Node.ELEMENT_NODE &&
|
||||
[ANCHOR, FOCUS, CURSOR].indexOf(
|
||||
(<Element>next).getAttribute(DATA_ELEMENT) || '',
|
||||
) >= 0
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { Selector, Context } from '../types/node';
|
||||
import { getDocument, getWindow } from '../utils/node';
|
||||
import { getDocument } from '../utils/node';
|
||||
import { isNode, isNodeEntry, isNodeList } from '../node/utils';
|
||||
|
||||
/**
|
||||
* 缓存selector创建的node
|
||||
*/
|
||||
const nodeCaches = new Map<string, Element>();
|
||||
/**
|
||||
* 解析节点
|
||||
* @param selector 选择器
|
||||
|
@ -21,6 +25,20 @@ function domParser(
|
|||
const isTd = selector.indexOf('<td') === 0;
|
||||
//替换注释
|
||||
selector = selector.trim().replace(/<!--[^>]*-->/g, '');
|
||||
|
||||
const cacheNode = nodeCaches.get(selector);
|
||||
if (cacheNode) {
|
||||
if (isTr) {
|
||||
const tbody = cacheNode.querySelector('tbody');
|
||||
return tbody ? tbody.cloneNode(true).childNodes : [];
|
||||
}
|
||||
|
||||
if (isTd) {
|
||||
const tr = cacheNode.querySelector('tr');
|
||||
return tr ? tr.cloneNode(true).childNodes : [];
|
||||
}
|
||||
return cacheNode.cloneNode(true).childNodes;
|
||||
}
|
||||
/**
|
||||
* 无法单独解析 tr、td 标签,如果有tr、td标签这里需要补充 table 节点的结构
|
||||
*/
|
||||
|
@ -40,7 +58,7 @@ function domParser(
|
|||
//创建一个空节点,用来包裹需要生成的节点
|
||||
const container = getDocument().createElement('div');
|
||||
container.innerHTML = selector;
|
||||
|
||||
nodeCaches.set(selector, container.cloneNode(true) as Element);
|
||||
if (isTr) {
|
||||
const tbody = container.querySelector('tbody');
|
||||
return tbody ? tbody.childNodes : [];
|
||||
|
@ -70,10 +88,7 @@ function domParser(
|
|||
return nodes;
|
||||
}
|
||||
// 片段
|
||||
if (
|
||||
isNode(selector) &&
|
||||
selector.nodeType === getWindow().Node.DOCUMENT_FRAGMENT_NODE
|
||||
) {
|
||||
if (isNode(selector) && selector.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
||||
const nodes: Node[] = [];
|
||||
let node = selector.firstChild;
|
||||
while (node) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Node from './entry';
|
||||
import { NodeInterface, Selector, Context, NodeEntry } from '../types/node';
|
||||
import { getDocument, getWindow } from '../utils/node';
|
||||
import { getDocument } from '../utils/node';
|
||||
import Parse from './parse';
|
||||
import { isNode } from './utils';
|
||||
|
||||
|
@ -20,7 +20,7 @@ export default (
|
|||
const entry = new (clazz || Node)(nodes, context ? context : undefined);
|
||||
if (
|
||||
isNode(selector) &&
|
||||
selector.nodeType === getWindow().Node.DOCUMENT_FRAGMENT_NODE
|
||||
selector.nodeType === window.Node.DOCUMENT_FRAGMENT_NODE
|
||||
)
|
||||
entry.fragment = selector as DocumentFragment;
|
||||
return entry;
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
TargetOp,
|
||||
} from '../types/ot';
|
||||
import { NodeInterface } from '../types/node';
|
||||
import { getDocument, getWindow } from '../utils';
|
||||
import { getDocument } from '../utils';
|
||||
import { isCursorOp, isTransientElement, updateIndex, toDOM } from './utils';
|
||||
import { $ } from '../node';
|
||||
import { DATA_ID, EDITABLE_SELECTOR } from '../constants';
|
||||
|
@ -51,7 +51,7 @@ class Consumer implements ConsumerInterface {
|
|||
1 === path.length ||
|
||||
pathOffset === JSON0_INDEX.TAG_NAME ||
|
||||
pathOffset === JSON0_INDEX.ATTRIBUTE ||
|
||||
childNode.nodeType === getWindow().Node.TEXT_NODE
|
||||
childNode.nodeType === Node.TEXT_NODE
|
||||
) {
|
||||
return {
|
||||
startNode: childNode,
|
||||
|
|
|
@ -19,13 +19,7 @@ import {
|
|||
UI,
|
||||
UI_SELECTOR,
|
||||
} from '../constants/root';
|
||||
import {
|
||||
getParentInRoot,
|
||||
getWindow,
|
||||
toHex,
|
||||
unescapeDots,
|
||||
unescape,
|
||||
} from '../utils';
|
||||
import { getParentInRoot, toHex, unescapeDots, unescape } from '../utils';
|
||||
|
||||
export const isTransientElement = (
|
||||
node: NodeInterface,
|
||||
|
@ -343,7 +337,7 @@ export const toJSON0 = (
|
|||
let values: Array<{} | string>;
|
||||
if (!isTransientElement(node)) {
|
||||
const { attributes, nodeValue } = node.get<Element>()!;
|
||||
if (node.type === getWindow().Node.ELEMENT_NODE) {
|
||||
if (node.type === Node.ELEMENT_NODE) {
|
||||
values = [node.name];
|
||||
const data = {};
|
||||
for (let i = 0; attributes && i < attributes.length; i++) {
|
||||
|
@ -362,9 +356,7 @@ export const toJSON0 = (
|
|||
childToJSON0(node, values);
|
||||
return values;
|
||||
}
|
||||
return node.type === getWindow().Node.TEXT_NODE
|
||||
? String(nodeValue)
|
||||
: undefined;
|
||||
return node.type === Node.TEXT_NODE ? String(nodeValue) : undefined;
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
|
|
@ -17,7 +17,6 @@ import {
|
|||
toHex,
|
||||
transformCustomTags,
|
||||
getListStyle,
|
||||
getWindow,
|
||||
} from '../utils';
|
||||
import TextParser from './text';
|
||||
import { $ } from '../node';
|
||||
|
@ -57,7 +56,7 @@ const stylesToString = (styles: { [k: string]: string }) => {
|
|||
};
|
||||
|
||||
class Parser implements ParserInterface {
|
||||
private root: NodeInterface;
|
||||
root: NodeInterface;
|
||||
private editor: EditorInterface;
|
||||
constructor(
|
||||
source: string | Node | NodeInterface,
|
||||
|
@ -79,10 +78,7 @@ class Parser implements ParserInterface {
|
|||
.replace(/<p(>|\s+[^>]*>)/gi, '<paragraph$1')
|
||||
.replace(/<\/p>/gi, '</paragraph>');
|
||||
source = transformCustomTags(source);
|
||||
const doc = new (getWindow().DOMParser)().parseFromString(
|
||||
source,
|
||||
'text/html',
|
||||
);
|
||||
const doc = new DOMParser().parseFromString(source, 'text/html');
|
||||
this.root = $(doc.body);
|
||||
const p = $('<p></p>');
|
||||
const paragraphs = this.root.find('paragraph');
|
||||
|
@ -94,7 +90,7 @@ class Parser implements ParserInterface {
|
|||
Object.keys(attributes).forEach((name) => {
|
||||
pNode.attributes(name, attributes[name]);
|
||||
});
|
||||
node.replace(cNode, pNode);
|
||||
node.replace(cNode, pNode, true);
|
||||
});
|
||||
} else if (isNodeEntry(source)) {
|
||||
this.root = source;
|
||||
|
@ -126,7 +122,7 @@ class Parser implements ParserInterface {
|
|||
const { rule } = value;
|
||||
oldRules.push(rule);
|
||||
const { name, attributes, style } = value.node;
|
||||
delete attributes['data-id'];
|
||||
delete attributes[DATA_ID];
|
||||
const newNode = $(`<${name} />`);
|
||||
nodeApi.setAttributes(newNode, {
|
||||
...attributes,
|
||||
|
@ -198,7 +194,7 @@ class Parser implements ParserInterface {
|
|||
return newNode;
|
||||
};
|
||||
//当前节点是 inline 节点,inline 节点不允许嵌套、不允许放入mark节点
|
||||
inlineApi.flat(node);
|
||||
inlineApi.flat(node, schema);
|
||||
//当前节点是 mark 节点
|
||||
if (nodeApi.isMark(node, schema)) {
|
||||
//过滤掉当前mark节点属性样式并使用剩下的属性样式组成新的节点
|
||||
|
@ -480,7 +476,7 @@ class Parser implements ParserInterface {
|
|||
const node = domNode.get<HTMLElement>();
|
||||
if (
|
||||
node &&
|
||||
node.nodeType === getWindow().Node.ELEMENT_NODE &&
|
||||
node.nodeType === Node.ELEMENT_NODE &&
|
||||
'none' === node.style['user-select'] &&
|
||||
node.parentNode
|
||||
) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { NodeInterface } from './types/node';
|
||||
import { RangeInterface, RangePath } from './types/range';
|
||||
import { getWindow, isMobile } from './utils';
|
||||
import { isMobile } from './utils';
|
||||
import { CARD_ELEMENT_KEY, CARD_SELECTOR } from './constants/card';
|
||||
import { ANCHOR, CURSOR, FOCUS } from './constants/selection';
|
||||
import {
|
||||
|
@ -205,7 +205,7 @@ class Range implements RangeInterface {
|
|||
*/
|
||||
enlargeFromTextNode = () => {
|
||||
const enlargePosition = (node: Node, offset: number, type: string) => {
|
||||
if (node.nodeType !== getWindow().Node.TEXT_NODE) {
|
||||
if (node.nodeType !== Node.TEXT_NODE) {
|
||||
return;
|
||||
}
|
||||
if (offset === 0) {
|
||||
|
@ -239,7 +239,7 @@ class Range implements RangeInterface {
|
|||
*/
|
||||
shrinkToTextNode = () => {
|
||||
const shrinkPosition = (node: Node, offset: number, type: string) => {
|
||||
if (node.nodeType !== getWindow().Node.ELEMENT_NODE) {
|
||||
if (node.nodeType !== Node.ELEMENT_NODE) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -260,12 +260,12 @@ class Range implements RangeInterface {
|
|||
right = childNodes[offset];
|
||||
}
|
||||
|
||||
if (left && left.nodeType === getWindow().Node.TEXT_NODE) {
|
||||
if (left && left.nodeType === Node.TEXT_NODE) {
|
||||
child = left;
|
||||
offset = child.nodeValue?.length || 0;
|
||||
}
|
||||
|
||||
if (right && right.nodeType === getWindow().Node.TEXT_NODE) {
|
||||
if (right && right.nodeType === Node.TEXT_NODE) {
|
||||
child = right;
|
||||
offset = 0;
|
||||
}
|
||||
|
@ -305,7 +305,7 @@ class Range implements RangeInterface {
|
|||
) => {
|
||||
let domNode = $(node);
|
||||
if (
|
||||
domNode.type === getWindow().Node.TEXT_NODE ||
|
||||
domNode.type === Node.TEXT_NODE ||
|
||||
(!toBlock && nodeApi.isBlock(domNode)) ||
|
||||
domNode.isEditable()
|
||||
) {
|
||||
|
@ -363,10 +363,10 @@ class Range implements RangeInterface {
|
|||
let child;
|
||||
let childDom;
|
||||
while (
|
||||
this.startContainer.nodeType === getWindow().Node.ELEMENT_NODE &&
|
||||
this.startContainer.nodeType === Node.ELEMENT_NODE &&
|
||||
(child = this.startContainer.childNodes[this.startOffset]) &&
|
||||
(childDom = $(child)) &&
|
||||
child.nodeType === getWindow().Node.ELEMENT_NODE &&
|
||||
child.nodeType === Node.ELEMENT_NODE &&
|
||||
!childDom.isCursor() &&
|
||||
!node.isVoid(child) &&
|
||||
(!childDom.isCard() ||
|
||||
|
@ -376,11 +376,11 @@ class Range implements RangeInterface {
|
|||
this.setStart(child, 0);
|
||||
}
|
||||
while (
|
||||
this.endContainer.nodeType === getWindow().Node.ELEMENT_NODE &&
|
||||
this.endContainer.nodeType === Node.ELEMENT_NODE &&
|
||||
this.endOffset > 0 &&
|
||||
(child = this.endContainer.childNodes[this.endOffset - 1]) &&
|
||||
(childDom = $(child)) &&
|
||||
child.nodeType === getWindow().Node.ELEMENT_NODE &&
|
||||
child.nodeType === Node.ELEMENT_NODE &&
|
||||
!node.isVoid(child) &&
|
||||
!childDom.isCursor() &&
|
||||
(!childDom.isCard() ||
|
||||
|
@ -514,7 +514,7 @@ class Range implements RangeInterface {
|
|||
if (
|
||||
startContainer !== endContainer ||
|
||||
collapsed === true ||
|
||||
startContainer.nodeType === getWindow().Node.TEXT_NODE
|
||||
startContainer.nodeType === Node.TEXT_NODE
|
||||
) {
|
||||
return elements;
|
||||
}
|
||||
|
@ -533,7 +533,7 @@ class Range implements RangeInterface {
|
|||
|
||||
getStartOffsetNode = (): Node => {
|
||||
const { startContainer, startOffset } = this;
|
||||
if (startContainer.nodeType === getWindow().Node.ELEMENT_NODE) {
|
||||
if (startContainer.nodeType === Node.ELEMENT_NODE) {
|
||||
return (
|
||||
startContainer.childNodes[startOffset] ||
|
||||
startContainer.childNodes[startOffset - 1] ||
|
||||
|
@ -545,7 +545,7 @@ class Range implements RangeInterface {
|
|||
|
||||
getEndOffsetNode = (): Node => {
|
||||
const { endContainer, endOffset } = this;
|
||||
if (endContainer.nodeType === getWindow().Node.ELEMENT_NODE) {
|
||||
if (endContainer.nodeType === Node.ELEMENT_NODE) {
|
||||
return (
|
||||
endContainer.childNodes[endOffset] ||
|
||||
endContainer.childNodes[endOffset - 1] ||
|
||||
|
@ -568,10 +568,7 @@ class Range implements RangeInterface {
|
|||
|
||||
scrollRangeIntoView = () => {
|
||||
const node = this.getEndOffsetNode();
|
||||
const root =
|
||||
node.nodeType === getWindow().Node.TEXT_NODE
|
||||
? node.parentNode
|
||||
: node;
|
||||
const root = node.nodeType === Node.TEXT_NODE ? node.parentNode : node;
|
||||
const rect = this.collapsed
|
||||
? (root as Element).getBoundingClientRect()
|
||||
: this.getClientRect();
|
||||
|
@ -890,13 +887,13 @@ Range.fromPath = (
|
|||
offset = 0;
|
||||
}
|
||||
if (
|
||||
node.nodeType === getWindow().Node.ELEMENT_NODE &&
|
||||
node.nodeType === Node.ELEMENT_NODE &&
|
||||
offset > node.childNodes.length
|
||||
) {
|
||||
offset = node.childNodes.length;
|
||||
}
|
||||
if (
|
||||
node.nodeType === getWindow().Node.TEXT_NODE &&
|
||||
node.nodeType === Node.TEXT_NODE &&
|
||||
offset > (node.nodeValue?.length || 0)
|
||||
) {
|
||||
offset = node.nodeValue?.length || 0;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { startsWith } from 'lodash-es';
|
||||
import { getDocument, getWindow } from '../../utils';
|
||||
import { getDocument } from '../../utils';
|
||||
import { AjaxInterface, AjaxOptions, SetupOptions } from '../../types/request';
|
||||
import { isFormData, toQueryString, urlAppend } from './utils';
|
||||
import {
|
||||
|
@ -53,7 +53,7 @@ class Ajax implements AjaxInterface {
|
|||
...globalSetup,
|
||||
...options,
|
||||
url,
|
||||
context: options.context || getWindow(),
|
||||
context: options.context || window,
|
||||
doc: options.doc || getDocument(),
|
||||
jsonpCallback: options.jsonpCallback || 'callback',
|
||||
method: options.method || 'GET',
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
SchemaValue,
|
||||
SchemaValueObject,
|
||||
} from './types';
|
||||
import { getWindow, validUrl } from './utils';
|
||||
import { validUrl } from './utils';
|
||||
import { getHashId } from './node';
|
||||
import { DATA_ID } from './constants';
|
||||
|
||||
|
@ -149,23 +149,20 @@ class Schema implements SchemaInterface {
|
|||
* @param callback 查找条件
|
||||
*/
|
||||
find(callback: (rule: SchemaRule) => boolean): Array<SchemaRule> {
|
||||
let schemas: Array<SchemaRule> = [];
|
||||
Object.keys(this.data).some((key) => {
|
||||
const schemas: Array<SchemaRule> = [];
|
||||
Object.keys(this.data).forEach((key) => {
|
||||
if (key !== 'globals') {
|
||||
const rules = (this.data[key] as Array<SchemaRule>).filter(
|
||||
(rule) => callback(rule),
|
||||
callback,
|
||||
);
|
||||
if (rules && rules.length > 0) {
|
||||
schemas = schemas.concat(rules);
|
||||
}
|
||||
schemas.push(...rules);
|
||||
}
|
||||
return;
|
||||
});
|
||||
return schemas;
|
||||
}
|
||||
|
||||
getType(node: NodeInterface, filter?: (rule: SchemaRule) => boolean) {
|
||||
if (node.type !== getWindow().Node.ELEMENT_NODE) return undefined;
|
||||
if (node.type !== Node.ELEMENT_NODE) return undefined;
|
||||
let id = node.attributes(DATA_ID);
|
||||
if (!id) id = getHashId(node, false);
|
||||
else id = id.split('-')[0];
|
||||
|
@ -184,7 +181,7 @@ class Schema implements SchemaInterface {
|
|||
*/
|
||||
getRule(node: NodeInterface, filter?: (rule: SchemaRule) => boolean) {
|
||||
filter = filter || ((rule) => rule.name === node.name);
|
||||
if (node.type !== getWindow().Node.ELEMENT_NODE) return undefined;
|
||||
if (node.type !== Node.ELEMENT_NODE) return undefined;
|
||||
return this._all.find(
|
||||
(rule) => filter!(rule) && this.checkNode(node, rule.attributes),
|
||||
);
|
||||
|
@ -197,16 +194,15 @@ class Schema implements SchemaInterface {
|
|||
*/
|
||||
checkNode(
|
||||
node: NodeInterface,
|
||||
attributes?: SchemaAttributes | SchemaStyle,
|
||||
attributes: SchemaAttributes | SchemaStyle = {},
|
||||
): boolean {
|
||||
//获取节点属性
|
||||
const nodeAttributes = node.attributes();
|
||||
const nodeStyles = node.css();
|
||||
delete nodeAttributes['style'];
|
||||
const styles = (attributes || {}).style as SchemaAttributes;
|
||||
attributes = omit({ ...attributes }, 'style');
|
||||
const styles = (attributes.style || {}) as SchemaAttributes;
|
||||
//需要属性每一项都能效验通过
|
||||
const attrResult = Object.keys(attributes).every((attributesName) => {
|
||||
if (attributesName === 'style') return true;
|
||||
return this.checkValue(
|
||||
attributes as SchemaAttributes,
|
||||
attributesName,
|
||||
|
@ -214,7 +210,7 @@ class Schema implements SchemaInterface {
|
|||
);
|
||||
});
|
||||
if (!attrResult) return false;
|
||||
return Object.keys(styles || {}).every((styleName) => {
|
||||
return Object.keys(styles).every((styleName) => {
|
||||
return this.checkValue(styles, styleName, nodeStyles[styleName]);
|
||||
});
|
||||
}
|
||||
|
@ -324,15 +320,14 @@ 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) {
|
||||
delete styles[styleName];
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!this.checkValue(
|
||||
rule?.attributes?.style! as SchemaAttributes,
|
||||
rule.attributes!.style as SchemaAttributes,
|
||||
styleName,
|
||||
styles[styleName],
|
||||
true,
|
||||
|
@ -347,15 +342,14 @@ 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) {
|
||||
delete attributes[attributesName];
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!this.checkValue(
|
||||
rule?.attributes! as SchemaAttributes,
|
||||
rule.attributes as SchemaAttributes,
|
||||
attributesName,
|
||||
attributes[attributesName],
|
||||
true,
|
||||
|
@ -379,18 +373,18 @@ class Schema implements SchemaInterface {
|
|||
) {
|
||||
const rule = this.getRule(node);
|
||||
if (!rule) return;
|
||||
let allRule: SchemaRule = { ...rule };
|
||||
const globalRule = Object.keys(this.data.globals).find(
|
||||
const { globals } = this.data;
|
||||
const globalRule = Object.keys(globals).find(
|
||||
(dataType) => rule.type === dataType,
|
||||
);
|
||||
if (globalRule) {
|
||||
allRule.attributes = merge(
|
||||
{
|
||||
...allRule.attributes,
|
||||
},
|
||||
{ ...this.data.globals[globalRule] },
|
||||
);
|
||||
}
|
||||
const allRule = {
|
||||
...omit(rule, 'attributes'),
|
||||
attributes: merge(
|
||||
{},
|
||||
rule.attributes,
|
||||
globalRule ? globals[globalRule] : {},
|
||||
),
|
||||
};
|
||||
this.filterAttributes(attributes, allRule);
|
||||
this.filterStyles(styles, allRule);
|
||||
}
|
||||
|
@ -403,19 +397,18 @@ class Schema implements SchemaInterface {
|
|||
*/
|
||||
closest(name: string) {
|
||||
let topName = name;
|
||||
this.data.blocks
|
||||
.filter((rule) => rule.name === name)
|
||||
.forEach((block) => {
|
||||
const schema = block as SchemaBlock;
|
||||
if (schema.allowIn) {
|
||||
schema.allowIn.forEach((parentName) => {
|
||||
if (this.isAllowIn(parentName, topName)) {
|
||||
topName = parentName;
|
||||
}
|
||||
});
|
||||
topName = this.closest(topName);
|
||||
}
|
||||
});
|
||||
this.data.blocks.forEach((block) => {
|
||||
if (block.name !== name) return;
|
||||
const schema = block as SchemaBlock;
|
||||
if (schema.allowIn) {
|
||||
schema.allowIn.forEach((parentName) => {
|
||||
if (this.isAllowIn(parentName, topName)) {
|
||||
topName = parentName;
|
||||
}
|
||||
});
|
||||
topName = this.closest(topName);
|
||||
}
|
||||
});
|
||||
return topName;
|
||||
}
|
||||
/**
|
||||
|
@ -427,15 +420,14 @@ class Schema implements SchemaInterface {
|
|||
isAllowIn(source: string, target: string) {
|
||||
//p节点下不允许放其它block节点
|
||||
if (source === 'p') return false;
|
||||
return this.data.blocks
|
||||
.filter((rule) => rule.name === target)
|
||||
.some((block) => {
|
||||
const schema = block as SchemaBlock;
|
||||
if (schema.allowIn && schema.allowIn.indexOf(source) > -1) {
|
||||
return true;
|
||||
}
|
||||
return;
|
||||
});
|
||||
return this.data.blocks.some((block) => {
|
||||
if (block.name !== target) return;
|
||||
const schema = block as SchemaBlock;
|
||||
if (schema.allowIn && schema.allowIn.indexOf(source) > -1) {
|
||||
return true;
|
||||
}
|
||||
return;
|
||||
});
|
||||
}
|
||||
addAllowIn(parent: string, child: string = 'p') {
|
||||
const rule = this.data.blocks.find(
|
||||
|
|
|
@ -23,7 +23,7 @@ import { BlockModelInterface } from './block';
|
|||
import { RequestInterface } from './request';
|
||||
import { RangeInterface } from './range';
|
||||
import { Op } from 'sharedb';
|
||||
import { NodeIdInterface } from 'src';
|
||||
import { NodeIdInterface } from './';
|
||||
|
||||
/**
|
||||
* 编辑器容器接口
|
||||
|
@ -727,7 +727,7 @@ export interface EngineInterface extends EditorInterface {
|
|||
): void;
|
||||
/**
|
||||
* 解析粘贴数据,生成符合编辑器数据的片段之后扁平化阶段触发
|
||||
* @param node 所有粘贴片段遍历后的子节点
|
||||
* @param node 所有粘贴片段遍历后的根节点
|
||||
*/
|
||||
on(
|
||||
eventType: 'paste:each-after',
|
||||
|
@ -896,7 +896,7 @@ export interface EngineInterface extends EditorInterface {
|
|||
off(eventType: 'paste:each', listener: (root: NodeInterface) => void): void;
|
||||
/**
|
||||
* 解析粘贴数据,生成符合编辑器数据的片段之后扁平化阶段触发
|
||||
* @param node 所有粘贴片段遍历后的子节点
|
||||
* @param node 所有粘贴片段遍历后的根节点
|
||||
*/
|
||||
off(
|
||||
eventType: 'paste:each-after',
|
||||
|
@ -1023,7 +1023,7 @@ export interface EngineInterface extends EditorInterface {
|
|||
trigger(eventType: 'paste:each', root: NodeInterface): void;
|
||||
/**
|
||||
* 解析粘贴数据,生成符合编辑器数据的片段之后扁平化阶段触发
|
||||
* @param node 所有粘贴片段遍历后的子节点
|
||||
* @param node 所有粘贴片段遍历后的根节点
|
||||
*/
|
||||
trigger(eventType: 'paste:each-after', root: NodeInterface): void;
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { NodeInterface } from './node';
|
||||
import { PluginInterface, ElementPluginInterface } from './plugin';
|
||||
import { RangeInterface } from './range';
|
||||
import { SchemaInterface } from './schema';
|
||||
|
||||
export interface InlineModelInterface {
|
||||
/**
|
||||
|
@ -56,7 +57,7 @@ export interface InlineModelInterface {
|
|||
* 标准化inline节点,不能嵌套在mark标签内,不能嵌套inline标签
|
||||
* @param node
|
||||
*/
|
||||
flat(node: NodeInterface | RangeInterface): void;
|
||||
flat(node: NodeInterface | RangeInterface, schema?: SchemaInterface): void;
|
||||
}
|
||||
|
||||
export interface InlineInterface extends ElementPluginInterface {
|
||||
|
|
|
@ -623,8 +623,13 @@ export interface NodeModelInterface {
|
|||
* 将源节点的子节点追加到目标节点,并替换源节点
|
||||
* @param source 旧节点
|
||||
* @param target 新节点
|
||||
* @param copyId 是否复制id
|
||||
*/
|
||||
replace(source: NodeInterface, target: NodeInterface): NodeInterface;
|
||||
replace(
|
||||
source: NodeInterface,
|
||||
target: NodeInterface,
|
||||
copyId?: boolean,
|
||||
): NodeInterface;
|
||||
/**
|
||||
* 在光标位置插入一个节点
|
||||
* @param node 节点
|
||||
|
|
|
@ -28,6 +28,21 @@ export type Callbacks = {
|
|||
};
|
||||
|
||||
export interface ParserInterface {
|
||||
/**
|
||||
* 根节点
|
||||
*/
|
||||
root: NodeInterface;
|
||||
/**
|
||||
* 标准化节点
|
||||
* @param root 根节点
|
||||
* @param schema Schema
|
||||
* @param conversion 转换规则
|
||||
*/
|
||||
normalize(
|
||||
root: NodeInterface,
|
||||
schema: SchemaInterface,
|
||||
conversion: ConversionInterface | null,
|
||||
): void;
|
||||
/**
|
||||
* 遍历节点
|
||||
* @param node 根节点
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
RangeInterface,
|
||||
TypingHandleInterface,
|
||||
} from '../../types';
|
||||
import { getWindow } from '../../utils';
|
||||
import { CARD_KEY } from '../../constants';
|
||||
import Range from '../../range';
|
||||
import { $ } from '../../node';
|
||||
|
@ -72,7 +71,7 @@ class Delete implements TypingHandleInterface {
|
|||
range.collapse(true);
|
||||
return range;
|
||||
}
|
||||
if (node.nodeType === getWindow().Node.TEXT_NODE) {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
if (node['data'].length === 0) return this.getRange(node);
|
||||
if (!node.ownerDocument) return null;
|
||||
const range = Range.create(this.engine, node.ownerDocument);
|
||||
|
@ -107,7 +106,7 @@ class Delete implements TypingHandleInterface {
|
|||
}
|
||||
if (!card.isRightCursor(range.startNode)) return;
|
||||
nextNode = card.root[0];
|
||||
} else if (range.endContainer.nodeType === getWindow().Node.TEXT_NODE) {
|
||||
} else if (range.endContainer.nodeType === Node.TEXT_NODE) {
|
||||
if (range.endContainer['data'].length > range.endOffset) {
|
||||
event.preventDefault();
|
||||
const cloneRange = range.cloneRange();
|
||||
|
@ -119,8 +118,7 @@ class Delete implements TypingHandleInterface {
|
|||
}
|
||||
nextNode = range.endContainer;
|
||||
} else {
|
||||
if (range.endContainer.nodeType !== getWindow().Node.ELEMENT_NODE)
|
||||
return;
|
||||
if (range.endContainer.nodeType !== Node.ELEMENT_NODE) return;
|
||||
if (range.endContainer.childNodes.length === 0) {
|
||||
nextNode = range.endContainer;
|
||||
} else if (range.endOffset === 0) {
|
||||
|
|
|
@ -16,18 +16,6 @@ export const getDocument = (node?: Node): Document => {
|
|||
: document;
|
||||
};
|
||||
|
||||
export const getWindow = (node?: Node): Window & typeof globalThis => {
|
||||
if (
|
||||
typeof window === 'undefined' &&
|
||||
typeof global['__amWindow'] === 'undefined'
|
||||
)
|
||||
throw 'window is not defined,If you are using ssr, you can assign a value to the `__amWindow` global variable.';
|
||||
const win = typeof window === 'undefined' ? global['__amWindow'] : window;
|
||||
if (!node) return win;
|
||||
const document = getDocument(node);
|
||||
return document['parentWindow'] || document.defaultView || win;
|
||||
};
|
||||
|
||||
/**
|
||||
* 移除空的文本节点,并连接相邻的文本节点
|
||||
* @param node 节点
|
||||
|
|
|
@ -5,8 +5,6 @@ import {
|
|||
READY_CARD_KEY,
|
||||
} from '../constants/card';
|
||||
import { DATA_ELEMENT } from '../constants/root';
|
||||
|
||||
import { getWindow } from './node';
|
||||
import { isMacos } from './user-agent';
|
||||
|
||||
/**
|
||||
|
@ -44,7 +42,7 @@ export const toCamelCase = (
|
|||
return value
|
||||
.split('-')
|
||||
.map((str, index) => {
|
||||
if (type === 'upper' || (type === 'lower' && index > 0)) {
|
||||
if (type === 'upper' || index > 0) {
|
||||
return str.charAt(0).toUpperCase() + str.substr(1);
|
||||
}
|
||||
if (type === 'lower' && index === 0) {
|
||||
|
@ -96,6 +94,7 @@ export const getAttrMap = (value: string): { [k: string]: string } => {
|
|||
return map;
|
||||
};
|
||||
|
||||
const stylesCaches = new Map<string, { [k: string]: string }>();
|
||||
/**
|
||||
* 将 style 样式转换为 map 数据类型
|
||||
* @param {string} style
|
||||
|
@ -103,15 +102,21 @@ export const getAttrMap = (value: string): { [k: string]: string } => {
|
|||
export const getStyleMap = (style: string): { [k: string]: string } => {
|
||||
style = style.replace(/"/g, '"');
|
||||
const map: { [k: string]: string } = {};
|
||||
if (!style) return map;
|
||||
const cacheStyle = stylesCaches.get(style);
|
||||
if (cacheStyle) return cacheStyle;
|
||||
const reg = /\s*([\w\-]+)\s*:([^;]*)(;|$)/g;
|
||||
let match;
|
||||
|
||||
while ((match = reg.exec(style))) {
|
||||
const key = match[1].toLowerCase().trim();
|
||||
const val = toHex(match[2]).trim();
|
||||
let val = match[2].trim();
|
||||
if (val.toLowerCase().includes('rgb')) {
|
||||
val = toHex(match[2]);
|
||||
}
|
||||
map[key] = val;
|
||||
}
|
||||
|
||||
stylesCaches.set(style, map);
|
||||
return map;
|
||||
};
|
||||
|
||||
|
@ -121,9 +126,8 @@ export const getStyleMap = (style: string): { [k: string]: string } => {
|
|||
* @param {string} attrName
|
||||
*/
|
||||
export const getComputedStyle = (element: Element, attrName: string) => {
|
||||
const win = getWindow(element);
|
||||
const camelKey = toCamelCase(attrName);
|
||||
const style = win?.getComputedStyle(element, null);
|
||||
const style = window.getComputedStyle(element, null);
|
||||
return style ? style[camelKey] : '';
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { getWindow } from './node';
|
||||
|
||||
const userAgent = (
|
||||
typeof navigator !== 'undefined' ? navigator : getWindow().navigator
|
||||
typeof navigator !== 'undefined' ? navigator : window.navigator
|
||||
).userAgent.toLowerCase();
|
||||
export const isServer = typeof navigator === 'undefined';
|
||||
/**
|
||||
|
|
|
@ -27,7 +27,7 @@ export const autoGetHotkey = (
|
|||
}
|
||||
return;
|
||||
};
|
||||
|
||||
const supportFontFamilyCache: { [key: string]: boolean } = {};
|
||||
/**
|
||||
* 是否支持字体
|
||||
* @param font 字体名称
|
||||
|
@ -38,7 +38,8 @@ export const isSupportFontFamily = (font: string) => {
|
|||
console.log('Font name is not legal !');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (supportFontFamilyCache[font] !== undefined)
|
||||
return supportFontFamilyCache[font];
|
||||
let width;
|
||||
const body = document.body;
|
||||
|
||||
|
@ -64,9 +65,10 @@ export const isSupportFontFamily = (font: string) => {
|
|||
const serifWidth = getWidth('serif');
|
||||
const sansWidth = getWidth('sans-serif');
|
||||
|
||||
return (
|
||||
const reuslt =
|
||||
monoWidth !== getWidth(font + ',monospace') ||
|
||||
sansWidth !== getWidth(font + ',sans-serif') ||
|
||||
serifWidth !== getWidth(font + ',serif')
|
||||
);
|
||||
serifWidth !== getWidth(font + ',serif');
|
||||
supportFontFamilyCache[font] = reuslt;
|
||||
return reuslt;
|
||||
};
|
||||
|
|
|
@ -27,7 +27,7 @@ export const autoGetHotkey = (
|
|||
}
|
||||
return;
|
||||
};
|
||||
|
||||
const supportFontFamilyCache: { [key: string]: boolean } = {};
|
||||
/**
|
||||
* 是否支持字体
|
||||
* @param font 字体名称
|
||||
|
@ -38,7 +38,8 @@ export const isSupportFontFamily = (font: string) => {
|
|||
console.log('Font name is not legal !');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (supportFontFamilyCache[font] !== undefined)
|
||||
return supportFontFamilyCache[font];
|
||||
let width;
|
||||
const body = document.body;
|
||||
|
||||
|
@ -64,9 +65,10 @@ export const isSupportFontFamily = (font: string) => {
|
|||
const serifWidth = getWidth('serif');
|
||||
const sansWidth = getWidth('sans-serif');
|
||||
|
||||
return (
|
||||
const reuslt =
|
||||
monoWidth !== getWidth(font + ',monospace') ||
|
||||
sansWidth !== getWidth(font + ',sans-serif') ||
|
||||
serifWidth !== getWidth(font + ',serif')
|
||||
);
|
||||
serifWidth !== getWidth(font + ',serif');
|
||||
supportFontFamilyCache[font] = reuslt;
|
||||
return reuslt;
|
||||
};
|
||||
|
|
|
@ -75,7 +75,8 @@ export default class extends MarkPlugin<Options> {
|
|||
pasteEach(node: NodeInterface) {
|
||||
//pt 转为px
|
||||
if (node.name === this.tagName) {
|
||||
const fontFamily = node.css(this.#styleName);
|
||||
const styles = node.css();
|
||||
const fontFamily = styles[this.#styleName];
|
||||
if (!fontFamily) return;
|
||||
const { filter } = this.options;
|
||||
if (filter) {
|
||||
|
|
|
@ -2,7 +2,6 @@ import { PswpInterface } from '@/types';
|
|||
import {
|
||||
$,
|
||||
EditorInterface,
|
||||
getWindow,
|
||||
isEngine,
|
||||
escape,
|
||||
NodeInterface,
|
||||
|
@ -83,7 +82,7 @@ export type Options = {
|
|||
onError?: () => void;
|
||||
};
|
||||
|
||||
export const winPixelRatio = getWindow().devicePixelRatio;
|
||||
export const winPixelRatio = window.devicePixelRatio;
|
||||
let pswp: PswpInterface | undefined = undefined;
|
||||
class Image {
|
||||
private editor: EditorInterface;
|
||||
|
|
|
@ -4,7 +4,6 @@ import PhotoSwipeUI from 'photoswipe/dist/photoswipe-ui-default';
|
|||
import {
|
||||
$,
|
||||
EditorInterface,
|
||||
getWindow,
|
||||
isHotkey,
|
||||
isMobile,
|
||||
NodeInterface,
|
||||
|
@ -125,7 +124,7 @@ class Pswp extends EventEmitter2 implements PswpInterface {
|
|||
bindClickEvent() {
|
||||
const onClick = (event: MouseEvent | TouchEvent) => {
|
||||
const node =
|
||||
getWindow().TouchEvent && event instanceof TouchEvent
|
||||
window.TouchEvent && event instanceof TouchEvent
|
||||
? $(event.touches[0].target)
|
||||
: $(event.target || []);
|
||||
if (node.hasClass('pswp__img')) {
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
import {
|
||||
$,
|
||||
NodeInterface,
|
||||
EventListener,
|
||||
isMobile,
|
||||
getWindow,
|
||||
} from '@aomao/engine';
|
||||
import { $, NodeInterface, EventListener, isMobile } from '@aomao/engine';
|
||||
import './index.css';
|
||||
|
||||
export type Options = {
|
||||
|
@ -93,11 +87,11 @@ class Resizer {
|
|||
);
|
||||
this.point = {
|
||||
x:
|
||||
getWindow().TouchEvent && event instanceof TouchEvent
|
||||
TouchEvent && event instanceof TouchEvent
|
||||
? event.touches[0].clientX
|
||||
: (event as MouseEvent).clientX,
|
||||
y:
|
||||
getWindow().TouchEvent && event instanceof TouchEvent
|
||||
TouchEvent && event instanceof TouchEvent
|
||||
? event.touches[0].clientY
|
||||
: (event as MouseEvent).clientY,
|
||||
};
|
||||
|
@ -122,7 +116,7 @@ class Resizer {
|
|||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const { clientX, clientY } =
|
||||
getWindow().TouchEvent && event instanceof TouchEvent
|
||||
TouchEvent && event instanceof TouchEvent
|
||||
? event.touches[0]
|
||||
: (event as MouseEvent);
|
||||
|
||||
|
|
|
@ -282,8 +282,13 @@ export default class extends Plugin<Options> {
|
|||
|
||||
pasteEach(node: NodeInterface) {
|
||||
//pt 转为px
|
||||
if (!node.isCard() && this.editor.node.isBlock(node)) {
|
||||
const textIndentSource = node.css(TEXT_INENT_KEY);
|
||||
if (
|
||||
node.isElement() &&
|
||||
!node.isCard() &&
|
||||
this.editor.node.isBlock(node)
|
||||
) {
|
||||
const styles = node.css();
|
||||
const textIndentSource = styles[TEXT_INENT_KEY];
|
||||
if (!!textIndentSource && textIndentSource.endsWith('pt')) {
|
||||
const textIndent = this.convertToPX(textIndentSource);
|
||||
if (!!textIndent) {
|
||||
|
|
|
@ -90,14 +90,11 @@ export default class extends InlinePlugin<Options> {
|
|||
|
||||
query() {
|
||||
if (!isEngine(this.editor)) return;
|
||||
const { change, inline } = this.editor;
|
||||
const range = change.range.get();
|
||||
const inlineNode = inline
|
||||
.findInlines(range)
|
||||
.find((node) => this.isSelf(node));
|
||||
const { change } = this.editor;
|
||||
const inlineNode = change.inlines.find((node) => this.isSelf(node));
|
||||
this.toolbar?.hide(inlineNode);
|
||||
if (inlineNode && !inlineNode.isCard()) {
|
||||
if (range.collapsed) this.toolbar?.show(inlineNode);
|
||||
this.toolbar?.show(inlineNode);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -94,14 +94,11 @@ export default class extends InlinePlugin<Options> {
|
|||
|
||||
query() {
|
||||
if (!isEngine(this.editor)) return;
|
||||
const { change, inline } = this.editor;
|
||||
const range = change.range.get();
|
||||
const inlineNode = inline
|
||||
.findInlines(range)
|
||||
.find((node) => this.isSelf(node));
|
||||
const { change } = this.editor;
|
||||
const inlineNode = change.inlines.find((node) => this.isSelf(node));
|
||||
this.toolbar?.hide(inlineNode);
|
||||
if (inlineNode && !inlineNode.isCard()) {
|
||||
if (range.collapsed) this.toolbar?.show(inlineNode);
|
||||
if (inlineNode && inlineNode.length > 0 && !inlineNode.isCard()) {
|
||||
this.toolbar?.show(inlineNode);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -572,7 +572,7 @@ class TableCommand extends EventEmitter2 implements TableCommandInterface {
|
|||
conversion,
|
||||
);
|
||||
const element = helper.trimBlankSpan($(pasteHTML));
|
||||
|
||||
this.editor.nodeId.generateAll(element, true);
|
||||
if (element.name === 'table') {
|
||||
helper.normalizeTable(element);
|
||||
const pasteTableModel = helper.getTableModel(element);
|
||||
|
|
|
@ -363,9 +363,14 @@ class TableComponent extends Card<TableValue> implements TableInterface {
|
|||
}
|
||||
});
|
||||
this.scrollbar.disableScroll();
|
||||
this.scrollbar.on('change', () => {
|
||||
if (isEngine(this.editor)) this.editor.ot.initSelection();
|
||||
});
|
||||
let changeTimeout: NodeJS.Timeout | undefined;
|
||||
const handleScrollbarChange = () => {
|
||||
if (changeTimeout) clearTimeout(changeTimeout);
|
||||
changeTimeout = setTimeout(() => {
|
||||
if (isEngine(this.editor)) this.editor.ot.initSelection();
|
||||
}, 50);
|
||||
};
|
||||
this.scrollbar.on('change', handleScrollbarChange);
|
||||
}
|
||||
this.scrollbar?.refresh();
|
||||
this.selection.on('select', () => {
|
||||
|
|
|
@ -122,7 +122,7 @@ div[data-card-key="table"].card-selected .data-table, div[data-card-key="table"]
|
|||
width: 100%;
|
||||
cursor: default;
|
||||
margin-bottom: -1px;
|
||||
z-index: 1;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.table-wrapper.active .table-cols-header {
|
||||
|
|
|
@ -31,7 +31,7 @@ class Table extends Plugin<Options> {
|
|||
editor.schema.add(this.schema());
|
||||
editor.conversion.add('th', 'td');
|
||||
editor.on('parse:html', (node) => this.parseHtml(node));
|
||||
editor.on('paste:each-after', (child) => this.pasteHtml(child));
|
||||
editor.on('paste:each-after', (root) => this.pasteHtml(root));
|
||||
editor.on('paste:schema', (schema: SchemaInterface) =>
|
||||
this.pasteSchema(schema),
|
||||
);
|
||||
|
@ -266,7 +266,7 @@ class Table extends Plugin<Options> {
|
|||
return value;
|
||||
}
|
||||
|
||||
pasteHtml(node: NodeInterface) {
|
||||
pasteHtml(root: NodeInterface) {
|
||||
if (!isEngine(this.editor)) return;
|
||||
const clearWH = (
|
||||
node: NodeInterface,
|
||||
|
@ -277,7 +277,10 @@ class Table extends Plugin<Options> {
|
|||
if (width.endsWith('%')) node.css(type, '');
|
||||
if (width.endsWith('pt')) node.css(type, this.convertToPX(width));
|
||||
};
|
||||
if (node.name === 'table') {
|
||||
const tables = root.find('table');
|
||||
tables.each((_, index) => {
|
||||
const node = tables.eq(index);
|
||||
if (!node) return;
|
||||
clearWH(node);
|
||||
clearWH(node, 'height');
|
||||
// 表头放在tbody最前面
|
||||
|
@ -339,9 +342,7 @@ class Table extends Plugin<Options> {
|
|||
.outerHTML.replace(/\n|\r\n/g, '')
|
||||
.replace(/>\s+</g, '><'),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
parseHtml(root: NodeInterface) {
|
||||
|
|
|
@ -49,11 +49,13 @@ export default class extends ListPlugin<Options> {
|
|||
this.editor.on('paste:markdown', (child) =>
|
||||
this.pasteMarkdown(child),
|
||||
);
|
||||
this.editor.on('paste:each-after', (child) => {
|
||||
if (
|
||||
child.name === 'li' &&
|
||||
child.hasClass(this.editor.list.CUSTOMZIE_LI_CLASS)
|
||||
) {
|
||||
this.editor.on('paste:each-after', (root) => {
|
||||
const liNodes = root.find(
|
||||
`li.${this.editor.list.CUSTOMZIE_LI_CLASS}`,
|
||||
);
|
||||
liNodes.each((_, index) => {
|
||||
const child = liNodes.eq(index);
|
||||
if (!child) return;
|
||||
const firstChild = child.first();
|
||||
if (
|
||||
firstChild &&
|
||||
|
@ -71,7 +73,7 @@ export default class extends ListPlugin<Options> {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue