思源笔记块更新脚本

本贴最后更新于 371 天前,其中的信息可能已经时移世异

列举了一些常用的块更新脚本,需要配合插件块转换工具使用。

注意,本文的大多数脚本没有经过严格测试,也缺乏特殊情况的处理,只为提供参考。

测试用例

测试用例,请忽略。

//用于测试executeFunc调用(被调用的函数)
const id = "20241204232140-zvlfl9t";
const lute = tools.lute;
output = "---" + lute.BlockDOM2Md(lute.Md2BlockDOM(output));

//用于测试调用executeFunc
const lute = tools.lute;
output = "123" + lute.BlockDOM2Md(lute.Md2BlockDOM(output));
await tools
  .executeFunc(input, tools, output, { id: "20241204232140-zvlfl9t" })
  .then((res) => {
    input = res.input;
    tools = res.tools;
    output = res.output;
  });
output = "";
input.isDelete = true;

//测试超时强制退出,修改time的值以进行比较
let time = 8000;
const safePromise = new Promise((resolve) =>
  setTimeout(() => {
    console.log(time);
    resolve(1);
  }, time),
);

await safePromise;
console.log(input);

//测试报错
const prettier = tools.prettier;
const result = await prettier.prettier.format(input.block.content, {
  parser: "markdown",
  plugins: [prettier.prettierPluginMarkdown],
});
a=b.c//应该在这里报错
console.log(result);

排版优化类

排版优化类的更新脚本不改变原文内容(标点符号除外),但会改变其样式,含块类型转换、行内样式转换等。

中文排版综合

  • 排版综合的代码片段将各类排版需求继续了结合,具体包含:

    • 空格替换
    • 西文字符替换为中文字符

使用 markdown 或者 dom 的 textContent 替换空格会引起格式丢失问题,所以以下代码片段使用的是逐节点遍历方法。

const dom = tools.lute.Md2BlockDOM(output);
const div = document.createElement("div");
div.innerHTML = dom;
const symbols = [
  { en: /,/g, zh: "," },
  { en: /;/g, zh: ";" },
  { en: /\(/g, zh: "(" },
  { en: /\)/g, zh: ")" },
  { en: /!/g, zh: "!" },
  { en: /(?<![0-9])\./g, zh: "。" }, //阿拉伯数字后紧跟的“.”不会被捕获
  { en: /:/g, zh: ":" },
  { en: /(?<![a-zA-Z]) (?![a-zA-Z])/g, zh: "" }, //空格替换,英文前后的空格不替换
  { en: />/g, zh: "〉" },
  { en: /</g, zh: "〈" },
  { en: /\t/, zh: "" },
  //在中文和英文之间增加空格
  { en: /([A-Za-z]+)([\u4e00-\u9fa5]+)/g, zh: "$1 $2" },
  { en: /([\u4e00-\u9fa5]+)([A-Za-z]+)/g, zh: "$1 $2" },
];
replaceSpace(div);
function replaceSpace(div) {
  for (let child of div.childNodes) {
    if (child.nodeType === Node.TEXT_NODE) {
      symbols.forEach((e) => {
        child.textContent = child.textContent.replace(e.en, e.zh);
      });
    }
    replaceSpace(child);
  }
}
output = tools.lute.BlockDOM2Md(div.innerHTML);

自定义词语标注

该功能将自定义的一组词语列为重点词语,重点词语将会用粗体表示。

需要在设置中将“编辑器 -> Markdown 行级星号语法”开启。

const strongList = ["应当", "可以"]; //自定义词语
for (let item of strongList) {
  const reg = new RegExp(`(?<!\\*\\*)${item}(?!\\*\\*)`, "g");
  output = output.replace(reg, `**${item}**`);
}

通过指定分隔符号拆分段落

默认的拆分符号是 ​,按需修改即可。会将段落转换为无序列表。

const splitSymbols = [";", ":"]; //在此改变拆分符号
let markdownList = [output];
for (let symbol of splitSymbols) {
  markdownList = markdownList.flatMap((e) =>
    e.split(symbol).map((e, i, arr) => {
      return i === arr.length - 1 ? e : e + symbol;
    }),
  );
}
markdownList = markdownList.flatMap((e, i) => {
  if (i === 0) {
    return "* " + e + "\n\n";
  }
  return "  * " + e + "\n\n";
});
output = markdownList.join(``);

