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 - 备份

多端同步

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

相关推荐
微臣愚钝2 小时前
前端【8】HTML+CSS+javascript实战项目----实现一个简单的待办事项列表 (To-Do List)
前端·javascript·css·html
lilu88888883 小时前
AI代码生成器赋能房地产:ScriptEcho如何革新VR/AR房产浏览体验
前端·人工智能·ar·vr
LCG元3 小时前
Vue.js组件开发-实现对视频预览
前端·vue.js·音视频
阿芯爱编程3 小时前
vue3 react区别
前端·react.js·前端框架
烛.照1034 小时前
Nginx部署的前端项目刷新404问题
运维·前端·nginx
YoloMari4 小时前
组件中的emit
前端·javascript·vue.js·微信小程序·uni-app
浪浪山小白兔4 小时前
HTML5 Web Worker 的使用与实践
前端·html·html5
疯狂小料5 小时前
React 路由导航与传参详解
前端·react.js·前端框架
boonya5 小时前
Yearning开源MySQL SQL审核平台
数据库·mysql·开源
customer086 小时前
【开源免费】基于SpringBoot+Vue.JS贸易行业crm系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源