Trilium:在个人文档这块儿强可怕

‌Trilium‌ 是一款专注于构建大型个人知识库的分层笔记软件,支持 Markdown、Mermaid、LaTeX 即时渲染和代码高亮,能够满足用户对笔记内容的灵活需求。Trilium 提供了强大的标签系统、丰富的编辑器、灵活的笔记组织、强大的搜索功能以及多设备同步等特性,帮助用户更好地管理和利用笔记。原文见我的公众号文章 Trilium:让你随心所欲的个人知识库笔记 👋

  • 文本笔记-这是默认的笔记类型,可让您放置富文本,图像等。
  • 代码笔记-某种形式的代码,通常是编程语言(例如 js)或数据结构(例如 XML)
  • 图片笔记-代表单张图片
  • 文件笔记-代表上传的文件(例如 docx MS Word 文档)。
  • render HTML 笔记-用作附加脚本的输出屏幕
  • 已保存的搜索记录-包含保存的搜索查询,并动态显示搜索结果作为其子记录
  • 关系映射图笔记-可视化笔记及其关系
  • 书笔记-显示其子笔记,对于阅读许多简短笔记很有用
  • mermaid new-使用 mermaid.js 创建图表和流程图
  • 画布笔记 new-允许使用 excalidraw 在无限画布上手绘笔记和基本图表

可参考 Trilium Wiki

Docker 部署软件

拉取镜像 docker pull zadam/trilium

一、无数据部署

DD 中启动镜像,设置名称和端口号即可,生成容器后,启动容器,访问 http://localhost:端口 即可。 在初始化界面中选择 Create new note 即可。

二、有数据部署

1.从远程服务器恢复
2.已有[backup].db

首先,在本地任意位置创建名为 trilium-data 的数据库目录,将已有[backup-xxx].db 文件名称修改为 document.db,并复制到该目录下。

最后,在 DD 启动镜像时,设置挂载映射本地(Host),如 C:\wc\code_dev\mywww\trilium-data目录,到容器(Container)中的数据存储位置 /home/node/trilium-data。如下图:

插件系统

🧑‍🌾 创建插件:

  • 新建一个笔记,类型选择 代码-JS frontend
  • Owned Attributes 中,添加 #widget 标签;
  • 小组件代码中需要重写 isEnabled() 函数,提供插件名称,返回一个布尔值,表示是否启用该插件。如:
js 复制代码
isEnabled() {
    return super.isEnabled() && this.note.hasLabel('字数统计');
}

以上代码表示,这个插件的名称是 字数统计,如果笔记的 Owned Attributes 包含 #字数统计 标签,则启用该插件。

  • 编写代码,一般为一个 class 类最后需要 module.exports 一个实例(‼️ 必须要返回一个实例,否则无法启动)。

💡 启用插件:

  • 在笔记的属性中,添加一个标签,如 字数统计
  • 刷新页面(或重启服务器)

这里 # 开头,然后输入插件名称 字数统计(一开始系统里没有,所以要完整输入),然后直接回车。现在在系统里也会留存这个标签名称了,下次在其它笔记添加时,输入过程会自动搜索系统里已有的标签名称。

例如实现一个Deep Word Count插件,可以统计某个目录下所有文件的字数,也可以统计单个文件的字数。

js 复制代码
const TPL = `<div style="padding: 10px; border-top: 1px solid var(--main-border-color); contain: none;">
    <strong>字数: </strong>
    <span class="total-word-count"></span>

    &nbsp;

    <strong>字符数: </strong>
    <span class="total-character-count"></span>

    &nbsp;

    <strong>标点符号数: </strong>
    <span class="total-punctuations-count"></span>
</div>`;

class DeepWordCountWidget extends api.NoteContextAwareWidget {
  get position() {
    return 100;
  }

  get parentWidget() {
    return "center-pane";
  }

  
  isEnabled() {
    return (
      super.isEnabled() &&
      this.note.type === "text" &&
      this.note.hasLabel("DeepWordCount")
    );
  }

