mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
Merge branch 'master' into dev
This commit is contained in:
commit
e76290e598
@ -1,9 +1,9 @@
|
|||||||
# Trilium笔记
|
# Trilium Notes
|
||||||
|
|
||||||
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md)
|
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md)
|
||||||
|
|
||||||
[](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
Trilium Notes是一个分层的笔记应用程序,专注于建立大型个人知识库。请参阅[屏幕截图](https://github.com/zadam/trilium/wiki/Screenshot-tour)以快速了解:
|
Trilium Notes 是一个层次化的笔记应用程序,专注于建立大型个人知识库。请参阅[屏幕截图](https://github.com/zadam/trilium/wiki/Screenshot-tour)以快速了解:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -14,36 +14,43 @@ Ukraine is currently suffering from Russian aggression, please consider donating
|
|||||||
## 特性
|
## 特性
|
||||||
|
|
||||||
* 笔记可以排列成任意深的树。单个笔记可以放在树中的多个位置(请参阅[克隆](https://github.com/zadam/trilium/wiki/Cloning-notes))
|
* 笔记可以排列成任意深的树。单个笔记可以放在树中的多个位置(请参阅[克隆](https://github.com/zadam/trilium/wiki/Cloning-notes))
|
||||||
* 丰富的所见即所得笔记编辑功能,包括带有markdown[自动格式化功能的](https://github.com/zadam/trilium/wiki/Text-notes#autoformat)表格,图像和[数学](https://github.com/zadam/trilium/wiki/Text-notes#math-support)
|
* 丰富的所见即所得笔记编辑功能,包括带有 Markdown [自动格式化功能的](https://github.com/zadam/trilium/wiki/Text-notes#autoformat)表格,图像和[数学](https://github.com/zadam/trilium/wiki/Text-notes#math-support)
|
||||||
* 支持编辑[使用源代码的笔记](https://github.com/zadam/trilium/wiki/Code-notes),包括语法高亮显示
|
* 支持编辑[使用源代码的笔记](https://github.com/zadam/trilium/wiki/Code-notes),包括语法高亮显示
|
||||||
* 笔记之间快速[导航](https://github.com/zadam/trilium/wiki/Note-navigation),全文搜索和[笔记挂起](https://github.com/zadam/trilium/wiki/Note-hoisting)
|
* 笔记之间快速[导航](https://github.com/zadam/trilium/wiki/Note-navigation),全文搜索和[笔记聚焦](https://github.com/zadam/trilium/wiki/Note-hoisting)
|
||||||
* 无缝[笔记版本控制](https://github.com/zadam/trilium/wiki/Note-revisions)
|
* 无缝[笔记版本控制](https://github.com/zadam/trilium/wiki/Note-revisions)
|
||||||
* 笔记[属性](https://github.com/zadam/trilium/wiki/Attributes)可用于笔记组织,查询和高级[脚本编写](https://github.com/zadam/trilium/wiki/Scripts)
|
* 笔记[属性](https://github.com/zadam/trilium/wiki/Attributes)可用于笔记组织,查询和高级[脚本编写](https://github.com/zadam/trilium/wiki/Scripts)
|
||||||
* [同步](https://github.com/zadam/trilium/wiki/Synchronization)与自托管同步服务器
|
* [同步](https://github.com/zadam/trilium/wiki/Synchronization)与自托管同步服务器
|
||||||
|
* 有一个[第三方提供的同步服务器托管服务](https://trilium.cc/paid-hosting)
|
||||||
|
* 公开地[分享](https://github.com/zadam/trilium/wiki/Sharing)(发布)笔记到互联网
|
||||||
* 具有按笔记粒度的强大的[笔记加密](https://github.com/zadam/trilium/wiki/Protected-notes)
|
* 具有按笔记粒度的强大的[笔记加密](https://github.com/zadam/trilium/wiki/Protected-notes)
|
||||||
|
* 使用自带的 Excalidraw 来绘制图表(笔记类型“画布”)
|
||||||
* [关系图](https://github.com/zadam/trilium/wiki/Relation-map)和[链接图](https://github.com/zadam/trilium/wiki/Link-map),用于可视化笔记及其关系
|
* [关系图](https://github.com/zadam/trilium/wiki/Relation-map)和[链接图](https://github.com/zadam/trilium/wiki/Link-map),用于可视化笔记及其关系
|
||||||
* [脚本](https://github.com/zadam/trilium/wiki/Scripts)-请参阅[高级展示](https://github.com/zadam/trilium/wiki/Advanced-showcases)
|
* [脚本](https://github.com/zadam/trilium/wiki/Scripts) - 请参阅[高级功能展示](https://github.com/zadam/trilium/wiki/Advanced-showcases)
|
||||||
* 可用性和性能均能很好地扩展至超过10万个笔记
|
* 在拥有超过 10 万条笔记时仍能保持良好的可用性和性能
|
||||||
* 针对智能手机和平板电脑进行触摸优化的[移动前端](https://github.com/zadam/trilium/wiki/Mobile-frontend)
|
* 针对智能手机和平板电脑进行优化的[用于移动设备的前端](https://github.com/zadam/trilium/wiki/Mobile-frontend)
|
||||||
* [夜间主题](https://github.com/zadam/trilium/wiki/Themes)
|
* [夜间主题](https://github.com/zadam/trilium/wiki/Themes)
|
||||||
* [Evernote](https://github.com/zadam/trilium/wiki/Evernote-import)和[Markdown导入导出](https://github.com/zadam/trilium/wiki/Markdown)
|
* [Evernote](https://github.com/zadam/trilium/wiki/Evernote-import) 和 [Markdown 导入导出](https://github.com/zadam/trilium/wiki/Markdown)功能
|
||||||
* [Web Clipper](https://github.com/zadam/trilium/wiki/Web-clipper)可轻松保存Web内容
|
* 使用[网页剪藏](https://github.com/zadam/trilium/wiki/Web-clipper)轻松保存互联网上的内容
|
||||||
|
|
||||||
## 构建
|
## 构建
|
||||||
|
|
||||||
Trilium是作为桌面应用程序(Linux和Windows)或服务器上托管的Web应用程序(Linux)提供的。Mac OS桌面版本可用,但[不受支持](https://github.com/zadam/trilium/wiki/FAQ#mac-os-support)。
|
Trilium 可以用作桌面应用程序(Linux 和 Windows)或服务器(Linux)上托管的 Web 应用程序。虽然有 macOS 版本的桌面应用程序,但它[不受支持](https://github.com/zadam/trilium/wiki/FAQ#mac-os-support)。
|
||||||
|
|
||||||
* 如果要在桌面上使用Trilium,请从[最新版本](https://github.com/zadam/trilium/releases/latest)下载适用于您平台的二进制[版本](https://github.com/zadam/trilium/releases/latest),解压缩该软件包并运行`trilium`可执行文件。
|
* 如果要在桌面上使用 Trilium,请从[最新版本](https://github.com/zadam/trilium/releases/latest)下载适用于您平台的二进制版本,解压缩该软件包并运行`trilium`可执行文件。
|
||||||
* 如果要在服务器上安装Trilium,请遵循[此页面](https://github.com/zadam/trilium/wiki/Server-installation)。
|
* 如果要在服务器上安装 Trilium,请参考[此页面](https://github.com/zadam/trilium/wiki/Server-installation)。
|
||||||
* 当前仅支持(经过测试)最新的Chrome和Firefox浏览器。
|
* 当前仅支持(测试过)最近发布的 Chrome 和 Firefox 浏览器。
|
||||||
|
|
||||||
|
Trilium 也提供 Flatpak:
|
||||||
|
|
||||||
|
[<img width="240" src="https://flathub.org/assets/badges/flathub-badge-en.png">](https://flathub.org/apps/details/com.github.zadam.trilium)
|
||||||
|
|
||||||
## 文档
|
## 文档
|
||||||
|
|
||||||
[有关文档页面的完整列表,请参见Wiki。](https://github.com/zadam/trilium/wiki/)
|
[有关文档页面的完整列表,请参见 Wiki。](https://github.com/zadam/trilium/wiki/)
|
||||||
|
|
||||||
[中文Wiki在这里](https://github.com/baddate/trilium/wiki/)
|
* [Wiki 的中文翻译版本](https://github.com/baddate/trilium/wiki/)
|
||||||
|
|
||||||
您还可以阅读[个人知识库模式](https://github.com/zadam/trilium/wiki/Patterns-of-personal-knowledge-base),以获取有关如何使用Trilium的灵感。
|
您还可以阅读[个人知识库模式](https://github.com/zadam/trilium/wiki/Patterns-of-personal-knowledge-base),以获取有关如何使用 Trilium 的灵感。
|
||||||
|
|
||||||
## 贡献
|
## 贡献
|
||||||
|
|
||||||
@ -51,7 +58,7 @@ Trilium是作为桌面应用程序(Linux和Windows)或服务器上托管的W
|
|||||||
|
|
||||||
[](https://gitpod.io/#https://github.com/zadam/trilium)
|
[](https://gitpod.io/#https://github.com/zadam/trilium)
|
||||||
|
|
||||||
或在本地克隆并运行
|
或者克隆本仓库到本地,并运行
|
||||||
|
|
||||||
```
|
```
|
||||||
npm install
|
npm install
|
||||||
@ -60,7 +67,15 @@ npm run start-server
|
|||||||
|
|
||||||
## 致谢
|
## 致谢
|
||||||
|
|
||||||
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - 市场上最好的所见即所得编辑器,互动性强且聆听能力强的团队
|
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - 市面上最好的所见即所得编辑器,拥有互动性强且聆听能力强的团队
|
||||||
* [FancyTree](https://github.com/mar10/fancytree) - 一个非常丰富的关于树的库,强大的没有对手。没有它,Trilium Notes将不会如此。
|
* [FancyTree](https://github.com/mar10/fancytree) - 一个非常丰富的关于树的库,强大到没有对手。没有它,Trilium Notes 将不会如此。
|
||||||
* [CodeMirror](https://github.com/codemirror/CodeMirror) - 支持大量语言的代码编辑器
|
* [CodeMirror](https://github.com/codemirror/CodeMirror) - 支持大量语言的代码编辑器
|
||||||
* [jsPlumb](https://github.com/jsplumb/jsplumb)强大的可视化连接库。- 用于[关系图](https://github.com/zadam/trilium/wiki/Relation-map)和[链接图](https://github.com/zadam/trilium/wiki/Link-map)
|
* [jsPlumb](https://github.com/jsplumb/jsplumb) - 强大的可视化连接库。用于[关系图](https://github.com/zadam/trilium/wiki/Relation-map)和[链接图](https://github.com/zadam/trilium/wiki/Link-map)
|
||||||
|
|
||||||
|
## 捐赠
|
||||||
|
|
||||||
|
你可以通过 GitHub Sponsors,[PayPal](https://paypal.me/za4am) 或者比特币 (bitcoin:bc1qv3svjn40v89mnkre5vyvs2xw6y8phaltl385d2) 来捐赠。
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
本程序是自由软件:你可以再发布本软件和/或修改本软件,只要你遵循 Free Software Foundation 发布的 GNU Affero General Public License 的第三版或者任何(由你选择)更晚的版本。
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
-- "randomize" branchIds so it's clear user should not rely on them
|
|
||||||
UPDATE branches SET branchId = '7LSsI2FnZPW2' WHERE parentNoteId = 'hidden' AND noteId = 'search';
|
|
||||||
UPDATE branches SET branchId = 'wEcmxk4CNC7G' WHERE parentNoteId = 'singles' AND noteId = 'globalnotemap';
|
|
||||||
UPDATE branches SET branchId = '191uVR6Cu6fA' WHERE parentNoteId = 'hidden' AND noteId = 'sqlconsole';
|
|
||||||
UPDATE branches SET branchId = 'OjX5Phxp6A4N' WHERE parentNoteId = 'root' AND noteId = 'hidden';
|
|
||||||
UPDATE branches SET branchId = 'glNBYFYZRH8P' WHERE parentNoteId = 'hidden' AND noteId = 'bulkaction';
|
|
||||||
UPDATE branches SET branchId = 'cAT25wvGMg3K' WHERE parentNoteId = 'root' AND noteId = 'share';
|
|
6
db/migrations/0198__rename_branchIds.sql
Normal file
6
db/migrations/0198__rename_branchIds.sql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
UPDATE branches SET branchId = '_hidden__search' WHERE parentNoteId = 'hidden' AND noteId = 'search';
|
||||||
|
UPDATE branches SET branchId = 'root__globalNoteMap' WHERE parentNoteId = 'singles' AND noteId = 'globalnotemap';
|
||||||
|
UPDATE branches SET branchId = '_hidden__sqlConsole' WHERE parentNoteId = 'hidden' AND noteId = 'sqlconsole';
|
||||||
|
UPDATE branches SET branchId = 'root__hidden' WHERE parentNoteId = 'root' AND noteId = 'hidden';
|
||||||
|
UPDATE branches SET branchId = '_hidden__bulkAction' WHERE parentNoteId = 'hidden' AND noteId = 'bulkaction';
|
||||||
|
UPDATE branches SET branchId = '_hidden__share' WHERE parentNoteId = 'root' AND noteId = 'share';
|
@ -1,2 +1,2 @@
|
|||||||
DELETE FROM branches WHERE noteId = '_globalNoteMap' AND parentNoteId != 'singles'; -- make sure there are no clones which would fail at the next line
|
DELETE FROM branches WHERE noteId = '_globalNoteMap' AND parentNoteId != 'singles' AND parentNoteId != '_hidden'; -- make sure there are no clones which would fail at the next line
|
||||||
UPDATE branches SET parentNoteId = '_hidden' WHERE noteId = '_globalNoteMap';
|
UPDATE branches SET parentNoteId = '_hidden' WHERE noteId = '_globalNoteMap';
|
||||||
|
24
db/migrations/0210__consistency_checks.js
Normal file
24
db/migrations/0210__consistency_checks.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
module.exports = async () => {
|
||||||
|
const cls = require("../../src/services/cls");
|
||||||
|
const beccaLoader = require("../../src/becca/becca_loader");
|
||||||
|
const log = require("../../src/services/log");
|
||||||
|
const consistencyChecks = require("../../src/services/consistency_checks");
|
||||||
|
const noteService = require("../../src/services/notes");
|
||||||
|
|
||||||
|
await cls.init(async () => {
|
||||||
|
// precaution for the 0211 migration
|
||||||
|
noteService.eraseDeletedNotesNow();
|
||||||
|
|
||||||
|
beccaLoader.load();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// precaution before running 211 which might produce unique constraint problems if the DB was not consistent
|
||||||
|
consistencyChecks.runOnDemandChecksWithoutExclusiveLock(true);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
// consistency checks might start failing in the future if there's some incompatible migration down the road
|
||||||
|
// we can optimistically assume the DB is consistent and still continue
|
||||||
|
log.error(`Consistency checks failed in migration 0210: ${e.message} ${e.stack}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
12
db/migrations/0211__rename_branchIds.sql
Normal file
12
db/migrations/0211__rename_branchIds.sql
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
-- case based on isDeleted is needed, otherwise 2 branches (1 deleted, 1 not) might get the same ID
|
||||||
|
UPDATE entity_changes SET entityId = COALESCE((
|
||||||
|
SELECT
|
||||||
|
CASE isDeleted
|
||||||
|
WHEN 0 THEN parentNoteId || '_' || noteId
|
||||||
|
WHEN 1 THEN branchId
|
||||||
|
END
|
||||||
|
FROM branches WHERE branchId = entityId
|
||||||
|
), entityId)
|
||||||
|
WHERE entityName = 'branches' AND isErased = 0;
|
||||||
|
|
||||||
|
UPDATE branches SET branchId = parentNoteId || '_' || noteId WHERE isDeleted = 0;
|
21
db/migrations/0212__delete_all_attributes_of_named_notes.js
Normal file
21
db/migrations/0212__delete_all_attributes_of_named_notes.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
module.exports = () => {
|
||||||
|
const cls = require("../../src/services/cls");
|
||||||
|
const beccaLoader = require("../../src/becca/becca_loader");
|
||||||
|
const becca = require("../../src/becca/becca");
|
||||||
|
|
||||||
|
cls.init(() => {
|
||||||
|
beccaLoader.load();
|
||||||
|
|
||||||
|
const hidden = becca.getNote("_hidden");
|
||||||
|
|
||||||
|
for (const noteId of hidden.getSubtreeNoteIds({includeHidden: true})) {
|
||||||
|
if (noteId.startsWith("_")) { // is "named" note
|
||||||
|
const note = becca.getNote(noteId);
|
||||||
|
|
||||||
|
for (const attr of note.getOwnedAttributes()) {
|
||||||
|
attr.markAsDeleted("0212__delete_all_attributes_of_named_notes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@ -2,7 +2,7 @@
|
|||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"productName": "Trilium Notes",
|
"productName": "Trilium Notes",
|
||||||
"description": "Trilium Notes",
|
"description": "Trilium Notes",
|
||||||
"version": "0.58.0-beta",
|
"version": "0.58.2-beta",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"main": "electron.js",
|
"main": "electron.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -13,7 +13,7 @@ describe("Search", () => {
|
|||||||
becca.reset();
|
becca.reset();
|
||||||
|
|
||||||
rootNote = new NoteBuilder(new Note({noteId: 'root', title: 'root', type: 'text'}));
|
rootNote = new NoteBuilder(new Note({noteId: 'root', title: 'root', type: 'text'}));
|
||||||
new Branch({branchId: 'root', noteId: 'root', parentNoteId: 'none', notePosition: 10});
|
new Branch({branchId: 'none_root', noteId: 'root', parentNoteId: 'none', notePosition: 10});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("simple path match", () => {
|
it("simple path match", () => {
|
||||||
|
@ -180,7 +180,7 @@ function getNotePath(noteId) {
|
|||||||
let branchId;
|
let branchId;
|
||||||
|
|
||||||
if (note.isRoot()) {
|
if (note.isRoot()) {
|
||||||
branchId = 'root';
|
branchId = 'none_root';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const parentNote = note.parents[0];
|
const parentNote = note.parents[0];
|
||||||
|
@ -46,7 +46,10 @@ class AbstractEntity {
|
|||||||
return this.utcDateModified || this.utcDateCreated;
|
return this.utcDateModified || this.utcDateCreated;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @protected */
|
/**
|
||||||
|
* @protected
|
||||||
|
* @returns {Becca}
|
||||||
|
*/
|
||||||
get becca() {
|
get becca() {
|
||||||
if (!becca) {
|
if (!becca) {
|
||||||
becca = require('../becca');
|
becca = require('../becca');
|
||||||
@ -75,7 +78,7 @@ class AbstractEntity {
|
|||||||
/**
|
/**
|
||||||
* Saves entity - executes SQL, but doesn't commit the transaction on its own
|
* Saves entity - executes SQL, but doesn't commit the transaction on its own
|
||||||
*
|
*
|
||||||
* @returns {AbstractEntity}
|
* @returns {this}
|
||||||
*/
|
*/
|
||||||
save() {
|
save() {
|
||||||
const entityName = this.constructor.entityName;
|
const entityName = this.constructor.entityName;
|
||||||
|
@ -78,7 +78,7 @@ class Branch extends AbstractEntity {
|
|||||||
childNote.parentBranches.push(this);
|
childNote.parentBranches.push(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.branchId === 'root') {
|
if (this.noteId === 'root') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,8 +165,7 @@ class Branch extends AbstractEntity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.branchId === 'root'
|
if (this.noteId === 'root'
|
||||||
|| this.noteId === 'root'
|
|
||||||
|| this.noteId === cls.getHoistedNoteId()) {
|
|| this.noteId === cls.getHoistedNoteId()) {
|
||||||
|
|
||||||
throw new Error("Can't delete root or hoisted branch/note");
|
throw new Error("Can't delete root or hoisted branch/note");
|
||||||
@ -209,11 +208,19 @@ class Branch extends AbstractEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
beforeSaving() {
|
beforeSaving() {
|
||||||
|
if (!this.noteId || !this.parentNoteId) {
|
||||||
|
throw new Error(`noteId and parentNoteId are mandatory properties for Branch`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.branchId = `${this.parentNoteId}_${this.noteId}`;
|
||||||
|
|
||||||
if (this.notePosition === undefined || this.notePosition === null) {
|
if (this.notePosition === undefined || this.notePosition === null) {
|
||||||
let maxNotePos = 0;
|
let maxNotePos = 0;
|
||||||
|
|
||||||
for (const childBranch of this.parentNote.getChildBranches()) {
|
for (const childBranch of this.parentNote.getChildBranches()) {
|
||||||
if (maxNotePos < childBranch.notePosition && childBranch.noteId !== '_hidden') {
|
if (maxNotePos < childBranch.notePosition
|
||||||
|
&& childBranch.noteId !== '_hidden' // hidden has very large notePosition to always stay last
|
||||||
|
) {
|
||||||
maxNotePos = childBranch.notePosition;
|
maxNotePos = childBranch.notePosition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,6 +232,10 @@ class Branch extends AbstractEntity {
|
|||||||
this.isExpanded = false;
|
this.isExpanded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.prefix?.trim()) {
|
||||||
|
this.prefix = null;
|
||||||
|
}
|
||||||
|
|
||||||
this.utcDateModified = dateUtils.utcNowDateTime();
|
this.utcDateModified = dateUtils.utcNowDateTime();
|
||||||
|
|
||||||
super.beforeSaving();
|
super.beforeSaving();
|
||||||
@ -246,13 +257,20 @@ class Branch extends AbstractEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createClone(parentNoteId, notePosition) {
|
createClone(parentNoteId, notePosition) {
|
||||||
return new Branch({
|
const existingBranch = this.becca.getBranchFromChildAndParent(this.noteId, parentNoteId);
|
||||||
noteId: this.noteId,
|
|
||||||
parentNoteId: parentNoteId,
|
if (existingBranch) {
|
||||||
notePosition: notePosition,
|
existingBranch.notePosition = notePosition;
|
||||||
prefix: this.prefix,
|
return existingBranch;
|
||||||
isExpanded: this.isExpanded
|
} else {
|
||||||
});
|
return new Branch({
|
||||||
|
noteId: this.noteId,
|
||||||
|
parentNoteId: parentNoteId,
|
||||||
|
notePosition: notePosition,
|
||||||
|
prefix: this.prefix,
|
||||||
|
isExpanded: this.isExpanded
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -945,13 +945,14 @@ class Note extends AbstractEntity {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {String[]} */
|
/** @returns {String[]} - includes the subtree node as well */
|
||||||
getSubtreeNoteIds({includeArchived = true, resolveSearch = false} = {}) {
|
getSubtreeNoteIds({includeArchived = true, includeHidden = false, resolveSearch = false} = {}) {
|
||||||
return this.getSubtree({includeArchived, resolveSearch})
|
return this.getSubtree({includeArchived, includeHidden, resolveSearch})
|
||||||
.notes
|
.notes
|
||||||
.map(note => note.noteId);
|
.map(note => note.noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @deprecated use getSubtreeNoteIds() instead */
|
||||||
getDescendantNoteIds() {
|
getDescendantNoteIds() {
|
||||||
return this.getSubtreeNoteIds();
|
return this.getSubtreeNoteIds();
|
||||||
}
|
}
|
||||||
@ -1171,7 +1172,8 @@ class Note extends AbstractEntity {
|
|||||||
* @param {string} type - attribute type (label / relation)
|
* @param {string} type - attribute type (label / relation)
|
||||||
* @param {string} name - name of the attribute, not including the leading ~/#
|
* @param {string} name - name of the attribute, not including the leading ~/#
|
||||||
* @param {string} [value] - value of the attribute - text for labels, target note ID for relations; optional.
|
* @param {string} [value] - value of the attribute - text for labels, target note ID for relations; optional.
|
||||||
*
|
* @param {boolean} [isInheritable=false]
|
||||||
|
* @param {int} [position]
|
||||||
* @return {Attribute}
|
* @return {Attribute}
|
||||||
*/
|
*/
|
||||||
addAttribute(type, name, value = "", isInheritable = false, position = 1000) {
|
addAttribute(type, name, value = "", isInheritable = false, position = 1000) {
|
||||||
@ -1192,7 +1194,7 @@ class Note extends AbstractEntity {
|
|||||||
*
|
*
|
||||||
* @param {string} name - name of the label, not including the leading #
|
* @param {string} name - name of the label, not including the leading #
|
||||||
* @param {string} [value] - text value of the label; optional
|
* @param {string} [value] - text value of the label; optional
|
||||||
*
|
* @param {boolean} [isInheritable=false]
|
||||||
* @return {Attribute}
|
* @return {Attribute}
|
||||||
*/
|
*/
|
||||||
addLabel(name, value = "", isInheritable = false) {
|
addLabel(name, value = "", isInheritable = false) {
|
||||||
@ -1204,8 +1206,8 @@ class Note extends AbstractEntity {
|
|||||||
* returned.
|
* returned.
|
||||||
*
|
*
|
||||||
* @param {string} name - name of the relation, not including the leading ~
|
* @param {string} name - name of the relation, not including the leading ~
|
||||||
* @param {string} value - ID of the target note of the relation
|
* @param {string} targetNoteId
|
||||||
*
|
* @param {boolean} [isInheritable=false]
|
||||||
* @return {Attribute}
|
* @return {Attribute}
|
||||||
*/
|
*/
|
||||||
addRelation(name, targetNoteId, isInheritable = false) {
|
addRelation(name, targetNoteId, isInheritable = false) {
|
||||||
|
@ -35,15 +35,14 @@ function register(router) {
|
|||||||
existing.save();
|
existing.save();
|
||||||
|
|
||||||
return res.status(200).json(mappers.mapBranchToPojo(existing));
|
return res.status(200).json(mappers.mapBranchToPojo(existing));
|
||||||
}
|
} else {
|
||||||
|
try {
|
||||||
|
const branch = new Branch(params).save();
|
||||||
|
|
||||||
try {
|
res.status(201).json(mappers.mapBranchToPojo(branch));
|
||||||
const branch = new Branch(params).save();
|
} catch (e) {
|
||||||
|
throw new eu.EtapiError(400, eu.GENERIC_CODE, e.message);
|
||||||
res.status(201).json(mappers.mapBranchToPojo(branch));
|
}
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
throw new eu.EtapiError(400, eu.GENERIC_CODE, e.message);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -10,7 +10,9 @@ async function moveBeforeBranch(branchIdsToMove, beforeBranchId) {
|
|||||||
branchIdsToMove = filterRootNote(branchIdsToMove);
|
branchIdsToMove = filterRootNote(branchIdsToMove);
|
||||||
branchIdsToMove = filterSearchBranches(branchIdsToMove);
|
branchIdsToMove = filterSearchBranches(branchIdsToMove);
|
||||||
|
|
||||||
if (['root', '_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(beforeBranchId)) {
|
const beforeBranch = await froca.getBranch(beforeBranchId);
|
||||||
|
|
||||||
|
if (['root', '_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(beforeBranch.noteId)) {
|
||||||
toastService.showError('Cannot move notes here.');
|
toastService.showError('Cannot move notes here.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,24 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
|
|||||||
/** @property {NoteContextAwareWidget} */
|
/** @property {NoteContextAwareWidget} */
|
||||||
this.NoteContextAwareWidget = NoteContextAwareWidget;
|
this.NoteContextAwareWidget = NoteContextAwareWidget;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property {NoteContextAwareWidget}
|
||||||
|
* @deprecated use NoteContextAwareWidget instead
|
||||||
|
*/
|
||||||
|
this.TabAwareWidget = NoteContextAwareWidget;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property {NoteContextAwareWidget}
|
||||||
|
* @deprecated use NoteContextAwareWidget instead
|
||||||
|
*/
|
||||||
|
this.TabCachingWidget = NoteContextAwareWidget;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property {NoteContextAwareWidget}
|
||||||
|
* @deprecated use NoteContextAwareWidget instead
|
||||||
|
*/
|
||||||
|
this.NoteContextCachingWidget = NoteContextAwareWidget;
|
||||||
|
|
||||||
/** @property {BasicWidget} */
|
/** @property {BasicWidget} */
|
||||||
this.BasicWidget = BasicWidget;
|
this.BasicWidget = BasicWidget;
|
||||||
|
|
||||||
|
@ -5,6 +5,9 @@ import hoistedNoteService from "../services/hoisted_note.js";
|
|||||||
import appContext from "../components/app_context.js";
|
import appContext from "../components/app_context.js";
|
||||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||||
import linkContextMenuService from "../menus/link_context_menu.js";
|
import linkContextMenuService from "../menus/link_context_menu.js";
|
||||||
|
import utils from "../services/utils.js";
|
||||||
|
|
||||||
|
const esc = utils.escapeHtml;
|
||||||
|
|
||||||
const TPL = `<div class="note-map-widget" style="position: relative;">
|
const TPL = `<div class="note-map-widget" style="position: relative;">
|
||||||
<style>
|
<style>
|
||||||
@ -102,7 +105,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
|
|||||||
ctx.arc(node.x, node.y, this.noteIdToSizeMap[node.id], 0, 2 * Math.PI, false);
|
ctx.arc(node.x, node.y, this.noteIdToSizeMap[node.id], 0, 2 * Math.PI, false);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
})
|
})
|
||||||
.nodeLabel(node => node.name)
|
.nodeLabel(node => esc(node.name))
|
||||||
.maxZoom(7)
|
.maxZoom(7)
|
||||||
.warmupTicks(30)
|
.warmupTicks(30)
|
||||||
.linkDirectionalArrowLength(5)
|
.linkDirectionalArrowLength(5)
|
||||||
@ -114,7 +117,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
|
|||||||
|
|
||||||
if (this.mapType === 'link') {
|
if (this.mapType === 'link') {
|
||||||
this.graph
|
this.graph
|
||||||
.linkLabel(l => `${l.source.name} - <strong>${l.name}</strong> - ${l.target.name}`)
|
.linkLabel(l => `${esc(l.source.name)} - <strong>${esc(l.name)}</strong> - ${esc(l.target.name)}`)
|
||||||
.linkCanvasObject((link, ctx) => this.paintLink(link, ctx))
|
.linkCanvasObject((link, ctx) => this.paintLink(link, ctx))
|
||||||
.linkCanvasObjectMode(() => "after");
|
.linkCanvasObjectMode(() => "after");
|
||||||
}
|
}
|
||||||
|
@ -637,7 +637,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prepareRootNode() {
|
prepareRootNode() {
|
||||||
return this.prepareNode(froca.getBranch('root'));
|
return this.prepareNode(froca.getBranch('none_root'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -987,3 +987,7 @@ button.close:hover {
|
|||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
@ -172,7 +172,7 @@ function updateNoteAttributes(req) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// no existing attribute has been matched so we need to create a new one
|
// no existing attribute has been matched, so we need to create a new one
|
||||||
// type, name and isInheritable are immutable so even if there is an attribute with matching type & name, we need to create a new one and delete the former one
|
// type, name and isInheritable are immutable so even if there is an attribute with matching type & name, we need to create a new one and delete the former one
|
||||||
|
|
||||||
note.addAttribute(incAttr.type, incAttr.name, incAttr.value, incAttr.isInheritable, position);
|
note.addAttribute(incAttr.type, incAttr.name, incAttr.value, incAttr.isInheritable, position);
|
||||||
|
@ -143,7 +143,7 @@ function setExpanded(req) {
|
|||||||
const {branchId} = req.params;
|
const {branchId} = req.params;
|
||||||
const expanded = parseInt(req.params.expanded);
|
const expanded = parseInt(req.params.expanded);
|
||||||
|
|
||||||
if (branchId !== 'root') {
|
if (branchId !== 'none_root') {
|
||||||
sql.execute("UPDATE branches SET isExpanded = ? WHERE branchId = ?", [expanded, branchId]);
|
sql.execute("UPDATE branches SET isExpanded = ? WHERE branchId = ?", [expanded, branchId]);
|
||||||
// we don't sync expanded label
|
// we don't sync expanded label
|
||||||
// also this does not trigger updates to the frontend, this would trigger too many reloads
|
// also this does not trigger updates to the frontend, this would trigger too many reloads
|
||||||
@ -172,7 +172,7 @@ function setExpandedForSubtree(req) {
|
|||||||
SELECT branchId FROM tree`, [branchId]);
|
SELECT branchId FROM tree`, [branchId]);
|
||||||
|
|
||||||
// root is always expanded
|
// root is always expanded
|
||||||
branchIds = branchIds.filter(branchId => branchId !== 'root');
|
branchIds = branchIds.filter(branchId => branchId !== 'none_root');
|
||||||
|
|
||||||
sql.executeMany(`UPDATE branches SET isExpanded = ${expanded} WHERE branchId IN (???)`, branchIds);
|
sql.executeMany(`UPDATE branches SET isExpanded = ${expanded} WHERE branchId IN (???)`, branchIds);
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ function getNotesAndBranchesAndAttributes(noteIds) {
|
|||||||
|
|
||||||
if (noteIds.has('root')) {
|
if (noteIds.has('root')) {
|
||||||
branches.push({
|
branches.push({
|
||||||
branchId: 'root',
|
branchId: 'none_root',
|
||||||
noteId: 'root',
|
noteId: 'root',
|
||||||
parentNoteId: 'none',
|
parentNoteId: 'none',
|
||||||
notePosition: 0,
|
notePosition: 0,
|
||||||
|
@ -4,8 +4,8 @@ const build = require('./build');
|
|||||||
const packageJson = require('../../package');
|
const packageJson = require('../../package');
|
||||||
const {TRILIUM_DATA_DIR} = require('./data_dir');
|
const {TRILIUM_DATA_DIR} = require('./data_dir');
|
||||||
|
|
||||||
const APP_DB_VERSION = 209;
|
const APP_DB_VERSION = 212;
|
||||||
const SYNC_VERSION = 28;
|
const SYNC_VERSION = 29;
|
||||||
const CLIPPER_PROTOCOL_VERSION = "1.0";
|
const CLIPPER_PROTOCOL_VERSION = "1.0";
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -1 +1 @@
|
|||||||
module.exports = { buildDate:"2022-12-24T22:09:05+01:00", buildRevision: "f08fbf7bca60fc7c727d96b10785a0c5a5f93810" };
|
module.exports = { buildDate:"2022-12-29T00:12:54+01:00", buildRevision: "d36cf47974cd8bc6bd45c1da774a9a55d45f998e" };
|
||||||
|
@ -35,7 +35,7 @@ function cloneNoteToNote(noteId, parentNoteId, prefix) {
|
|||||||
isExpanded: 0
|
isExpanded: 0
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
log.info(`Cloned note ${noteId} to new parent note ${parentNoteId} with prefix ${prefix}`);
|
log.info(`Cloned note '${noteId}' to new parent note '${parentNoteId}' with prefix '${prefix}'`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@ -78,14 +78,14 @@ function ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) {
|
|||||||
return validationResult;
|
return validationResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
new Branch({
|
const branch = new Branch({
|
||||||
noteId: noteId,
|
noteId: noteId,
|
||||||
parentNoteId: parentNoteId,
|
parentNoteId: parentNoteId,
|
||||||
prefix: prefix,
|
prefix: prefix,
|
||||||
isExpanded: 0
|
isExpanded: 0
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
log.info(`Ensured note '${noteId}' is in parent note '${parentNoteId}' with prefix '${prefix}'`);
|
log.info(`Ensured note '${noteId}' is in parent note '${parentNoteId}' with prefix '${branch.prefix}'`);
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
@ -169,7 +169,7 @@ function cloneNoteAfter(noteId, afterBranchId) {
|
|||||||
isExpanded: 0
|
isExpanded: 0
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
log.info(`Cloned note '${noteId}' into parent note '${afterNote.parentNoteId}' after note '${afterNote.noteId}', branch ${afterBranchId}`);
|
log.info(`Cloned note '${noteId}' into parent note '${afterNote.parentNoteId}' after note '${afterNote.noteId}', branch '${afterBranchId}'`);
|
||||||
|
|
||||||
return { success: true, branchId: branch.branchId };
|
return { success: true, branchId: branch.branchId };
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ const Branch = require('../becca/entities/branch');
|
|||||||
const noteRevisionService = require('./note_revisions');
|
const noteRevisionService = require('./note_revisions');
|
||||||
const becca = require("../becca/becca");
|
const becca = require("../becca/becca");
|
||||||
const utils = require("../services/utils");
|
const utils = require("../services/utils");
|
||||||
const {sanitizeAttributeName} = require("./sanitize_attribute_name.js");
|
const {sanitizeAttributeName} = require("./sanitize_attribute_name");
|
||||||
const noteTypes = require("../services/note_types").getNoteTypeNames();
|
const noteTypes = require("../services/note_types").getNoteTypeNames();
|
||||||
|
|
||||||
class ConsistencyChecks {
|
class ConsistencyChecks {
|
||||||
@ -72,7 +72,7 @@ class ConsistencyChecks {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
logError(`Tree cycle detected at parent-child relationship: ${parentNoteId} - ${noteId}, whole path: ${path}`);
|
logError(`Tree cycle detected at parent-child relationship: '${parentNoteId}' - '${noteId}', whole path: '${path}'`);
|
||||||
|
|
||||||
this.unrecoveredConsistencyErrors = true;
|
this.unrecoveredConsistencyErrors = true;
|
||||||
}
|
}
|
||||||
@ -133,9 +133,9 @@ class ConsistencyChecks {
|
|||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
|
|
||||||
logFix(`Branch ${branchId} has been deleted since it references missing note ${noteId}`);
|
logFix(`Branch '${branchId}' has been deleted since it references missing note '${noteId}'`);
|
||||||
} else {
|
} else {
|
||||||
logError(`Branch ${branchId} references missing note ${noteId}`);
|
logError(`Branch '${branchId}' references missing note '${noteId}'`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -144,7 +144,7 @@ class ConsistencyChecks {
|
|||||||
FROM branches
|
FROM branches
|
||||||
LEFT JOIN notes ON notes.noteId = branches.parentNoteId
|
LEFT JOIN notes ON notes.noteId = branches.parentNoteId
|
||||||
WHERE branches.isDeleted = 0
|
WHERE branches.isDeleted = 0
|
||||||
AND branches.branchId != 'root'
|
AND branches.noteId != 'root'
|
||||||
AND notes.noteId IS NULL`,
|
AND notes.noteId IS NULL`,
|
||||||
({branchId, parentNoteId}) => {
|
({branchId, parentNoteId}) => {
|
||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
@ -154,9 +154,9 @@ class ConsistencyChecks {
|
|||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
|
|
||||||
logFix(`Branch ${branchId} was set to root parent since it was referencing missing parent note ${parentNoteId}`);
|
logFix(`Branch '${branchId}' was set to root parent since it was referencing missing parent note '${parentNoteId}'`);
|
||||||
} else {
|
} else {
|
||||||
logError(`Branch ${branchId} references missing parent note ${parentNoteId}`);
|
logError(`Branch '${branchId}' references missing parent note '${parentNoteId}'`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -173,9 +173,9 @@ class ConsistencyChecks {
|
|||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
|
|
||||||
logFix(`Attribute ${attributeId} has been deleted since it references missing source note ${noteId}`);
|
logFix(`Attribute '${attributeId}' has been deleted since it references missing source note '${noteId}'`);
|
||||||
} else {
|
} else {
|
||||||
logError(`Attribute ${attributeId} references missing source note ${noteId}`);
|
logError(`Attribute '${attributeId}' references missing source note '${noteId}'`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -193,9 +193,9 @@ class ConsistencyChecks {
|
|||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
|
|
||||||
logFix(`Relation ${attributeId} has been deleted since it references missing note ${noteId}`)
|
logFix(`Relation '${attributeId}' has been deleted since it references missing note '${noteId}'`)
|
||||||
} else {
|
} else {
|
||||||
logError(`Relation ${attributeId} references missing note ${noteId}`)
|
logError(`Relation '${attributeId}' references missing note '${noteId}'`)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -220,9 +220,9 @@ class ConsistencyChecks {
|
|||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
|
|
||||||
logFix(`Branch ${branchId} has been deleted since associated note ${noteId} is deleted.`);
|
logFix(`Branch '${branchId}' has been deleted since associated note '${noteId}' is deleted.`);
|
||||||
} else {
|
} else {
|
||||||
logError(`Branch ${branchId} is not deleted even though associated note ${noteId} is deleted.`)
|
logError(`Branch '${branchId}' is not deleted even though associated note '${noteId}' is deleted.`)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -240,9 +240,9 @@ class ConsistencyChecks {
|
|||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
|
|
||||||
logFix(`Branch ${branchId} has been deleted since associated parent note ${parentNoteId} is deleted.`);
|
logFix(`Branch '${branchId}' has been deleted since associated parent note '${parentNoteId}' is deleted.`);
|
||||||
} else {
|
} else {
|
||||||
logError(`Branch ${branchId} is not deleted even though associated parent note ${parentNoteId} is deleted.`)
|
logError(`Branch '${branchId}' is not deleted even though associated parent note '${parentNoteId}' is deleted.`)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -262,9 +262,9 @@ class ConsistencyChecks {
|
|||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
|
|
||||||
logFix(`Created missing branch ${branch.branchId} for note ${noteId}`);
|
logFix(`Created missing branch '${branch.branchId}' for note '${noteId}'`);
|
||||||
} else {
|
} else {
|
||||||
logError(`No undeleted branch found for note ${noteId}`);
|
logError(`No undeleted branch found for note '${noteId}'`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -296,12 +296,12 @@ class ConsistencyChecks {
|
|||||||
for (const branch of branches.slice(1)) {
|
for (const branch of branches.slice(1)) {
|
||||||
branch.markAsDeleted();
|
branch.markAsDeleted();
|
||||||
|
|
||||||
logFix(`Removing branch ${branch.branchId} since it's parent-child duplicate of branch ${origBranch.branchId}`);
|
logFix(`Removing branch '${branch.branchId}' since it's a parent-child duplicate of branch '${origBranch.branchId}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
} else {
|
} else {
|
||||||
logError(`Duplicate branches for note ${noteId} and parent ${parentNoteId}`);
|
logError(`Duplicate branches for note '${noteId}' and parent '${parentNoteId}'`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -322,9 +322,9 @@ class ConsistencyChecks {
|
|||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
|
|
||||||
logFix(`Note ${noteId} type has been change to file since it had invalid type=${type}`)
|
logFix(`Note '${noteId}' type has been change to file since it had invalid type '${type}'`)
|
||||||
} else {
|
} else {
|
||||||
logError(`Note ${noteId} has invalid type=${type}`);
|
logError(`Note '${noteId}' has invalid type '${type}'`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -361,9 +361,9 @@ class ConsistencyChecks {
|
|||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
|
|
||||||
logFix(`Note ${noteId} content was set to empty string since there was no corresponding row`);
|
logFix(`Note '${noteId}' content was set to empty string since there was no corresponding row`);
|
||||||
} else {
|
} else {
|
||||||
logError(`Note ${noteId} content row does not exist`);
|
logError(`Note '${noteId}' content row does not exist`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -385,9 +385,9 @@ class ConsistencyChecks {
|
|||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
|
|
||||||
logFix(`Note ${noteId} content was set to "${blankContent}" since it was null even though it is not deleted`);
|
logFix(`Note '${noteId}' content was set to '${blankContent}' since it was null even though it is not deleted`);
|
||||||
} else {
|
} else {
|
||||||
logError(`Note ${noteId} content is null even though it is not deleted`);
|
logError(`Note '${noteId}' content is null even though it is not deleted`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -404,9 +404,9 @@ class ConsistencyChecks {
|
|||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
|
|
||||||
logFix(`Note revision content ${noteRevisionId} was created and set to erased since it did not exist.`);
|
logFix(`Note revision content '${noteRevisionId}' was created and set to erased since it did not exist.`);
|
||||||
} else {
|
} else {
|
||||||
logError(`Note revision content ${noteRevisionId} does not exist`);
|
logError(`Note revision content '${noteRevisionId}' does not exist`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -431,12 +431,12 @@ class ConsistencyChecks {
|
|||||||
branch.parentNoteId = 'root';
|
branch.parentNoteId = 'root';
|
||||||
branch.save();
|
branch.save();
|
||||||
|
|
||||||
logFix(`Child branch ${branch.branchId} has been moved to root since it was a child of a search note ${parentNoteId}`)
|
logFix(`Child branch '${branch.branchId}' has been moved to root since it was a child of a search note '${parentNoteId}'`)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
} else {
|
} else {
|
||||||
logError(`Search note ${parentNoteId} has children`);
|
logError(`Search note '${parentNoteId}' has children`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -453,9 +453,9 @@ class ConsistencyChecks {
|
|||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
|
|
||||||
logFix(`Removed relation ${relation.attributeId} of name "${relation.name} with empty target.`);
|
logFix(`Removed relation '${relation.attributeId}' of name '${relation.name}' with empty target.`);
|
||||||
} else {
|
} else {
|
||||||
logError(`Relation ${attributeId} has empty target.`);
|
logError(`Relation '${attributeId}' has empty target.`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -474,9 +474,9 @@ class ConsistencyChecks {
|
|||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
|
|
||||||
logFix(`Attribute ${attributeId} type was changed to label since it had invalid type '${type}'`);
|
logFix(`Attribute '${attributeId}' type was changed to label since it had invalid type '${type}'`);
|
||||||
} else {
|
} else {
|
||||||
logError(`Attribute ${attributeId} has invalid type '${type}'`);
|
logError(`Attribute '${attributeId}' has invalid type '${type}'`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -494,9 +494,9 @@ class ConsistencyChecks {
|
|||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
|
|
||||||
logFix(`Removed attribute ${attributeId} because owning note ${noteId} is also deleted.`);
|
logFix(`Removed attribute '${attributeId}' because owning note '${noteId}' is also deleted.`);
|
||||||
} else {
|
} else {
|
||||||
logError(`Attribute ${attributeId} is not deleted even though owning note ${noteId} is deleted.`);
|
logError(`Attribute '${attributeId}' is not deleted even though owning note '${noteId}' is deleted.`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -515,9 +515,9 @@ class ConsistencyChecks {
|
|||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
|
|
||||||
logFix(`Removed attribute ${attributeId} because target note ${targetNoteId} is also deleted.`);
|
logFix(`Removed attribute '${attributeId}' because target note '${targetNoteId}' is also deleted.`);
|
||||||
} else {
|
} else {
|
||||||
logError(`Attribute ${attributeId} is not deleted even though target note ${targetNoteId} is deleted.`);
|
logError(`Attribute '${attributeId}' is not deleted even though target note '${targetNoteId}' is deleted.`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -545,9 +545,9 @@ class ConsistencyChecks {
|
|||||||
isSynced: entityName !== 'options' || entity.isSynced
|
isSynced: entityName !== 'options' || entity.isSynced
|
||||||
});
|
});
|
||||||
|
|
||||||
logFix(`Created missing entity change for entityName=${entityName}, entityId=${entityId}`);
|
logFix(`Created missing entity change for entityName '${entityName}', entityId '${entityId}'`);
|
||||||
} else {
|
} else {
|
||||||
logError(`Missing entity change for entityName=${entityName}, entityId=${entityId}`);
|
logError(`Missing entity change for entityName '${entityName}', entityId '${entityId}'`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -565,9 +565,9 @@ class ConsistencyChecks {
|
|||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
sql.execute("DELETE FROM entity_changes WHERE entityName = ? AND entityId = ?", [entityName, entityId]);
|
sql.execute("DELETE FROM entity_changes WHERE entityName = ? AND entityId = ?", [entityName, entityId]);
|
||||||
|
|
||||||
logFix(`Deleted extra entity change id=${id}, entityName=${entityName}, entityId=${entityId}`);
|
logFix(`Deleted extra entity change id '${id}', entityName '${entityName}', entityId '${entityId}'`);
|
||||||
} else {
|
} else {
|
||||||
logError(`Unrecognized entity change id=${id}, entityName=${entityName}, entityId=${entityId}`);
|
logError(`Unrecognized entity change id '${id}', entityName '${entityName}', entityId '${entityId}'`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -586,9 +586,9 @@ class ConsistencyChecks {
|
|||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
|
|
||||||
logFix(`Erasing entityName=${entityName}, entityId=${entityId} since entity change id=${id} has it as erased.`);
|
logFix(`Erasing entityName '${entityName}', entityId '${entityId}' since entity change id '${id}' has it as erased.`);
|
||||||
} else {
|
} else {
|
||||||
logError(`Entity change id=${id} has entityName=${entityName}, entityId=${entityId} as erased, but it's not.`);
|
logError(`Entity change id '${id}' has entityName '${entityName}', entityId '${entityId}' as erased, but it's not.`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -621,12 +621,12 @@ class ConsistencyChecks {
|
|||||||
this.fixedIssues = true;
|
this.fixedIssues = true;
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
|
|
||||||
logFix(`Renamed incorrectly named attributes "${origName}" to ${fixedName}`);
|
logFix(`Renamed incorrectly named attributes '${origName}' to '${fixedName}'`);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.unrecoveredConsistencyErrors = true;
|
this.unrecoveredConsistencyErrors = true;
|
||||||
|
|
||||||
logFix(`There are incorrectly named attributes "${origName}"`);
|
logFix(`There are incorrectly named attributes '${origName}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -670,7 +670,7 @@ class ConsistencyChecks {
|
|||||||
this.findSyncIssues();
|
this.findSyncIssues();
|
||||||
|
|
||||||
// root branch should always be expanded
|
// root branch should always be expanded
|
||||||
sql.execute("UPDATE branches SET isExpanded = 1 WHERE branchId = 'root'");
|
sql.execute("UPDATE branches SET isExpanded = 1 WHERE noteId = 'root'");
|
||||||
|
|
||||||
if (!this.unrecoveredConsistencyErrors) {
|
if (!this.unrecoveredConsistencyErrors) {
|
||||||
// we run this only if basic checks passed since this assumes basic data consistency
|
// we run this only if basic checks passed since this assumes basic data consistency
|
||||||
@ -701,13 +701,7 @@ class ConsistencyChecks {
|
|||||||
let elapsedTimeMs;
|
let elapsedTimeMs;
|
||||||
|
|
||||||
await syncMutexService.doExclusively(() => {
|
await syncMutexService.doExclusively(() => {
|
||||||
const startTimeMs = Date.now();
|
elapsedTimeMs = this.runChecksInner();
|
||||||
|
|
||||||
this.runDbDiagnostics();
|
|
||||||
|
|
||||||
this.runAllChecksAndFixers();
|
|
||||||
|
|
||||||
elapsedTimeMs = Date.now() - startTimeMs;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.unrecoveredConsistencyErrors) {
|
if (this.unrecoveredConsistencyErrors) {
|
||||||
@ -721,6 +715,16 @@ class ConsistencyChecks {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runChecksInner() {
|
||||||
|
const startTimeMs = Date.now();
|
||||||
|
|
||||||
|
this.runDbDiagnostics();
|
||||||
|
|
||||||
|
this.runAllChecksAndFixers();
|
||||||
|
|
||||||
|
return Date.now() - startTimeMs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBlankContent(isProtected, type, mime) {
|
function getBlankContent(isProtected, type, mime) {
|
||||||
@ -750,9 +754,14 @@ function runPeriodicChecks() {
|
|||||||
consistencyChecks.runChecks();
|
consistencyChecks.runChecks();
|
||||||
}
|
}
|
||||||
|
|
||||||
function runOnDemandChecks(autoFix) {
|
async function runOnDemandChecks(autoFix) {
|
||||||
const consistencyChecks = new ConsistencyChecks(autoFix);
|
const consistencyChecks = new ConsistencyChecks(autoFix);
|
||||||
consistencyChecks.runChecks();
|
await consistencyChecks.runChecks();
|
||||||
|
}
|
||||||
|
|
||||||
|
function runOnDemandChecksWithoutExclusiveLock(autoFix) {
|
||||||
|
const consistencyChecks = new ConsistencyChecks(autoFix);
|
||||||
|
consistencyChecks.runChecksInner();
|
||||||
}
|
}
|
||||||
|
|
||||||
function runEntityChangesChecks() {
|
function runEntityChangesChecks() {
|
||||||
@ -769,5 +778,6 @@ sqlInit.dbReady.then(() => {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
runOnDemandChecks,
|
runOnDemandChecks,
|
||||||
|
runOnDemandChecksWithoutExclusiveLock,
|
||||||
runEntityChangesChecks
|
runEntityChangesChecks
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,7 @@ const becca = require("../becca/becca");
|
|||||||
|
|
||||||
let maxEntityChangeId = 0;
|
let maxEntityChangeId = 0;
|
||||||
|
|
||||||
function addEntityChangeWithinstanceId(origEntityChange, instanceId) {
|
function addEntityChangeWithInstanceId(origEntityChange, instanceId) {
|
||||||
const ec = {...origEntityChange, instanceId};
|
const ec = {...origEntityChange, instanceId};
|
||||||
|
|
||||||
return addEntityChange(ec);
|
return addEntityChange(ec);
|
||||||
@ -71,7 +71,7 @@ function addEntityChangesForSector(entityName, sector) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
log.info(`Added sector ${sector} of ${entityName} to sync queue in ${Date.now() - startTime}ms.`);
|
log.info(`Added sector ${sector} of '${entityName}' to sync queue in ${Date.now() - startTime}ms.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanupEntityChangesForMissingEntities(entityName, entityPrimaryKey) {
|
function cleanupEntityChangesForMissingEntities(entityName, entityPrimaryKey) {
|
||||||
@ -85,45 +85,38 @@ function cleanupEntityChangesForMissingEntities(entityName, entityPrimaryKey) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function fillEntityChanges(entityName, entityPrimaryKey, condition = '') {
|
function fillEntityChanges(entityName, entityPrimaryKey, condition = '') {
|
||||||
try {
|
cleanupEntityChangesForMissingEntities(entityName, entityPrimaryKey);
|
||||||
cleanupEntityChangesForMissingEntities(entityName, entityPrimaryKey);
|
|
||||||
|
|
||||||
sql.transactional(() => {
|
sql.transactional(() => {
|
||||||
const entityIds = sql.getColumn(`SELECT ${entityPrimaryKey} FROM ${entityName}`
|
const entityIds = sql.getColumn(`SELECT ${entityPrimaryKey} FROM ${entityName}`
|
||||||
+ (condition ? ` WHERE ${condition}` : ''));
|
+ (condition ? ` WHERE ${condition}` : ''));
|
||||||
|
|
||||||
let createdCount = 0;
|
let createdCount = 0;
|
||||||
|
|
||||||
for (const entityId of entityIds) {
|
for (const entityId of entityIds) {
|
||||||
const existingRows = sql.getValue("SELECT COUNT(1) FROM entity_changes WHERE entityName = ? AND entityId = ?", [entityName, entityId]);
|
const existingRows = sql.getValue("SELECT COUNT(1) FROM entity_changes WHERE entityName = ? AND entityId = ?", [entityName, entityId]);
|
||||||
|
|
||||||
// we don't want to replace existing entities (which would effectively cause full resync)
|
// we don't want to replace existing entities (which would effectively cause full resync)
|
||||||
if (existingRows === 0) {
|
if (existingRows === 0) {
|
||||||
createdCount++;
|
createdCount++;
|
||||||
|
|
||||||
const entity = becca.getEntity(entityName, entityId);
|
const entity = becca.getEntity(entityName, entityId);
|
||||||
|
|
||||||
addEntityChange({
|
addEntityChange({
|
||||||
entityName,
|
entityName,
|
||||||
entityId,
|
entityId,
|
||||||
hash: entity.generateHash(),
|
hash: entity.generateHash(),
|
||||||
isErased: false,
|
isErased: false,
|
||||||
utcDateChanged: entity.getUtcDateChanged(),
|
utcDateChanged: entity.getUtcDateChanged(),
|
||||||
isSynced: entityName !== 'options' || !!entity.isSynced
|
isSynced: entityName !== 'options' || !!entity.isSynced
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (createdCount > 0) {
|
if (createdCount > 0) {
|
||||||
log.info(`Created ${createdCount} missing entity changes for ${entityName}.`);
|
log.info(`Created ${createdCount} missing entity changes for ${entityName}.`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
// this is to fix migration from 0.30 to 0.32, can be removed later
|
|
||||||
// see https://github.com/zadam/trilium/issues/557
|
|
||||||
log.error(`Filling entity changes failed for ${entityName} ${entityPrimaryKey} with error "${e.message}", continuing`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function fillAllEntityChanges() {
|
function fillAllEntityChanges() {
|
||||||
@ -145,7 +138,7 @@ module.exports = {
|
|||||||
addNoteReorderingEntityChange,
|
addNoteReorderingEntityChange,
|
||||||
moveEntityChangeToTop,
|
moveEntityChangeToTop,
|
||||||
addEntityChange,
|
addEntityChange,
|
||||||
addEntityChangeWithinstanceId,
|
addEntityChangeWithInstanceId,
|
||||||
fillAllEntityChanges,
|
fillAllEntityChanges,
|
||||||
addEntityChangesForSector,
|
addEntityChangesForSector,
|
||||||
getMaxEntityChangeId: () => maxEntityChangeId
|
getMaxEntityChangeId: () => maxEntityChangeId
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const becca = require("../becca/becca");
|
const becca = require("../becca/becca");
|
||||||
const noteService = require("./notes");
|
const noteService = require("./notes");
|
||||||
|
const Attribute = require("../becca/entities/attribute.js");
|
||||||
|
|
||||||
const LBTPL_ROOT = "_lbTplRoot";
|
const LBTPL_ROOT = "_lbTplRoot";
|
||||||
const LBTPL_BASE = "_lbTplBase";
|
const LBTPL_BASE = "_lbTplBase";
|
||||||
@ -10,6 +11,12 @@ const LBTPL_BUILTIN_WIDGET = "_lbTplBuiltinWidget";
|
|||||||
const LBTPL_SPACER = "_lbTplSpacer";
|
const LBTPL_SPACER = "_lbTplSpacer";
|
||||||
const LBTPL_CUSTOM_WIDGET = "_lbTplCustomWidget";
|
const LBTPL_CUSTOM_WIDGET = "_lbTplCustomWidget";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hidden subtree is generated as a "predictable structure" which means that it avoids generating random IDs to always
|
||||||
|
* produce same structure. This is needed because it is run on multiple instances in the sync cluster which might produce
|
||||||
|
* duplicate subtrees. This way, all instances will generate the same structure with same IDs.
|
||||||
|
*/
|
||||||
|
|
||||||
const HIDDEN_SUBTREE_DEFINITION = {
|
const HIDDEN_SUBTREE_DEFINITION = {
|
||||||
id: '_hidden',
|
id: '_hidden',
|
||||||
title: 'Hidden Notes',
|
title: 'Hidden Notes',
|
||||||
@ -243,13 +250,7 @@ function checkHiddenSubtreeRecursively(parentNoteId, item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let note = becca.notes[item.id];
|
let note = becca.notes[item.id];
|
||||||
let branch = becca.branches[item.id];
|
let branch;
|
||||||
|
|
||||||
const attrs = [...(item.attributes || [])];
|
|
||||||
|
|
||||||
if (item.icon) {
|
|
||||||
attrs.push({ type: 'label', name: 'iconClass', value: `bx ${item.icon}` });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!note) {
|
if (!note) {
|
||||||
({note, branch} = noteService.createNewNote({
|
({note, branch} = noteService.createNewNote({
|
||||||
@ -260,27 +261,35 @@ function checkHiddenSubtreeRecursively(parentNoteId, item) {
|
|||||||
content: '',
|
content: '',
|
||||||
ignoreForbiddenParents: true
|
ignoreForbiddenParents: true
|
||||||
}));
|
}));
|
||||||
|
} else {
|
||||||
|
branch = note.getParentBranches().find(branch => branch.parentNoteId === parentNoteId);
|
||||||
|
}
|
||||||
|
|
||||||
if (item.type === 'launcher') {
|
const attrs = [...(item.attributes || [])];
|
||||||
if (item.command) {
|
|
||||||
attrs.push({ type: 'relation', name: 'template', value: LBTPL_COMMAND });
|
|
||||||
attrs.push({ type: 'label', name: 'command', value: item.command });
|
|
||||||
} else if (item.builtinWidget) {
|
|
||||||
if (item.builtinWidget === 'spacer') {
|
|
||||||
attrs.push({ type: 'relation', name: 'template', value: LBTPL_SPACER });
|
|
||||||
attrs.push({ type: 'label', name: 'baseSize', value: item.baseSize });
|
|
||||||
attrs.push({ type: 'label', name: 'growthFactor', value: item.growthFactor });
|
|
||||||
} else {
|
|
||||||
attrs.push({ type: 'relation', name: 'template', value: LBTPL_BUILTIN_WIDGET });
|
|
||||||
}
|
|
||||||
|
|
||||||
attrs.push({ type: 'label', name: 'builtinWidget', value: item.builtinWidget });
|
if (item.icon) {
|
||||||
} else if (item.targetNoteId) {
|
attrs.push({ type: 'label', name: 'iconClass', value: `bx ${item.icon}` });
|
||||||
attrs.push({ type: 'relation', name: 'template', value: LBTPL_NOTE_LAUNCHER });
|
}
|
||||||
attrs.push({ type: 'relation', name: 'target', value: item.targetNoteId });
|
|
||||||
|
if (item.type === 'launcher') {
|
||||||
|
if (item.command) {
|
||||||
|
attrs.push({ type: 'relation', name: 'template', value: LBTPL_COMMAND });
|
||||||
|
attrs.push({ type: 'label', name: 'command', value: item.command });
|
||||||
|
} else if (item.builtinWidget) {
|
||||||
|
if (item.builtinWidget === 'spacer') {
|
||||||
|
attrs.push({ type: 'relation', name: 'template', value: LBTPL_SPACER });
|
||||||
|
attrs.push({ type: 'label', name: 'baseSize', value: item.baseSize });
|
||||||
|
attrs.push({ type: 'label', name: 'growthFactor', value: item.growthFactor });
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`No action defined for launcher ${JSON.stringify(item)}`);
|
attrs.push({ type: 'relation', name: 'template', value: LBTPL_BUILTIN_WIDGET });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attrs.push({ type: 'label', name: 'builtinWidget', value: item.builtinWidget });
|
||||||
|
} else if (item.targetNoteId) {
|
||||||
|
attrs.push({ type: 'relation', name: 'template', value: LBTPL_NOTE_LAUNCHER });
|
||||||
|
attrs.push({ type: 'relation', name: 'target', value: item.targetNoteId });
|
||||||
|
} else {
|
||||||
|
throw new Error(`No action defined for launcher ${JSON.stringify(item)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,8 +314,17 @@ function checkHiddenSubtreeRecursively(parentNoteId, item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const attr of attrs) {
|
for (const attr of attrs) {
|
||||||
if (!note.hasAttribute(attr.type, attr.name)) {
|
const attrId = note.noteId + "_" + attr.type.charAt(0) + attr.name;
|
||||||
note.addAttribute(attr.type, attr.name, attr.value);
|
|
||||||
|
if (!note.getAttributes().find(attr => attr.attributeId === attrId)) {
|
||||||
|
new Attribute({
|
||||||
|
attributeId: attrId,
|
||||||
|
noteId: note.noteId,
|
||||||
|
type: attr.type,
|
||||||
|
name: attr.name,
|
||||||
|
value: attr.value,
|
||||||
|
isInheritable: false
|
||||||
|
}).save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ async function migrate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fs.readdirSync(resourceDir.MIGRATIONS_DIR).forEach(file => {
|
fs.readdirSync(resourceDir.MIGRATIONS_DIR).forEach(file => {
|
||||||
const match = file.match(/([0-9]{4})__([a-zA-Z0-9_ ]+)\.(sql|js)/);
|
const match = file.match(/^([0-9]{4})__([a-zA-Z0-9_ ]+)\.(sql|js)$/);
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
const dbVersion = parseInt(match[1]);
|
const dbVersion = parseInt(match[1]);
|
||||||
@ -62,9 +62,10 @@ async function migrate() {
|
|||||||
log.info(`Migration to version ${mig.dbVersion} has been successful.`);
|
log.info(`Migration to version ${mig.dbVersion} has been successful.`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error(`error during migration to version ${mig.dbVersion}: ${e.stack}`);
|
log.error(`error during migration to version ${mig.dbVersion}: ${e.stack}`);
|
||||||
log.error("migration failed, crashing hard"); // this is not very user friendly :-/
|
log.error("migration failed, crashing hard"); // this is not very user-friendly :-/
|
||||||
|
|
||||||
utils.crash();
|
utils.crash();
|
||||||
|
break; // crash() above does not seem to work right away
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -155,6 +155,9 @@ function createNewNote(params) {
|
|||||||
cls.disableEntityEvents();
|
cls.disableEntityEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: think about what can happen if the note already exists with the forced ID
|
||||||
|
// I guess on DB it's going to be fine, but becca references between entities
|
||||||
|
// might get messed up (two Note instance for the same ID existing in the references)
|
||||||
note = new Note({
|
note = new Note({
|
||||||
noteId: params.noteId, // optionally can force specific noteId
|
noteId: params.noteId, // optionally can force specific noteId
|
||||||
title: params.title,
|
title: params.title,
|
||||||
|
@ -51,9 +51,9 @@ function runNotesWithLabel(runAttrValue) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sqlInit.dbReady.then(() => {
|
sqlInit.dbReady.then(() => {
|
||||||
if (!process.env.TRILIUM_SAFE_MODE) {
|
cls.init(() => hiddenSubtreeService.checkHiddenSubtree());
|
||||||
cls.init(() => hiddenSubtreeService.checkHiddenSubtree());
|
|
||||||
|
|
||||||
|
if (!process.env.TRILIUM_SAFE_MODE) {
|
||||||
setTimeout(cls.wrap(() => runNotesWithLabel('backendStartup')), 10 * 1000);
|
setTimeout(cls.wrap(() => runNotesWithLabel('backendStartup')), 10 * 1000);
|
||||||
|
|
||||||
setInterval(cls.wrap(() => runNotesWithLabel('hourly')), 3600 * 1000);
|
setInterval(cls.wrap(() => runNotesWithLabel('hourly')), 3600 * 1000);
|
||||||
|
@ -77,7 +77,6 @@ async function createInitialDatabase() {
|
|||||||
rootNote.setContent('');
|
rootNote.setContent('');
|
||||||
|
|
||||||
new Branch({
|
new Branch({
|
||||||
branchId: 'root',
|
|
||||||
noteId: 'root',
|
noteId: 'root',
|
||||||
parentNoteId: 'none',
|
parentNoteId: 'none',
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
|
@ -42,7 +42,7 @@ function updateEntity(entityChange, entityRow, instanceId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNormalEntity(remoteEntityChange, entity, instanceId) {
|
function updateNormalEntity(remoteEntityChange, remoteEntityRow, instanceId) {
|
||||||
const localEntityChange = sql.getRow(`
|
const localEntityChange = sql.getRow(`
|
||||||
SELECT utcDateChanged, hash, isErased
|
SELECT utcDateChanged, hash, isErased
|
||||||
FROM entity_changes
|
FROM entity_changes
|
||||||
@ -54,7 +54,7 @@ function updateNormalEntity(remoteEntityChange, entity, instanceId) {
|
|||||||
|
|
||||||
sql.execute(`DELETE FROM ${remoteEntityChange.entityName} WHERE ${primaryKey} = ?`, remoteEntityChange.entityId);
|
sql.execute(`DELETE FROM ${remoteEntityChange.entityName} WHERE ${primaryKey} = ?`, remoteEntityChange.entityId);
|
||||||
|
|
||||||
entityChangesService.addEntityChangeWithinstanceId(remoteEntityChange, instanceId);
|
entityChangesService.addEntityChangeWithInstanceId(remoteEntityChange, instanceId);
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -65,13 +65,13 @@ function updateNormalEntity(remoteEntityChange, entity, instanceId) {
|
|||||||
|| localEntityChange.hash !== remoteEntityChange.hash // sync error, we should still update
|
|| localEntityChange.hash !== remoteEntityChange.hash // sync error, we should still update
|
||||||
) {
|
) {
|
||||||
if (['note_contents', 'note_revision_contents'].includes(remoteEntityChange.entityName)) {
|
if (['note_contents', 'note_revision_contents'].includes(remoteEntityChange.entityName)) {
|
||||||
entity.content = handleContent(entity.content);
|
remoteEntityRow.content = handleContent(remoteEntityRow.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
sql.transactional(() => {
|
sql.transactional(() => {
|
||||||
sql.replace(remoteEntityChange.entityName, entity);
|
sql.replace(remoteEntityChange.entityName, remoteEntityRow);
|
||||||
|
|
||||||
entityChangesService.addEntityChangeWithinstanceId(remoteEntityChange, instanceId);
|
entityChangesService.addEntityChangeWithInstanceId(remoteEntityChange, instanceId);
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -86,15 +86,16 @@ function updateNoteReordering(entityChange, entity, instanceId) {
|
|||||||
sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [entity[key], key]);
|
sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [entity[key], key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
entityChangesService.addEntityChangeWithinstanceId(entityChange, instanceId);
|
entityChangesService.addEntityChangeWithInstanceId(entityChange, instanceId);
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleContent(content) {
|
function handleContent(content) {
|
||||||
// we always use Buffer object which is different from normal saving - there we use simple string type for "string notes"
|
// we always use Buffer object which is different from normal saving - there we use simple string type for
|
||||||
// the problem is that in general it's not possible to whether a note_content is string note or note (syncs can arrive out of order)
|
// "string notes". The problem is that in general it's not possible to detect whether a note_content
|
||||||
|
// is string note or note (syncs can arrive out of order)
|
||||||
content = content === null ? null : Buffer.from(content, 'base64');
|
content = content === null ? null : Buffer.from(content, 'base64');
|
||||||
|
|
||||||
if (content && content.byteLength === 0) {
|
if (content && content.byteLength === 0) {
|
||||||
@ -109,7 +110,7 @@ function eraseEntity(entityChange, instanceId) {
|
|||||||
const {entityName, entityId} = entityChange;
|
const {entityName, entityId} = entityChange;
|
||||||
|
|
||||||
if (!["notes", "note_contents", "branches", "attributes", "note_revisions", "note_revision_contents"].includes(entityName)) {
|
if (!["notes", "note_contents", "branches", "attributes", "note_revisions", "note_revision_contents"].includes(entityName)) {
|
||||||
log.error(`Cannot erase entity ${entityName}, id ${entityId}`);
|
log.error(`Cannot erase entity '${entityName}', id '${entityId}'`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +120,7 @@ function eraseEntity(entityChange, instanceId) {
|
|||||||
|
|
||||||
eventService.emit(eventService.ENTITY_DELETE_SYNCED, { entityName, entityId });
|
eventService.emit(eventService.ENTITY_DELETE_SYNCED, { entityName, entityId });
|
||||||
|
|
||||||
entityChangesService.addEntityChangeWithinstanceId(entityChange, instanceId);
|
entityChangesService.addEntityChangeWithInstanceId(entityChange, instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user