JavaScript - 爬取文章目录
2023年11月30日 2024年2月13日
说明
- Hugo是提供文章目录内容的
1{{ .TableOfContents }}
- 限制
- 文章目录支持折叠
- 指定级别标题
Hugo的标题只有2-4 - 标题重名
仍未解决
- 文章目录支持折叠
- 将文章目录填写到Bootstrap 5侧边折叠导航
RUNOOB - Bootstrap5 - 侧边栏导航 - 标题样式
RUNOOB - Bootstrap5 - 下拉菜单 - 设计
- 点击标题跳转到文章是保证了的
- 展开/折叠子标题需点击可折叠标题末尾的按钮
- 点击标题跳转到文章是保证了的
侧边折叠导航结构
-
最外层为
1<div><ul>中间</ul></div>
-
无子标题结构为
1<li><a href="#"><span></span>heading 1</a></li>
其中, span会显示一个圆点标识行, 以及该标题是否可折叠
-
有子标题结构为
li
由三部分组成:a
,button
,dib
a
为标题链接- 点击按钮可折叠/取消折叠子标题
div
往下是ul
, 之后是子标题
1<li> 2 <a href="#"><span></span>heading 2</a> 3 <button></button> 4 <div> 5 <ul> 6 <li><a href="#"><span></span>heading 2 - 1</a></li> 7 <li><a href="#"><span></span>heading 2 - 2</a> 8 </li> 9 </ul> 10 </div> 11</li>
1<div> 2 <ul> 3 <li><a href="#"><span></span>heading 1</a></li> 4 <li> 5 <a href="#"><span></span>heading 2</a> 6 <button></button> 7 <div> 8 <ul> 9 <li><a href="#"><span></span>heading 2 - 1</a></li> 10 <li><a href="#"><span></span>heading 2 - 2</a> 11 </li> 12 </ul> 13 </div> 14 </li> 15 <li><a href="#><span></span>heading 3</a></li> 16 </ul> 17</div>
思路
- 遍历逻辑前后添加最外层
- 每一个标题是否可折叠, 通过其标题大小和下一个标题的大小相比较作出判断; 这其中还涉及是否要关闭非叶子标题
- 当前标题大: 可折叠
- 二者相等: 不可折叠
- 后者标题大: 不可折叠; 关闭上级标题
- 当前标题大: 可折叠
- 最后一个标题: 不可折叠, 关闭上级标题
折叠关键参数
- data-bs-target
折叠按钮信息, button元素使用 - data-bs-toggle
用于可折叠标题的下级div; 可折叠标题最外层为li
点击按钮时, 折叠或展开div下的元素
与data-bs-target相关联: data-bs-target为#id
, data-bs-toggle则为id
- collapse
可折叠标题的下级div使用; 默认折叠
为div加上show
, 默认展开 - aria-expanded
用于button, 关系到折叠图片的旋转
为false, 图标尖尖朝左; 为true, 图标尖尖朝下
与div是否有show相关联: div有show, 默认展开, 则应为true; div无show, 默认折叠, 则应为false
注意项
- html代码里的双引号需要转义
- 为了保证代码清晰, 以及一定程度的复用, 灵活使用传参和封装
- 自用, 一定程度上, 配置有些繁琐
接口
- 封装属性
1function format_attribute(attribute, content) 2{ 3 temp = attribute; 4 temp += "=\"" + content + "\""; 5 return temp; 6}
- 封装span
1function format_span(circle_color) 2{ 3 temp = "<span "; 4 temp += format_attribute("class", "d-inline-block rounded-circle " + circle_color); 5 temp += format_attribute("style", "width: .5em; height: .5em; flex-shrink: 0 "); 6 temp += "></span>"; 7 8 return temp; 9}
- 封装叶子标题a
1function format_a(heading, class_content, descript, circle_color) 2{ 3 temp = "<a "; 4 5 href_content = "#" + heading; 6 temp += format_attribute("href", href_content); 7 8 temp += " "; 9 temp += format_attribute("class", class_content); 10 temp += ">"; 11 temp += format_span(circle_color); 12 temp += "   " + descript; 13 temp += "</a>"; 14 15 return temp; 16}
- 封装叶子标题li
1function format_li(heading, descript, circle_color) 2{ 3 temp = "<li "; 4 temp += format_attribute("class", "mb-1 my-1 ms-3"); 5 temp += ">"; 6 temp += format_a(heading, "rounded align-items-baseline", descript, circle_color); 7 temp += "</li>"; 8 9 return temp; 10}
- 封装非叶子标题li开头
1function format_li_start(class_content) 2{ 3 temp = "<li "; 4 temp += format_attribute("class", class_content); 5 temp += ">"; 6 7 return temp; 8}
- 封装非叶子标题button
1function format_button_toogle(class_content, if_toggle, toggle_target, expanded_value) 2{ 3 4 temp = "<button "; 5 temp += format_attribute("class", class_content); 6 7 temp += " "; 8 temp += format_attribute("data-bs-toggle", if_toggle); 9 10 temp += " "; 11 temp += format_attribute("data-bs-target", toggle_target); 12 13 temp += " "; 14 temp += format_attribute("aria-expanded", expanded_value); 15 16 temp += "></button>"; 17 18 return temp; 19}
- 封装非叶子标题a
1function format_button_a(descript, a_class, heading) 2{ 3 temp = "<a "; 4 5 href_content = "#" + heading; 6 temp += format_attribute("href", href_content); 7 8 temp += " "; 9 temp += format_attribute("class", a_class + " " + 10 "align-items-baseline"); 11 12 temp += ">"; 13 14 temp += format_span("bg-success"); 15 temp += "   " + descript; 16 17 temp += "</a>"; 18 return temp; 19}
- 封装非叶子标题div开头
1function format_div_start(id) 2{ 3 temp = "<div "; 4 5 /* 默认不展开, 添加show可以展开 */ 6 temp += format_attribute("class", "collapse"); 7 8 temp += " "; 9 temp += format_attribute("id", id); 10 11 temp += ">"; 12 13 return temp; 14}
- 封装非叶子标题ul开头
1function format_ul_start(class_content) 2{ 3 temp = "<ul "; 4 5 temp += format_attribute("class", class_content); 6 7 temp += ">"; 8 9 return temp; 10}
- 封装非叶子标题开头
1function button_pack_start(heading, descript) 2{ 3 temp = format_li_start("mb-1 my-1 ms-3"); 4 5 toggle_target_id = "toc-" + heading; 6 toggle_target = "#" + toggle_target_id; 7 8 a_class = "rounded"; 9 10 temp += format_button_a(descript, a_class, heading); 11 12 temp += format_button_toogle("toc-btn-toggle rounded collapsed", "collapse", toggle_target, "false"); 13 14 temp += format_div_start(toggle_target_id); 15 16 temp += format_ul_start("btn-toggle-nav list-unstyled fw-normal pb-1"); 17 18 return temp; 19}
- 封装非叶子标题结尾
1function button_pack_close() /* close_button */ 2{ 3 temp = ""; 4 temp += "</ul>"; 5 temp += "</div>"; 6 temp += "</li>"; 7 return temp; 8}
- 传入标题数组, 返回文章目录
1function format_toc(headings) 2{ 3 toc = "<div class=\"flex-shrink-0 p-3 toc-sticky\">\n"; 4 toc += "<ul class=\"list-unstyled ps-0\">\n" 5 6 for (i = 1; i < headings.length; ++i) 7 { 8 tag_next = parseInt(headings[i].tagName[1]); 9 10 heading = headings[i - 1]; 11 // console.log(heading.innerText.replace(" #", "")); 12 13 id = heading.getAttribute('id'); 14 tag = parseInt(heading.tagName[1]); 15 descript = heading.innerText.replace(" #", ""); 16 17 if (tag_next > tag) 18 { 19 // toc += format_button(id); 20 toc += button_pack_start(id, descript); 21 } 22 else 23 { 24 // toc += format_li(id); 25 toc += format_li(id, descript, "bg-primary") + "\n"; 26 if (tag_next < tag) 27 { 28 for (j = 0; j < tag - tag_next; ++j) 29 { 30 // toc += close_button(); 31 toc += button_pack_close() + "\n"; 32 } 33 } 34 } 35 } 36 37 heading = headings[headings.length - 1]; 38 id = heading.getAttribute('id'); 39 tag = parseInt(heading.tagName[1]); 40 heading.innerText.replace(" #", ""); 41 42 // toc += format_li(id_next); 43 toc += format_li(id, descript, "bg-primary"); 44 for (j = 0; j < tag - 2; ++j) 45 { 46 // toc += close_button(); 47 toc += button_pack_close() + "\n"; 48 } 49 50 toc += "</ul></div>\n"; 51 52 // console.log(toc); 53 54 return toc; 55}
- 获取页面指定大小标题, 往页面写入文章目录
1function add_toc() 2{ 3 const headings = Array.apply(null, document.querySelectorAll('h2[id], h3[id], h4[id]')) 4 .filter(function(value, index, arr) { return arr[index].querySelector('.anchor'); }); 5 6 const toc = format_toc(headings); 7 document.write(toc); 8}
函数, 属性和样式
-
span - CSS样式
作为a的下级元素, 解决标题名过长圆点会被压缩的问题1flex-shink: 0
-
a - CSS样式
类align-items-baseline
a的下级元素的对齐方式
-
span - CSS样式
Bootstrap 5 提供的背景色- bg-success 绿色 非叶子标题 bg-primary 蓝色 叶子标题 -
ul - CSS样式
类list-unstyled
a前面无圆点
-
a - CSS样式
类toc-btn-toggle
自定义样式, 其中设置文本分行较为重要
1word-wrap: break-word;
-
Html和JavaScript
- TagName 标签类型, 如h2, h5 innerText 标题内文本 JS: string replace 文本替换 document.write 在执行脚本处写入
便签
- |
---|
Bootstrap5 - 折叠 |
JavaScript创建元素 |
将a用作按钮 |
Bootstrap 5 - 按钮 |
JS判断元素是否有下级 |
元素不被压缩 |
JS - replace |
CSS - align-items |
Bootstrap5 - 颜色 |
html空格 |
自动换行 |