Skip to content

Commit

Permalink
refactor(list): implement list with tree
Browse files Browse the repository at this point in the history
  • Loading branch information
jiawei686 authored and humyfred committed Dec 28, 2021
1 parent 119685d commit 43ba79a
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 173 deletions.
308 changes: 152 additions & 156 deletions src/core/hooks/List.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,6 @@ function attrsToAttributeString(object) {
return attrs.join(' ');
}

function classNamesToAttributeString(array) {
if (array instanceof Array && array.length > 0) {
return attrsToAttributeString({ class: array.join(' ') });
}
return '';
}

export function makeChecklist(text) {
return text.replace(/([*+-]\s+)\[(\s|x)\]/g, (whole, pre, test) => {
const checkHtml = /\s/.test(test)
Expand All @@ -42,186 +35,189 @@ export function makeChecklist(text) {
});
}

// 缩进处理
function handleIndent(str, node) {
const indentRegex = /^(\t|[ ])/;
let $str = str;
while (indentRegex.test($str)) {
node.space += $str[0] === '\t' ? 4 : 1;
$str = $str.replace(indentRegex, '');
}
return $str;
}

// 序号样式处理
function getListStyle(m2) {
if (/^[a-z]/.test(m2)) {
return 'lower-greek';
}
if (/^[一二三四五六七八九十]/.test(m2)) {
return 'cjk-ideographic';
}
if (/^I/.test(m2)) {
return 'upper-roman';
}
if (/^\+/.test(m2)) {
return 'circle';
}
if (/^\*/.test(m2)) {
return 'square';
}
return 'default';
}

// 标识符处理
function handleMark(str, node) {
const listRegex = /^((([*+-]|\d+[.]|[a-z]\.|[I一二三四五六七八九十]+\.)[ \t]+)([^\r]+?)($|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.]|[a-z]\.|[I一二三四五六七八九十]+\.)[ \t]+)))/;
if (!listRegex.test(str)) {
node.type = 'blank';
return str;
}
return str.replace(listRegex, (wholeMatch, m1, m2, m3, m4) => {
node.type = m2.search(/[*+-]/g) > -1 ? 'ul' : 'ol';
node.listStyle = getListStyle(m2);
node.start = Number(m2.replace('.', '')) ? Number(m2.replace('.', '')) : 1;
return m4;
});
}

class Node {
// 列表树节点
constructor() {
this.index = 0;
this.space = 0;
this.type = '';
this.start = 1;
this.listStyle = '';
this.strs = [];
this.children = [];
this.lines = 0;
}
}

