介绍
使用 <details>
和 <summary>
可以实现如下效果:
<details>
<summary>click me</summary>
<div>
<p>详细内容</p>
<p>详细内容</p>
<p>详细内容</p>
<div>
</details>
click me
详细内容
详细内容
详细内容
我的博客系统所使用的富文本编辑器对不支持嵌入 HTML 标签,而我又想实现上述的效果
博客系统支持代码注入,于是我往博客页中注入一段 js 代码,将 HTML 页面中符合下述格式的元素
<h6> #begin[click_to_display] with_text </h6>
<elem> ...
<h6> #end[click_to_display] </h6>
替换成
<div>
<p>
<input type="checkbox">
<span> with_text </span>
</p>
<div>
<elem> ...
</div>
</div>
其中,<input type="checkbox">
可以控制 <div>
中的 <elem> ...
是否显示,跟使用 <details>
和 <summary>
的效果差不多。
(但最终我还是换了另一个支持嵌入 HTML 的富文本编辑器)
用法示例
我可以这样写:
一个不支持嵌入 html 代码的富文本编辑器(示例)
段落
我的第一个C程序
六级标题
#begin[click_to_display] 点击查看代码
段落
hello world
代码块开始
C
#include <stdio.h>
int main(void) {
puts("hello world");
return 0;
}
代码块结束
引用
为什么是 puts() 而不是 printf()?
六级标题
#end[click_to_display]
博客系统将上述内容转换成 html,其中 六级标题
被转换成 <h6>
:
转换成 html 后,浏览器的渲染效果
我的第一个C程序
#begin[click_to_display] 点击查看代码
打印 hello world
#include <stdio.h>
int main(void) {
puts("hello world");
return 0;
}
为什么是 puts() 而不是 printf()?
#end[click_to_display]
接着,开启浏览器控制台,执行下述代码,就能看到 ###### #begin[click_to_display]
和 ###### #end[cmd]
所包裹的内容被替换了,最终效果跟使用了 <details>
和 <summary>
的效果差不多
代码实现
往博客系统注入下述代码
(function() {
/* -- config -- */
/* 只改动 root_elem 中的 h6 */
const root_elem = document.body;
/* 找到所有的 <h6>#begin[cmd]</h6> 和 <h6>#end[cmd]</h6> 所包裹的元素 */
function select_cmd_h6(cmd) {
const begin_regex = /^\s*#begin\[\s*(\w+?)\s*\]\s*(.*)$/;
const end_regex = /^\s*#end\[\s*(\w+?)\s*\]\s*(.*)$/;
function is_cmd_begin(h6, cmd) {
const matched = h6.textContent.match(begin_regex);
return matched !== null && matched[1] === cmd;
}
function is_cmd_end(h6, cmd) {
const matched = h6.textContent.match(end_regex);
return matched !== null && matched[1] === cmd;
}
const all_h6 = root_elem.querySelectorAll("h6");
const all_cmd_h6 = [];
for (let i = 0; i < all_h6.length; i++) {
const h6 = all_h6[i];
const matched = h6.textContent.match(begin_regex);
if (matched === null || matched[1] !== cmd)
continue;
const with_text = matched.length >= 3 ? matched[2] : null;
const surrounded_elems = [];
let is_find_end = false;
let count = 0;
let elem = h6.nextSibling;
for (; elem !== null; elem = elem.nextSibling) {
if (elem.nodeName === "H6") {
if (is_cmd_begin(elem, cmd))
count += 1;
else if (is_cmd_end(elem, cmd)) {
if (count > 0)
count -= 1;
else {
is_find_end = true;
break;
}
}
}
surrounded_elems.push(elem);
}
if ( ! is_find_end)
continue;
all_cmd_h6.push({
"cmd": cmd,
"h6_begin": h6,
"h6_end": elem,
"with_text": with_text,
"surrounded_elems": surrounded_elems
});
};
return all_cmd_h6;
}
/* -- click_to_display -- */
/* 【将符合下述格式的元素】
<h6> #begin[click_to_display] with_text </h6>
<elem> ...
<h6> #end[click_to_display] </h6>
【替换成】
<div>
<p>
<input type="checkbox">
<span> with_text </span>
</p>
<div>
<elem> ...
</div>
</div>
*/
(function () {
const cmd = "click_to_display";
select_cmd_h6(cmd).forEach(cmd_h6 => {
const h6_begin = cmd_h6["h6_begin"];
const h6_end = cmd_h6["h6_end"];
const with_text = cmd_h6["with_text"];
const surrounded_elems = cmd_h6["surrounded_elems"];
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = false;
const div_surround = document.createElement("div");
div_surround.style.borderTop = "1px solid #000";
div_surround.style.padding = "0.5em";
surrounded_elems.forEach(function (elem) {
div_surround.appendChild(elem);
});
div_surround.style.display = "none";
checkbox.addEventListener("change", function () {
if (checkbox.checked)
div_surround.style.display = "";
else
div_surround.style.display = "none";
});
const p = document.createElement("p");
p.style.margin = "0";
p.style.padding = "0.25em 0.5em 0.3em 0.5em";
p.appendChild(checkbox);
if (with_text !== null) {
const span = document.createElement("span");
span.textContent = with_text;
p.appendChild(span);
}
const div = document.createElement("div");
div.style.outline = "1px solid #000";
div.style.borderRadius = "6px";
div.appendChild(p);
div.appendChild(div_surround);
h6_begin.parentNode.replaceChild(div, h6_begin);
h6_end.remove();
});
})();
/* -- how to custom -- */
/* 【控制台输出符合下述格式的元素】
<h6> #begin[xxx] with_text </h6>
<elem> ...
<h6> #end[xxx] </h6>
*/
/* (function(){
const cmd = "xxx";
const css_name = "xxx_" + random_str(4);
add_css(`.${css_name}{ color: red; }`);
select_cmd_h6(cmd).forEach(cmd_h6 => {
const h6_begin = cmd_h6["h6_begin"];
const h6_end = cmd_h6["h6_end"];
const with_text = cmd_h6["with_text"];
const surrounded_elems = cmd_h6["surrounded_elems"];
console.info(cmd_h6);
});
})(); */
})();