fix: parse list nested html and markdown

This commit is contained in:
yanmao 2021-12-26 23:03:56 +08:00
parent a8d37cc58f
commit 15d8e848c7
12 changed files with 204 additions and 60 deletions

View File

@ -945,10 +945,6 @@ The style of the selected yes, the default is the border change, optional values
- `border` border changes
- `background` background color change
### `toolbarFollowMouse`
Whether the card toolbar follows the mouse position, the default flase
### `lazyRender`
Whether to enable lazy loading, the rendering is triggered when the card node is visible in the view

View File

@ -946,10 +946,6 @@ export default class extends Plugin {
- `border` 边框变化
- `background` 背景颜色变化
### `toolbarFollowMouse`
卡片工具栏是否跟随鼠标位置,默认 flase
### `lazyRender`
是否启用懒加载,卡片节点在视图内可见时触发渲染

View File

@ -50,7 +50,6 @@ abstract class CardEntry<T extends CardValue = {}> implements CardInterface<T> {
static readonly collab: boolean = true;
static readonly focus: boolean;
static readonly selectStyleType: SelectStyleType = SelectStyleType.BORDER;
static readonly toolbarFollowMouse: boolean = false;
static readonly lazyRender: boolean = false;
private defaultMaximize: MaximizeInterface;
isMaximize: boolean = false;

View File

@ -479,14 +479,9 @@ class ChangeModel implements ChangeInterface {
}
// 被删除了重新设置开始节点位置
if (startRange && !startRange.node[0].parentNode) {
const children = node.children();
startRange = {
node: node,
offset:
children.length === 1 &&
children[0].nodeName === 'BR'
? 0
: range.startOffset,
node: appendNodes[0],
offset: 0,
};
}
node = next;

View File

@ -162,6 +162,57 @@ export default class Paste {
ul.append(node);
return;
}
if (nodeApi.isList(node) && parent && nodeApi.isList(parent)) {
// 分割付节点list
const leftList: NodeInterface[] = [];
const rightList: NodeInterface[] = [];
let isLeft = true;
const rootChildren = parent.children().toArray();
let tempList = parent.clone();
const appendToTemp = () => {
if (tempList.children().length > 0) {
if (isLeft) leftList.push(tempList);
else rightList.push(tempList);
tempList = parent!.clone();
return true;
}
return false;
};
rootChildren.forEach((child) => {
if (!child) return;
if (child.equal(node)) {
isLeft = false;
return;
}
if (child.name === 'li') {
tempList.append(child);
return;
} else {
appendToTemp();
}
if (isLeft) leftList.push(child);
else rightList.push(child);
});
appendToTemp();
const indent = parent.attributes('data-indent') || '0';
this.engine.list.addIndent(node, parseInt(indent, 10));
leftList.push(node);
let prev = parent;
leftList.forEach((childNode) => {
const child = $(childNode);
if (!child || child.children().length === 0) return;
prev.after(child);
prev = child;
});
rightList.forEach((childNode) => {
const child = $(childNode);
if (!child || child.children().length === 0) return;
prev.after(child);
prev = child;
});
parent.remove();
return node.next() || undefined;
}
// 补齐 li
if (node.name !== 'li' && parentIsList) {
const li = $('<li />');
@ -170,33 +221,92 @@ export default class Paste {
return;
}
// <li>two<ol><li>three</li></ol>four</li>
if (
nodeApi.isList(node) &&
parent?.name === 'li' &&
(node.prev() || node.next())
) {
let li: NodeInterface | null;
/**
* <ul>
<li>/placeholder的功能</li>
<li></li>
<li>
<ul><li></li></ul>
</li>
<li>
<ul><li></li></ul>
</li>
</ul>
*/
if (nodeApi.isList(node) && parent?.name === 'li') {
// li没有父节点就移除包裹
const rootListElement = parent?.parent();
if (!rootListElement) {
nodeApi.unwrap(parent);
return;
}
// 分割付节点list
const leftList = rootListElement.clone();
const rightList = rootListElement.clone();
let isLeft = true;
const rootChildren = rootListElement.children().toArray();
rootChildren.forEach((child) => {
if (!child) return;
if (child.equal(parent!)) {
isLeft = false;
return;
}
if (isLeft) leftList.append(child);
else rightList.append(child);
});
const isCustomizeList = parent?.parent()?.hasClass('data-list');
const children = parent?.children();
let li: NodeInterface | null = null;
let next: NodeInterface | null = null;
children.each((child, index) => {
const node = children.eq(index);
if (!node || nodeApi.isEmptyWithTrim(node)) {
return;
}
const isList = nodeApi.isList(node);
if (!li || isList) {
const leftLast = leftList[leftList.length - 1];
if (isList) {
const indent =
$(leftLast)?.attributes('data-indent') || '0';
this.engine.list.addIndent(node, parseInt(indent, 10));
leftList[leftList.length] = node[0];
li = null;
return;
}
if (!li) {
li = isCustomizeList
? $('<li class="data-list-item" />')
: $('<li />');
parent?.before(li);
const last = $(leftLast)?.last();
if (last) last?.after(li);
else $(leftLast).append(li);
}
li.append(child);
if (isList) {
li = null;
if (!next) {
next = li;
}
});
parent?.remove();
return;
let prev = rootListElement;
leftList.each((childNode) => {
const child = $(childNode);
if (!child || child.children().length === 0) return;
prev.after(child);
prev = child;
});
rightList.each((childNode) => {
const child = $(childNode);
if (!child || child.children().length === 0) return;
prev.after(child);
prev = child;
});
rootListElement.remove();
return (
(next as NodeInterface | null)?.next() ||
leftList.next() ||
void 0
);
}
// p 改成 li
if (node.name === 'p' && parentIsList) {

View File

@ -939,7 +939,7 @@ class NodeEntry implements NodeInterface {
}
traverse(
callback: (node: NodeInterface) => boolean | void,
callback: (node: NodeInterface) => boolean | void | NodeInterface,
order: boolean = true,
includeEditableCard: boolean = false,
) {
@ -962,8 +962,10 @@ class NodeEntry implements NodeInterface {
if (result === false) {
return;
}
if (result !== true) {
if (result && typeof result !== 'boolean') {
child = result;
}
if (includeEditableCard && child.isEditableCard()) {
const editableElements = child.find(EDITABLE_SELECTOR);
editableElements.each((_, index) => {

View File

@ -119,10 +119,6 @@ export interface CardEntry<T extends CardValue = {}> {
* border
*/
readonly selectStyleType: SelectStyleType;
/**
* toolbar
*/
readonly toolbarFollowMouse: boolean;
/**
* false
*/

View File

@ -477,7 +477,7 @@ export interface NodeInterface {
* @param includeEditableCard
*/
traverse(
callback: (node: NodeInterface) => boolean | void,
callback: (node: NodeInterface) => boolean | void | NodeInterface,
order?: boolean,
includeEditableCard?: boolean,
): void;

View File

@ -51,10 +51,20 @@ class ToolbarPlugin<T extends ToolbarOptions> extends Plugin<T> {
if (isEngine(this.editor)) {
this.editor.on('keydown:slash', (event) => this.onSlash(event));
this.editor.on('parse:value', (node) => this.paserValue(node));
//this.editor.on('select', this.onSelect)
}
this.editor.language.add(locales);
}
// onSelect = () => {
// if (!isEngine(this.editor)) return;
// const { change, card } = this.editor;
// if(card.active) return
// const range = change.range.get().cloneRange().shrinkToTextNode();
// if(range.collapsed) return
// const { startNode, endNode } = range
// }
paserValue(node: NodeInterface) {
if (
node.isCard() &&

View File

@ -118,7 +118,7 @@ export default class<T extends OrderedListOptions> extends ListPlugin<T> {
const text = node.text();
if (!text) return;
const reg = /(^|\r\n|\n)(\d{1,9}\.)/;
const reg = /(^|\r\n|\n)\s*(\d{1,9}\.)/;
const match = reg.exec(text);
return {
reg,
@ -134,12 +134,20 @@ export default class<T extends OrderedListOptions> extends ListPlugin<T> {
const { list } = this.editor;
const createList = (nodes: Array<string>, start?: number) => {
const createList = (
nodes: Array<string>,
start?: number,
indent?: number,
) => {
const listNode = $(
`<${this.tagName} start="${start || 1}">${nodes.join('')}</${
this.tagName
}>`,
`<${this.tagName}>${nodes.join('')}</${this.tagName}>`,
);
if (start) {
listNode.attributes('start', start);
}
if (indent) {
listNode.attributes(this.editor.list.INDENT_KEY, indent);
}
list.addBr(listNode);
return listNode.get<Element>()?.outerHTML;
};
@ -147,21 +155,27 @@ export default class<T extends OrderedListOptions> extends ListPlugin<T> {
let newText = '';
const rows = text.split(/\n|\r\n/);
let nodes: Array<string> = [];
let indent = 0;
let start: number | undefined = undefined;
rows.forEach((row) => {
const match = /^(\d{1,9}\.)/.exec(row);
const match = /^(\s*)(\d{1,9}\.)/.exec(row);
if (match) {
const codeLength = match[1].length;
const codeLength = match[2].length;
if (start === undefined)
start = parseInt(match[1].substr(0, codeLength - 1), 10);
start = parseInt(match[2].substr(0, codeLength - 1), 10);
const content = row.substr(
/^\s+/.test(row.substr(codeLength))
(/^\s+/.test(row.substr(codeLength))
? codeLength + 1
: codeLength,
: codeLength) + match[1].length,
);
if (match[1].length !== indent && nodes.length > 0) {
newText += createList(nodes, undefined, indent);
nodes = [];
indent = Math.ceil(match[1].length / 2);
}
nodes.push(`<li>${content}</li>`);
} else if (nodes.length > 0) {
newText += createList(nodes, start) + '\n' + row + '\n';
newText += createList(nodes, start, indent) + '\n' + row + '\n';
nodes = [];
start = undefined;
} else {
@ -169,7 +183,7 @@ export default class<T extends OrderedListOptions> extends ListPlugin<T> {
}
});
if (nodes.length > 0) {
newText += createList(nodes, start) + '\n';
newText += createList(nodes, start, indent) + '\n';
}
node.text(newText);
}

View File

@ -27,6 +27,7 @@ export default class<T extends TasklistOptions> extends ListPlugin<T> {
attributes = {
class: '@var0',
'data-indent': '@var1',
};
variable = {
@ -34,6 +35,7 @@ export default class<T extends TasklistOptions> extends ListPlugin<T> {
required: true,
value: [this.editor.list.CUSTOMZIE_UL_CLASS, 'data-list-task'],
},
'@var1': '@number',
};
allowIn = ['blockquote', '$root'];
@ -244,12 +246,15 @@ export default class<T extends TasklistOptions> extends ListPlugin<T> {
const { list, card } = this.editor;
const createList = (nodes: Array<string>) => {
const createList = (nodes: Array<string>, indent?: number) => {
const listNode = $(
`<${this.tagName} class="${
list.CUSTOMZIE_UL_CLASS
} data-list-task">${nodes.join('')}</${this.tagName}>`,
);
if (indent) {
listNode.attributes(this.editor.list.INDENT_KEY, indent);
}
list.addBr(listNode);
return listNode.get<Element>()?.outerHTML;
};
@ -257,8 +262,9 @@ export default class<T extends TasklistOptions> extends ListPlugin<T> {
let newText = '';
const rows = text.split(/\n|\r\n/);
let nodes: Array<string> = [];
let indent = 0;
rows.forEach((row) => {
const match = /^(-\s*)?(\[[\sx]{0,1}\])/.exec(row);
const match = /^(\s*)(-\s*)?(\[[\sx]{0,1}\])/.exec(row);
if (match && !/(\[(.*)\]\(([\S]+?)\))/.test(row)) {
const codeLength = match[0].length;
const content = row.substr(
@ -275,20 +281,25 @@ export default class<T extends TasklistOptions> extends ListPlugin<T> {
},
);
tempNode.remove();
if (match[1].length !== indent && nodes.length > 0) {
newText += createList(nodes, indent);
nodes = [];
indent = Math.ceil(match[1].length / 2);
}
nodes.push(
`<li class="${list.CUSTOMZIE_LI_CLASS}">${
cardNode.get<Element>()?.outerHTML
}${content}</li>`,
);
} else if (nodes.length > 0) {
newText += createList(nodes) + '\n' + row + '\n';
newText += createList(nodes, indent) + '\n' + row + '\n';
nodes = [];
} else {
newText += row + '\n';
}
});
if (nodes.length > 0) {
newText += createList(nodes) + '\n';
newText += createList(nodes, indent) + '\n';
}
node.text(newText);
}

View File

@ -120,7 +120,7 @@ export default class<T extends UnorderedlistOptions> extends ListPlugin<T> {
const text = node.text();
if (!text) return;
const reg = /(^|\r\n|\n)([\*\-\+]{1,}\s+)/g;
const reg = /(^|\r\n|\n)\s*([\*\-\+]{1,}\s+)/g;
const match = reg.exec(text);
return {
reg,
@ -137,12 +137,20 @@ export default class<T extends UnorderedlistOptions> extends ListPlugin<T> {
const { list } = this.editor;
const createList = (nodes: Array<string>, start?: number) => {
const createList = (
nodes: Array<string>,
start?: number,
indent?: number,
) => {
const listNode = $(
`<${this.tagName} start="${start || 1}">${nodes.join('')}</${
this.tagName
}>`,
`<${this.tagName}>${nodes.join('')}</${this.tagName}>`,
);
if (start) {
listNode.attributes('start', start);
}
if (indent) {
listNode.attributes(this.editor.list.INDENT_KEY, indent);
}
list.addBr(listNode);
return listNode.get<Element>()?.outerHTML;
};
@ -150,21 +158,28 @@ export default class<T extends UnorderedlistOptions> extends ListPlugin<T> {
let newText = '';
const rows = text.split(/\n|\r\n/);
let nodes: Array<string> = [];
let indent = 0;
rows.forEach((row) => {
const match = /^([\*\-\+]{1,}\s+)/.exec(row);
const match = /^(\s*)([\*\-\+]{1,}\s+)/.exec(row);
if (match) {
const codeLength = match[1].length;
const content = row.substr(codeLength);
const codeLength = match[2].length;
const content = row.substr(codeLength + match[1].length);
if (match[1].length !== indent && nodes.length > 0) {
newText += createList(nodes, undefined, indent);
nodes = [];
indent = Math.ceil(match[1].length / 2);
}
nodes.push(`<li>${content}</li>`);
} else if (nodes.length > 0) {
newText += createList(nodes) + '\n' + row + '\n';
newText +=
createList(nodes, undefined, indent) + '\n' + row + '\n';
nodes = [];
} else {
newText += row + '\n';
}
});
if (nodes.length > 0) {
newText += createList(nodes) + '\n';
newText += createList(nodes, undefined, indent) + '\n';
}
node.text(newText);
}