export default class List extends ParagraphBase {
static HOOK_NAME = 'list';

constructor({ config }) {
super({ needCache: true });
this.config = config || {};
this.intentSpace = this.config.intentSpace > 0 ? this.config.intentSpace : 2;
this.tree = [];
this.emptyLines = 0;
}

$getListStyle(type, m2) {
if (/^[a-z]/.test(m2)) {
return 'lower-greek';
}
if (/^[一二三四五六七八九十]/.test(m2)) {
return 'cjk-ideographic';
}
if (/^I/.test(m2)) {
return 'upper-roman';
}
if (/^\+/.test(m2)) {
return 'circle';
}
if (/^\*/.test(m2)) {
return 'square';
addNode(node, current, parent, last) {
if (node.type === 'blank') {
this.tree[last].strs.push(node.strs[0]);
} else {
this.tree[parent].children.push(current);
this.tree[current] = {
...node,
parent,
};
}
return 'default';
}

$wrapList(text, sign, dataLines, sentenceMakeFunc) {
const html = makeChecklist(text);
buildTree(html, sentenceMakeFunc) {
const items = html.split('\n');
this.tree = [];
items.unshift('');
// 列表结尾换行符个数
const endLineFlagLength = html.match(/\n*$/g)[0].length;
const indents = [-1];
const spaces = [-2];
const types = [null];
const listStyles = [null];
const starts = [0];
const indentRegex = /^(\t|[ ])/;
const listRegex = /^((([*+-]|\d+[.]|[a-z]\.|[I一二三四五六七八九十]+\.)[ \t]+)([^\r]+?)($|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.]|[a-z]\.|[I一二三四五六七八九十]+\.)[ \t]+)))/;
let handledHtml = '';
items.unshift('');
// 预处理
for (let i = 1; i < items.length - endLineFlagLength; i++) {
let type = '';
let space = 0;
let listStyle = 'default';
let start = 1;
// 缩进处理
while (indentRegex.test(items[i])) {
space += items[i][0] === '\t' ? 4 : 1;
items[i] = items[i].replace(indentRegex, '');
for (let i = 0; i < items.length - endLineFlagLength; i++) {
const node = new Node();
items[i] = handleIndent(items[i], node);
items[i] = handleMark(items[i], node);
node.strs.push(sentenceMakeFunc(items[i]).html);
node.index = i;
if (i === 0) {
// 根节点
node.space = -2;
this.tree.push(node);
continue;
}
spaces.push(space);

if (!listRegex.test(items[i])) {
type = 'blank';
indents.push(indents[i - 1]);
let last = i - 1;
while (!this.tree[last] || this.tree[last].space > node.space) last -= 1;
if (node.type === 'blank') {
this.addNode(node, i, this.tree[last].parent, last);
} else {
let last = i - 1;
while (last > 0 && spaces[last] > spaces[i]) last -= 1;
if (spaces[i] < spaces[last] + this.intentSpace) {
indents.push(indents[last]);
} else if (spaces[i] < spaces[last] + this.intentSpace + 4) {
indents.push(indents[last] + 1);
const { space } = node;
const lastSpace = this.tree[last].space;
if (space < lastSpace + this.intentSpace) {
// 成为同级节点
if (this.config.listNested && this.tree[last].type !== node.type) {
this.addNode(node, i, last);
} else {
this.addNode(node, i, this.tree[last].parent);
}
} else if (space < lastSpace + this.intentSpace + 4) {
// 成为子节点
this.addNode(node, i, last);
} else {
type = 'blank';
indents.push(indents[i - 1]);
// 纯文本
node.type = 'blank';
this.addNode(node, i, this.tree[last].parent, last);
}
}

// 标识符处理
if (listRegex.test(items[i]) && type !== 'blank') {
items[i] = items[i].replace(listRegex, (wholeMatch, m1, m2, m3, m4) => {
type = m2.search(/[*+-]/g) > -1 ? 'ul' : 'ol';
listStyle = this.$getListStyle(type, m2);
start = Number(m2.replace('.', '')) ? Number(m2.replace('.', '')) : 1;
return m4;
});
}
types.push(type);
listStyles.push(listStyle);
starts.push(start);
}
// 区块末尾闭合标签
indents.push(-1);
types.push(null);
listStyles.push(null);
starts.push(0);

const checklistRegex = /<span class="ch-icon ch-icon-(square|check)"><\/span>/;
// 内容处理
const listStack = [null]; // 列表类型栈
items.forEach((item, index) => {
const { html: itemWithHtml } = sentenceMakeFunc(item);
// 数组越界,跳过
if (index < 1) {
return;
}

// 无类型列表单独处理,不入栈
if (types[index] === 'blank') {
handledHtml += `<br>${itemWithHtml}`;
return;
}
}

const itemClassNames = [];
const blockAttrs = {};
// checklist 判断
if (checklistRegex.test(itemWithHtml)) {
itemClassNames.push('check-list-item');
}
renderSubTree(node, children, type) {
let lines = 0;
const attr = {};
const content = children.reduce((html, item) => {
const child = this.tree[item];
const str = `<p>${child.strs.join('<br>')}</p>`;
child.lines += child.strs.length;
const children = child.children.length ? this.renderTree(item) : '';
node.lines += child.lines;
lines += child.lines;
return `${html}<li>${str}${children}</li>`;
}, '');
if (node.parent === undefined) {
// 根节点增加属性
attr['data-lines'] = node.index === 0 ? lines + this.emptyLines : lines;
attr['data-sign'] = this.sign;
}
attr.class = `cherry-list__${this.tree[children[0]].listStyle}`;
return `<${type}${attrsToAttributeString(attr)}>${content}</${type}>`;
}

const newListItemStartTag = `<li${classNamesToAttributeString(itemClassNames)}>`;
// 存在类型列表处理
if (indents[index] > indents[index - 1]) {
if (index === 1) {
// 首行签名
blockAttrs['data-sign'] = `${sign}list${dataLines}`;
blockAttrs['data-lines'] = `${dataLines}`;
}
blockAttrs.class = `cherry-list__${listStyles[index]}`;
if (types[index] === 'ol') {
// 有序列表,需要赋值起始位置
blockAttrs.start = `${starts[index]}`;
}
listStack.unshift(types[index]); // 有序、无序列表入栈
handledHtml += `<${types[index]}${attrsToAttributeString(blockAttrs)}>${newListItemStartTag}${itemWithHtml}`;
} else if (indents[index] <= indents[index - 1]) {
// 缩进减少,列表项闭合
// delta表示需要出栈的次数
const delta = indents[index - 1] - indents[index];
for (let i = 0; i < delta && listStack.length > 1; i++) {
const closeListType = listStack.shift(); // 列表出栈,闭合
handledHtml += `</li></${closeListType}>`;
}
// 栈顶列表类型
const [currentIndentListType] = listStack;
// 如果栈顶和当前列表项类型都为null,说明列表已经结束
if (currentIndentListType === null && types[index] === null) {
return;
}
if (currentIndentListType !== types[index]) {
blockAttrs['data-sign'] = `${sign}list1`; // 首行签名
blockAttrs['data-lines'] = '1';
blockAttrs.class = `cherry-list__${listStyles[index]}`;
// 更换列表类型,先闭合列表,列表出栈
if (this.config.listNested) {
for (let i = index; i < indents.length - 1; i++) {
indents[i] += 1;
}
} else {
handledHtml += `</li></${currentIndentListType}>`;
listStack.shift();
}
if (types[index] === 'ol') {
// 有序列表,需要赋值起始位置
blockAttrs.start = `${starts[index]}`;
}
listStack.unshift(types[index]); // 有序、无序列表入栈
handledHtml += `<${types[index]}${attrsToAttributeString(blockAttrs)}>${newListItemStartTag}`;
} else {
handledHtml += `</li>${newListItemStartTag}`;
}
handledHtml += itemWithHtml;
renderTree(current) {
let from = 0;
const node = this.tree[current];
const { children } = node;
const html = children.reduce((html, item, index) => {
if (index === 0) return html;
if (this.tree[children[index]].type === this.tree[children[index - 1]].type) {
return html;
}
});
const subTree = this.renderSubTree(node, children.slice(from, index), this.tree[children[index - 1]].type);
from = index;
return html + subTree;
}, '');

return (
html +
this.renderSubTree(node, children.slice(from, children.length), this.tree[children[children.length - 1]].type)
);
}

return { html: handledHtml, sign };
toHtml(text, sentenceMakeFunc) {
this.sign = this.$engine.md5(text);
this.buildTree(makeChecklist(text), sentenceMakeFunc);
return this.renderTree(0);
}

makeHtml(str, sentenceMakeFunc) {
let $str = `${str}~0`;
if (this.test($str)) {
$str = $str.replace(this.RULE.reg, (wholeMatch, lineSpaces, m1, m2) => {
const ignoreEndLineFlags = wholeMatch.replace(/\n*(\s*~0)?$/g, '');
const lines = this.getLineCount(ignoreEndLineFlags, lineSpaces);
$str = $str.replace(this.RULE.reg, (wholeMatch) => {
// 行数计算吸收的空行
this.emptyLines = wholeMatch.match(/^\n\n/)?.length ?? 0;
const text = wholeMatch.replace(/~0$/g, '').replace(/^\n+/, '');
const { html: result, sign } = this.$wrapList(text, this.$engine.md5(text), lines, sentenceMakeFunc);
return this.getCacheWithSpace(this.pushCache(result, sign, lines), wholeMatch);
const result = this.toHtml(text, sentenceMakeFunc);
return this.getCacheWithSpace(this.pushCache(result, this.sign), wholeMatch);
});
}
$str = $str.replace(/~0$/g, '');
Expand Down
3 changes: 3 additions & 0 deletions src/sass/markdown.scss
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@

li {
list-style: inherit;
p {
margin: 0;
}
}
}

Expand Down
Loading

0 comments on commit 43ba79a

Please sign in to comment.