菜单

szdxdxdx
szdxdxdx
发布于 2024-05-02 / 98 阅读
0
0

不支持嵌入 html 的富文本编辑器,如何实现<details>

介绍

使用 <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);
    });
  })(); */
})();

评论