将一个块拆分为两个块

通过分隔符号 ;​将一个段落块拆分为多个个段落块。

output = output.split(";").join("\r\n\r\n");

使用 prettier​格式化代码

//TODO 目前仅支持JavaScript且不会判断语言类型
const prettier = tools.prettier;
const result = await prettier.prettier.format(input.block.content, {
  parser: "babel",
  plugins: [prettier.prettierPluginBabel, prettier.prettierPluginEstree],
});
output = `
\`\`\`js
${result}
\`\`\`
`;

模板类

以下代码片段类似于模板,与内置模板相比,稍微增强的地方在于:可以进行网络请求等操作、能够使用 JavaScript 的全部内置函数、语句不用使用特殊符号包裹,但是在安全性方面可能会有不足。

汇总父级反向引用

示例:
## 标题2
在这里使用该代码片段,将汇总所有链接到`标题2`的块
//为保证安全,如果在原本有内容的块上使用时,不会生效。
if (output) {
  return;
}
output = `{{SELECT * FROM blocks WHERE id IN (SELECT block_id FROM refs WHERE def_block_id='${input.block.parent_id}') ORDER BY hpath}}`;

写入今日天气

这是一段使用网络请求数据的示例,功能为在块文本后追加当日天气数据。

//删除这段语句
await tools
  .executeFunc(input, tools, output, { name: "高德天气API" })
  .then((res) => {
    input = res.input;
    tools = res.tools;
    output = res.output;
  });

const key = input.extra.amapWeatherKey; //在这里替换为你的api Key,使用高德天气数据
const city = `110000`; //在这里设置城市代码,具体可参考https://lbs.amap.com/api/webservice/download
const res = await fetch(
  `https://restapi.amap.com/v3/weather/
weatherInfo?key=${key}&city=${city}&extensions=base`,
);
const json = await res.json();
const weather = json.lives[0].weather;
output = output + `北京天气:${weather}`;

块生成类

该类更新脚本一般为将一种类型的块转化为另一种类型的块,基本原则是保留旧块,仅添加新块。其中新块的内容与原块由较大程度的不同。

列表转 mermaid 流程图

列表转 mermaid 流程图,无序列表视为分支,有序列表视为顺序执行。

示例

  • 流程开始

    • 分支 1

    • 分支 2,含有子顺序

      1. 第一步
      2. 第二步
      3. 第三步
    • 分支 3

flowchart TD 20240609193756-o05tsgj["` `"] 20240609193756-lc5iyjq["`流程开始`"] 20240609193756-x9shzte["` `"] 20240609193756-usp2txi["`分支1 `"] 20240609193756-sd5uewa["`分支2,含有子顺序 `"] 20240609193756-edkdpjj["` `"] 20240609193756-0mysrhz["`第一步 `"] 20240609193756-ko3recd["`第二步 `"] 20240609193756-jbwn08u["`第三步 `"] 20240609193756-cr44ed9["`分支3 `"] 20240609193756-x9shzte --> 20240609193756-usp2txi 20240609193756-edkdpjj --> 20240609193756-0mysrhz 20240609193756-0mysrhz --> 20240609193756-ko3recd 20240609193756-ko3recd --> 20240609193756-jbwn08u 20240609193756-sd5uewa --> 20240609193756-edkdpjj 20240609193756-x9shzte --> 20240609193756-sd5uewa 20240609193756-x9shzte --> 20240609193756-cr44ed9 20240609193756-lc5iyjq --> 20240609193756-x9shzte 20240609193756-o05tsgj --> 20240609193756-lc5iyjq

代码