  doRender() {
    this.$widget = $(TPL);
    this.$totalWordCount = this.$widget.find(".total-word-count");
    this.$totalCharacterCount = this.$widget.find(".total-character-count");
    this.$totalPunctuationsCount = this.$widget.find(
      ".total-punctuations-count"
    );
    return this.$widget;
  }

  async refreshWithNote(note) {
    if (note.isFolder()) {
      await this.calculateTotalWordCount(note);
    } else {
      const { content } = await note.getNoteComplement();
      const text = $(content).text();
      const counts = this.getCounts(text);
      this.updateCounts(counts);
    }
  }

  async calculateTotalWordCount(note) {
    const counts = await this.recursiveCount(note);
    this.updateCounts(counts);
  }

  async recursiveCount(note) {
    let totalCounts = { words: 0, characters: 0, punctuations: 0 };

    const notes = await note.getChildNotes();
    const countPromises = notes.map(async (childNote) => {
      if (childNote.isFolder()) {
        const childCounts = await this.recursiveCount(childNote);
        totalCounts = this.aggregateCounts(totalCounts, childCounts);
      } else if (childNote.type === "text") {
        const { content } = await childNote.getNoteComplement();
        const text = $(content).text();
        const counts = this.getCounts(text);
        totalCounts = this.aggregateCounts(totalCounts, counts);
      }
    });

    await Promise.all(countPromises);
    return totalCounts;
  }

  aggregateCounts(total, counts) {
    return {
      words: total.words + counts.words,
      characters: total.characters + counts.characters,
      punctuations: total.punctuations + counts.punctuations,
    };
  }

  updateCounts(counts) {
    this.$totalWordCount.text(counts.words);
    this.$totalCharacterCount.text(counts.characters);
    this.$totalPunctuationsCount.text(counts.punctuations);
  }

  getCounts(text) {
    const chineseCount = (text.match(/[\u4e00-\u9fa5]/g) || []).length;
    const englishWordCount = (text.match(/[a-zA-Z]+/g) || []).length;
    const wordCount = chineseCount + englishWordCount;
    const charCount = text.replace(/\s+/g, "").length;
    const punctuationCount = (
      text.match(/[!!。,,。.《》「」{}*()()+\\/@#¥%&$^【】 ]/g) || []
    ).length;

    return {
      words: wordCount,
      characters: charCount,
      punctuations: punctuationCount,
    };
  }

  async entitiesReloadedEvent({ loadResults }) {
    if (loadResults.isNoteContentReloaded(this.noteId)) {
      this.refresh();
    }
  }
}

module.exports = new DeepWordCountWidget();

数据备份

参考 Trilium Wiki - 备份

多端同步

在服务器上部署后,其它地方登录时选择从服务器同步数据。下次在任意一端打开笔记,稍等片刻,就会自动同步最新数据。

相关推荐
m0_748238781 小时前
今日推荐库:@microsoftfetch-event-source 前端发送SSE请求实现GPT流式输出
前端·gpt
叫我菜菜就好1 小时前
【Flutter_Web】Flutter编译Web第一篇(插件篇):Flutter_web实现上传TOS上传资源,编写web插件
前端·javascript·flutter·dart
布兰妮甜1 小时前
React组件最佳实践
前端·javascript·react.js·组件
@阿猫阿狗~1 小时前
创建一个 React 项目
前端·react.js·前端框架
Amo 67291 小时前
react中使用echarts
前端·react.js·echarts
m0_748240541 小时前
华为数通产品-交换机--配置管理方式 (Console口登录、telnet ssh、WEB )
前端·华为·ssh
苟非1 小时前
暂停一下,给Next.js项目配置一下ESLint(Next+tailwind项目)
开发语言·前端·javascript·next
Irises`1 小时前
前端页面导出word
前端·word·bug
m0_748246351 小时前
JWT详细解析:全面掌握JSON Web Token及其使用
前端·网络·json
m0_748256562 小时前
Web 端语音对话 AI 示例:使用 Whisper 和 llama.cpp 构建语音聊天机器人
前端·人工智能·whisper