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>
<strong>字符数: </strong>
<span class="total-character-count"></span>
<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();
数据备份
多端同步
在服务器上部署后,其它地方登录时选择从服务器同步数据。下次在任意一端打开笔记,稍等片刻,就会自动同步最新数据。