let dom = document.createElement("div");
dom.innerHTML = tools.lute.Md2BlockDOM(output);
dom = dom.firstElementChild;
let nodeList = [];
let edgeList = [];
recur(dom);
function recur(dom) {
  if (!dom.getAttribute("data-node-id")) {
    return;
  }
  const id = dom.getAttribute("data-node-id");
  const type = dom.getAttribute("data-type");
  switch (type) {
    //*容器块
    case "NodeList":
    case "NodeListItem":
      nodeList.push({ id, content: "", type });
      let lastChild = null;
      const subType = dom.getAttribute("data-subtype");
      for (let child of dom.children) {
        recur(child);
        const childId = child.getAttribute("data-node-id");
        if (!childId) {
          continue;
        }
        const childType = child.getAttribute("data-type");
        if (childType !== "NodeList" && childType !== "NodeListItem") {
          nodeList.push({
            id: childId,
            content: child.textContent,
            type: childType,
            parentId: lastChild ? null : id,
          });
        }
        if (subType === "u") {
          edgeList.push({ source: id, target: childId });
        } else if (subType === "o") {
          if (lastChild) {
            const lastChildId = lastChild.getAttribute("data-node-id");
            edgeList.push({ source: lastChildId, target: childId });
          } else {
            edgeList.push({ source: id, target: childId });
          }
        }
        lastChild = child;
      }
      break;
    default:
      return;
  }
}
//*后处理,将列表项中的第一个段落去除,并将其内容作为父级(列表项的内容)
//*可以进一步将列表的第一个列表项去除,但是暂时不做处理
nodeList.map((child) => {
  if (!child.parentId) {
    return;
  }
  edgeList.forEach((e) => {
    if (e.source === child.id) {
      e.source = child.parentId;
    }
    if (e.target === child.id) {
      e.target = child.parentId;
    }
  });
  nodeList.find((e) => e.id === child.parentId).content = child.content;
});
nodeList = nodeList.filter((e) => !e.parentId);
edgeList = edgeList.filter((e) => e.source !== e.target);
let mermaid = nodeList.reduce((pre, cur) => {
  let content = tools.lute.BlockDOM2Md(cur.content).replace(/\{:.*?\}/g, "");
  content = content.replace(/[\r\n|\r|\n]{2,}/, "");
  return pre + `${cur.id}["\`${content}\`"]` + "\r\n";
}, "");
mermaid =
  mermaid +
  edgeList.reduce((pre, cur) => {
    return pre + `${cur.source} --> ${cur.target}` + "\r\n";
  }, "");
mermaid = `
\`\`\`mermaid
flowchart TD
${mermaid}
\`\`\`
`;

output = output + "\r\n\r\n" + mermaid;

特殊需求类

该类更新脚本为满足个性化需求,并不通用,仅供参考。

法条更新(合并)

const reg = /(第.{1,7}?条)/;
if (output.search(reg) !== 0) {
  input.isDelete = true;
} else {
  let i = input.index;
  let text = input.array[i].markdown;
  let result = "";
  do {
    text = text.replace(/[\u3000\t ]/g, "");
    if (!text) {
      continue;
    }
    //如不需要对第一行包含`第xx条`则对其进行加粗,改为let textResult = text;
    let textResult = i !== input.index ? text : text.replace(reg, "**$1** ");
    //对以`(`开头的行进行缩进
    if (text.startsWith("(") || text.startsWith("(")) {
      textResult = "  * " + textResult;
    } else {
      textResult = "* " + textResult;
    }
    result += "\r\n" + textResult;
    i++;
    text = input.array[i] ? input.array[i].markdown : null;
  } while (text && text.search(reg) !== 0);
  result = result + "\r\n---\r\n"; //最后添加分隔线
  output = result;
  input.extra.attrs = { name: "", alias: "" }; //删除所有命名和别名
}

法条更新

在原有段落转换为列表上做了增强,会将用软回车分行的文本(即在一个块中的多行文本)转换为列表,同时有一些特异性,包括:第一行包含 第xx条​则对其进行加粗,含 (xx)​的行会缩进。

以下是输入示例:

第二百二十条 有下列情形之一的,当事人可以向人民检察院申请检察建议或者抗诉:
(一)人民法院驳回再审申请的;
(二)人民法院逾期未对再审申请作出裁定的;
(三)再审判决、裁定有明显错误的。
人民检察院对当事人的申请应当在三个月内进行审查,作出提出或者不予提出检察建议或者抗诉的决定。当事人不得再次向人民检察院申请检察建议或者抗诉。

