fix: parse list nested html and markdown
This commit is contained in:
parent
a8d37cc58f
commit
15d8e848c7
|
@ -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
|
||||
|
|
|
@ -946,10 +946,6 @@ export default class extends Plugin {
|
|||
- `border` 边框变化
|
||||
- `background` 背景颜色变化
|
||||
|
||||
### `toolbarFollowMouse`
|
||||
|
||||
卡片工具栏是否跟随鼠标位置,默认 flase
|
||||
|
||||
### `lazyRender`
|
||||
|
||||
是否启用懒加载,卡片节点在视图内可见时触发渲染
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -119,10 +119,6 @@ export interface CardEntry<T extends CardValue = {}> {
|
|||
* 卡片选中后的样式效果,默认为 border
|
||||
*/
|
||||
readonly selectStyleType: SelectStyleType;
|
||||
/**
|
||||
* toolbar 跟随鼠标点击位置
|
||||
*/
|
||||
readonly toolbarFollowMouse: boolean;
|
||||
/**
|
||||
* 是否在卡片处于视图内时才渲染,默认 false
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() &&
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue