When using Markdown to edit and write articles, we will use theh1-h6
tag to define the chapter title, but theh1-h6
tag in the article generated by Markdown is a parallel structure,
It is not a tree structure. At this time, we need to manually parse these h tags and generate a directory tree according to their direct rules.
文章效果
文章dom结构
最终生成的目录结构
ideas
- Get all the h1~h6 tags in the article
- Compare the numbers of the h tag and judge from the current h tag. If the following h tag number is larger than yourself, it will be regarded as your own descendant, and if the h tag number is smaller than yourself or the same as yourself, it will stop immediately.
If we get the h tag like this:
var hEles = [
'h4',
'h6',
'h3',
'h4',
'h4',
'h1',
'h2',
'h3',
'h3',
'h3',
'h3',
'h2',
'h3',
'h3'
];
Then we first need to convert it to this:
var arr2 = [
{hLevel: 4}, {hLevel: 6}, {hLevel: 3}, {hLevel: 4},
{hLevel: 4}, {hLevel: 1}, {hLevel: 2}, {hLevel: 3},
{hLevel: 3}, {hLevel: 3}, {hLevel: 3}, {hLevel: 2},
{hLevel: 3}, {hLevel: 3}
];
Then convert to a tree:
var res = [
{ hLevel: 4, level: 1, children: [ {hLevel: 6, level: 2} ] },
{ hLevel: 3,, level: 1, children: [ {hLevel: 4, level: 2}, {hLevel: 4, level: 2} ] },
{
hLevel: 1,
level: 1,
children: [
{
hLevel: 2,
level: 2
children: [ {hLevel: 3, level: 3}, {hLevel: 3, level: 3}, {hLevel: 3, level: 3}, {hLevel: 3, level: 3} ]
},
{
hLevel: 2,
level: 2,
children: [ {hLevel: 3, level: 3}, {hLevel: 3, level: 3} ]
}
]
}
];
1. Code implementation scheme one
function toTree(flatArr){
var tree = [];
var copyArr = flatArr.map(function (item) { return item; });
// 根据指定级别查找该级别的子孙级,并删除掉已经查找到的子孙级
var getChildrenByLevel = function (currentLevelItem, arr, level) {
if(!currentLevelItem){
return;
}
// 将level值转成负数,再进行比较
var minusCurrentLevel = -currentLevelItem.hLevel;
var children = [];
for(var i = 0, len = arr.length; i < len; i++){
var levelItem = arr[i];
if(-levelItem.hLevel < minusCurrentLevel){
children.push(levelItem);
}else { // 只找最近那些子孙级
break;
}
}
// 从数组中删除已经找到的那些子孙级,以免影响到其他子孙级的查找
if(children.length > 0){
arr.splice(0, children.length);
}
return children;
}
var getTree = function (result, arr, level) {
// 首先将数组第一位移除掉,并添加到结果集中
var currentItem = arr.shift();
currentItem.level = level;
result.push(currentItem);
while (arr.length > 0){
if(!currentItem){
return;
}
// 根据当前级别获取它的子孙级
var children = getChildrenByLevel(currentItem, arr, level);
// 如果当前级别没有子孙级则开始下一个
if(children.length == 0){
currentItem = arr.shift();
currentItem.level = level;
if(currentItem){
result.push(currentItem);
}
continue;
}
currentItem.children = [];
// 查找到的子孙级继续查找子孙级
getTree(currentItem.children, children, level + 1);
}
}
getTree(tree, copyArr, 1);
return tree;
}
have a test:
var arr2 = [
{hLevel: 4}, {hLevel: 6}, {hLevel: 3}, {hLevel: 4},
{hLevel: 4}, {hLevel: 1}, {hLevel: 2}, {hLevel: 3},
{hLevel: 3}, {hLevel: 3}, {hLevel: 3}, {hLevel: 2},
{hLevel: 3}, {hLevel: 3}
];
console.log(toTree(arr));
2. Code implementation plan 2: stack idea implementation (2021-08-30)
function toTree2 (flatArr) {
var resultArr = []; // 结果集数组
var stack = []; // 栈数组
flatArr.forEach((levelItem, index) => {
levelItem.children = [];
if (resultArr.length == 0) { // 第一次循环时没有任何内容,则默认将第一个元素入栈
levelItem.parentCollector = resultArr; // 存储父级
stack.push(levelItem);
resultArr.push(levelItem);
} else {
let lastestLeveInStack = stack[stack.length - 1]; // 获取栈顶的元素,即最近如入栈的元素
if (lastestLeveInStack.level < levelItem.level) { // 遇到子级节点
levelItem.parentCollector = lastestLeveInStack.children;
stack.push(levelItem);
lastestLeveInStack.children.push(levelItem);
} else if (lastestLeveInStack.level == levelItem.level) {// 遇到同级节点
levelItem.parentCollector = lastestLeveInStack.parentCollector;
stack.push(levelItem); // 将当前元素入栈
lastestLeveInStack.parentCollector.push(levelItem);
} else {
let lastestLeveParentCollector = lastestLeveInStack.parentCollector;
let lastestLeveParentIndex = stack.findIndex(function (leveItem) {
return leveItem.children === lastestLeveParentCollector;
});
if (lastestLeveParentIndex > -1) {
// 栈顶节点的父级节点
let lastestLeveParent = stack[lastestLeveParentIndex];
if (lastestLeveParent.level < levelItem.level) { // 当前节点为栈顶节点父节点的子级
levelItem.parentCollector = lastestLeveParent.children;
lastestLeveParent.children.push(levelItem);
stack.push(levelItem);
return;
}
// 移除栈顶节点父级节点及其以后的所有节点
stack.splice(lastestLeveParentIndex);
if (stack.length == 0) { // 如果栈空了则该节点为顶层节点
levelItem.parentCollector = resultArr;
stack.push(levelItem);
resultArr.push(levelItem);
return;
}
lastestLeveInStack = stack[stack.length - 1];
lastestLeveParent = lastestLeveInStack.parentCollector;
if (lastestLeveInStack.level < levelItem.level) { // 当前节点为栈顶节点的子节点
levelItem.parentCollector = lastestLeveInStack.children;
stack.push(levelItem);
lastestLeveInStack.children.push(levelItem);
} else { // 当前节点为栈顶节点的兄弟节点
let lastestLeveParentCollector = lastestLeveInStack.parentCollector;
let collector = stack.find(function (leveItem) {
return leveItem.children === lastestLeveParentCollector;
});
// 如果栈顶节点的父级节点的级别比当前节点级别大,则继续往上查找
while (collector && collector.level >= levelItem.level) {
lastestLeveParentCollector = collector.parentCollector;
collector = stack.find(function (leveItem) {
return leveItem.children === lastestLeveParentCollector;
});
}
if (!collector) {
levelItem.parentCollector = resultArr;
stack.push(levelItem);
resultArr.push(levelItem);
} else {
levelItem.parentCollector = collector.children;
collector.children.push(levelItem);
/* let collectorIndex = stack.findIndex(function (item) {
return item === collector;
});
if (collectorIndex > -1) {
stack.splice(collectorIndex);
} */
stack.push(levelItem);
}
}
} else {
console.log('kkkkkkkkkkkkkkk');
}
}
}
});
// 移除元素的parentCollector属性
flatArr.forEach(levelItem => {
if (levelItem.parentCollector) {
delete levelItem.parentCollector;
}
if (levelItem.children.length == 0) {
delete levelItem.children;
}
});
return resultArr;
}
Next, you only need to generate the corresponding dom tree according to this tree structure.
// 根据树状结构数据生成章节目录dom树
function getChapterDomTree(chapterTreeData, parentNode){
if(!parentNode){
parentNode = createNodeByHtmlStr('<ul class="markdown-toc-list"></ul>')[0];
}
chapterTreeData.forEach(chapterItem => {
var itemDom = createNodeByHtmlStr('<li><a class="toc-level-' + chapterItem.level + '" href="#' + chapterItem.id + '">' + chapterItem.text + '</a></li>')[0];
parentNode.appendChild(itemDom);
if(chapterItem.children){
var catalogList = createNodeByHtmlStr('<ul class="markdown-toc-list"></ul>')[0];
itemDom.appendChild(catalogList);
getChapterDomTree(chapterItem.children, catalogList);
}
});
return parentNode;
}
// 根据html字符串生成dom元素
function createNodeByHtmlStr(htmlStr){
var div = document.createElement('div');
div.innerHTML = htmlStr;
var children = div.children;
div = null;
return children;
}
var treeData = toTree([...]);
var domTree = getChapterDomTree(treeData);
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。