以下是输出示例

  • 第二百二十条 有下列情形之一的,当事人可以向人民检察院申请检察建议或者抗诉:

    • (一)人民法院驳回再审申请的;
    • (二)人民法院逾期未对再审申请作出裁定的;
    • (三)再审判决、裁定有明显错误的。
  • 人民检察院对当事人的申请应当在三个月内进行审查,作出提出或者不予提出检察建议或者抗诉的决定。当事人不得再次向人民检察院申请检察建议或者抗诉。


if (input.block.type == "p") {
  const list = output.split("\n");
  let result = "";
  let i = 0;
  for (let text of list) {
    text = text.replace(/[\u3000\t ]/g, "");
    if (!text) {
      continue;
    }
    //如不需要对第一行包含`第xx条`则对其进行加粗,改为let textResult = text;
    let textResult = i ? text : text.replace(/(第.{1,6}条)/, "**$1** ");
    if (text.startsWith("*")) {
      textResult = textResult.replace("*", "* ");
    } else {
      textResult = "* " + textResult;
    }
    //对以`(`开头的行进行缩进
    if (text.startsWith("(") || text.startsWith("(")) {
      textResult = "  " + textResult;
    }
    result += "\r\n" + textResult;
    i++;
  }
  output = result + "\r\n---\r\n"; //最后添加分隔线
  //移除属性
  input.extra.attrs.name = "";
  input.extra.attrs.alias = "";
}

法条自动链接(未适配 v0.4.0)

拟进行更新的块:
《刑事诉讼法》第50条规定,可以用于证明案件事实的材料,都是证据。包括:……

拟进行的链接的块:
文档名为《刑事诉讼法》
* **第五十条** 可以用于证明案件事实的材料,都是证据。
* 证据包括:
  * (一)物证;……
* 证据必须经过查证属实,才能作为定案的根据。

更新后的效果:
((20230315001689-zoh0qlk "《刑事诉讼法》第50条"))规定,可以用于证明案件事实的材料,都是证据。包括:……
//* 在该块中查找可以链接的文本
const matchGroup = input.block.content.match(/(《.*?》)第(.*?)条/);
if (matchGroup) {
  const docTitle = matchGroup[1];
  const num = num2Chinese(matchGroup[2]);
  //*查询法条所在文档
  let body = {
    stmt: `SELECT * FROM blocks WHERE content='${docTitle}' AND type='d'`,
  };
  let res = await fetch("http://127.0.0.1:6806/api/query/sql", {
    body: JSON.stringify(body),
    method: "POST",
  });
  const docId = await res.json().then((e) => e.data[0].id);
  //* 查询法条所在块
  body = {
    stmt: `SELECT * FROM blocks WHERE root_id='${docId}' AND content LIKE ' 第${num}%' AND type='l'`,
  };
  res = await fetch("http://127.0.0.1:6806/api/query/sql", {
    body: JSON.stringify(body),
    method: "POST",
  });
  const blockId = await res.json().then((e) => e.data[0].id);
  //* 生成链接并更新块
  const link = `((${blockId} "${matchGroup[0]}"))`;
  output = output.replace(matchGroup[0], link);
}
//数字转中文,仅支持到千,对于法条来说够用了
function num2Chinese(num) {
  const chnNumChar = [
    "零",
    "一",
    "二",
    "三",
    "四",
    "五",
    "六",
    "七",
    "八",
    "九",
  ];
  const unit = ["千", "百", "十", ""];
  const unitLength = 4;
  const text = num + "";
  const list = text.split("");
  while (list.length < 4) {
    list.unshift("0");
  }
  let subResultList = [];
  for (let i = list.length - 1; i >= 0; i--) {
    if (list[i] !== "0") {
      subResultList.unshift(chnNumChar[list[i]] + unit[i]);
    } else {
      subResultList.unshift("零");
    }
  }
  let result = subResultList.join("");
  //*去除多余的零
  while (result.search("零零") !== -1) {
    result = result.replace(/零零/g, "零");
  }
  result = result.replace(/^零/, "");
  result = result.replace(/零$/, "");
  return result;
}

  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    28447 引用 • 119791 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...