Compare commits

...

42 Commits

Author SHA1 Message Date
zadam
0995c56506 release 0.58.3-beta 2023-01-04 22:36:31 +01:00
zadam
3a5fa2954d Merge remote-tracking branch 'origin/master' 2023-01-03 22:36:56 +01:00
zadam
398480415e package-lock.json 2023-01-03 22:36:41 +01:00
zadam
81013ed21a Merge branch 'stable'
# Conflicts:
#	package-lock.json
#	package.json
2023-01-03 22:35:45 +01:00
zadam
53360c147a npm script improvements 2023-01-03 22:34:31 +01:00
zadam
9e47da3f03 Merge pull request #3150 from zadam/dependabot/npm_and_yarn/json-schema-and-jsprim-0.4.0
Bump json-schema and jsprim
2023-01-03 22:32:36 +01:00
zadam
faefe10c15 Merge pull request #3423 from zadam/dependabot/npm_and_yarn/terser-5.16.1
Bump terser from 5.10.0 to 5.16.1
2023-01-03 22:32:15 +01:00
zadam
a4d3150a24 Merge pull request #2739 from zadam/dependabot/npm_and_yarn/nanoid-3.3.1
Bump nanoid from 3.1.30 to 3.3.1
2023-01-03 22:31:57 +01:00
zadam
e1f0676891 Merge pull request #3197 from zadam/dependabot/npm_and_yarn/dump-db/minimist-1.2.7
Bump minimist from 1.2.5 to 1.2.7 in /dump-db
2023-01-03 22:31:08 +01:00
zadam
792ac9b77d Merge pull request #3408 from zadam/dependabot/npm_and_yarn/qs-6.5.3
Bump qs from 6.5.2 to 6.5.3
2023-01-03 22:30:42 +01:00
zadam
bbbd2f7e82 Merge pull request #3481 from contributor/feature/safeVsCodeLaunch
Safe Vs Code debug launch
2023-01-03 22:29:36 +01:00
zadam
d910191e83 Merge pull request #3480 from contributor/feature/pageUrlSanitizeUrl
Fix pageUrl and clipping selection can create multiple notes for the same Url
2023-01-03 22:28:37 +01:00
zadam
e8a9389e6d remove console.log 2023-01-03 21:44:47 +01:00
zadam
6dce13bc34 export root note fix, #2346 2023-01-03 21:30:49 +01:00
contributor
71cdea3902 fix vscode launch can break release app data 2023-01-03 21:39:35 +02:00
contributor
85a6e8b47e fix clipping selection can create multiple notes for the same Url
sanitize was replacing '&' char to '&'and changing actual Url
2023-01-03 21:18:23 +02:00
contributor
f02ad63e97 npm i @braintree/sanitize-url 2023-01-03 21:18:23 +02:00
contributor
977399a73e refac: better naming 2023-01-03 21:18:23 +02:00
zadam
291ef87c9b fix edit button 2023-01-02 22:32:05 +01:00
zadam
115bf0af4d fix button paddings 2023-01-02 21:08:20 +01:00
zadam
e40f1fd11a fix inclusion of the image into the correct position in ENEX import, closes #3468 2023-01-01 21:02:54 +01:00
zadam
797ddf6205 search hit with exact note title match gets a strong score boost, closes #3470 2022-12-30 23:14:48 +01:00
zadam
ca4e1c19a7 make utils.normalize null-safe, #3469 2022-12-30 21:00:42 +01:00
zadam
dd82b0f958 Merge remote-tracking branch 'origin/master' 2022-12-29 10:25:54 +01:00
zadam
f150c223bc improved ZIP import validation and error handling 2022-12-29 10:25:49 +01:00
zadam
4f771cfa7a Merge pull request #3467 from contributor/feature/enex-pageUrl
Evernote import - use pageUrl attribute for enex source-url
2022-12-29 09:57:43 +01:00
contributor
aa3b26c965 Evernote import - use pageUrl attribute for enex source-url 2022-12-29 01:27:17 +02:00
zadam
f4cf04232f release 0.58.2-beta 2022-12-29 00:12:54 +01:00
zadam
d36cf47974 no-dir npm scripts 2022-12-28 23:17:59 +01:00
zadam
f809da58ec fix occurrences of 'root' branch 2022-12-28 13:09:49 +01:00
zadam
ff1f01be8c erase deleted notes in the migration 2022-12-28 12:58:53 +01:00
zadam
0ddaa8f5c2 migration fixes 2022-12-27 21:17:40 +01:00
zadam
620bed73bb put back the removed widgets, the BC break is not worth it 2022-12-27 15:05:12 +01:00
zadam
c177d2b97b fix migration 2022-12-27 14:52:32 +01:00
zadam
0758c82983 hidden subtree is now built as "predictable" to avoid duplicating it in sync 2022-12-27 14:44:28 +01:00
zadam
ecc2ed7d73 formatting 2022-12-27 10:22:50 +01:00
zadam
4b779d7512 small refactors in sync 2022-12-27 09:53:25 +01:00
dependabot[bot]
c409d7ff2a Bump qs from 6.5.2 to 6.5.3
Bumps [qs](https://github.com/ljharb/qs) from 6.5.2 to 6.5.3.
- [Release notes](https://github.com/ljharb/qs/releases)
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.5.2...v6.5.3)

---
updated-dependencies:
- dependency-name: qs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-15 15:39:13 +00:00
dependabot[bot]
f8dd175837 Bump terser from 5.10.0 to 5.16.1
Bumps [terser](https://github.com/terser/terser) from 5.10.0 to 5.16.1.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/compare/v5.10.0...v5.16.1)

---
updated-dependencies:
- dependency-name: terser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-15 15:39:04 +00:00
dependabot[bot]
df3212c304 Bump minimist from 1.2.5 to 1.2.7 in /dump-db
Bumps [minimist](https://github.com/minimistjs/minimist) from 1.2.5 to 1.2.7.
- [Release notes](https://github.com/minimistjs/minimist/releases)
- [Changelog](https://github.com/minimistjs/minimist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/minimistjs/minimist/compare/v1.2.5...v1.2.7)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-14 22:50:02 +00:00
dependabot[bot]
6ec734df13 Bump json-schema and jsprim
Bumps [json-schema](https://github.com/kriszyp/json-schema) and [jsprim](https://github.com/joyent/node-jsprim). These dependencies needed to be updated together.

Updates `json-schema` from 0.2.3 to 0.4.0
- [Release notes](https://github.com/kriszyp/json-schema/releases)
- [Commits](https://github.com/kriszyp/json-schema/compare/v0.2.3...v0.4.0)

Updates `jsprim` from 1.4.1 to 1.4.2
- [Release notes](https://github.com/joyent/node-jsprim/releases)
- [Changelog](https://github.com/TritonDataCenter/node-jsprim/blob/v1.4.2/CHANGES.md)
- [Commits](https://github.com/joyent/node-jsprim/compare/v1.4.1...v1.4.2)

---
updated-dependencies:
- dependency-name: json-schema
  dependency-type: indirect
- dependency-name: jsprim
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-22 14:03:30 +00:00
dependabot[bot]
102bfcebd0 Bump nanoid from 3.1.30 to 3.3.1
Bumps [nanoid](https://github.com/ai/nanoid) from 3.1.30 to 3.3.1.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.1.30...3.3.1)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-22 20:26:50 +00:00
51 changed files with 571 additions and 316 deletions

3
.vscode/launch.json vendored
View File

@@ -9,7 +9,8 @@
"<node_internals>/**" "<node_internals>/**"
], ],
"env": { "env": {
"TRILIUM_ENV": "dev" "TRILIUM_ENV": "dev",
"TRILIUM_DATA_DIR": "./data"
}, },
"outputCapture": "std", "outputCapture": "std",
"program": "${workspaceFolder}/src/www" "program": "${workspaceFolder}/src/www"

View File

@@ -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';

View 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';

View File

@@ -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';

View 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}`);
}
});
};

View 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;

View 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");
}
}
}
});
};

View File

@@ -387,9 +387,12 @@
} }
}, },
"node_modules/minimist": { "node_modules/minimist": {
"version": "1.2.5", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
}, },
"node_modules/mkdirp-classic": { "node_modules/mkdirp-classic": {
"version": "0.5.3", "version": "0.5.3",
@@ -1163,9 +1166,9 @@
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="
}, },
"minimist": { "minimist": {
"version": "1.2.5", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g=="
}, },
"mkdirp-classic": { "mkdirp-classic": {
"version": "0.5.3", "version": "0.5.3",

235
package-lock.json generated
View File

@@ -1,20 +1,21 @@
{ {
"name": "trilium", "name": "trilium",
"version": "0.58.0-beta", "version": "0.58.2-beta",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "trilium", "name": "trilium",
"version": "0.58.0-beta", "version": "0.58.2-beta",
"hasInstallScript": true, "hasInstallScript": true,
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^6.0.2",
"@electron/remote": "2.0.9", "@electron/remote": "2.0.9",
"@excalidraw/excalidraw": "0.13.0", "@excalidraw/excalidraw": "0.13.0",
"archiver": "5.3.1", "archiver": "5.3.1",
"async-mutex": "0.4.0", "async-mutex": "0.4.0",
"axios": "1.2.1", "axios": "1.2.2",
"better-sqlite3": "7.4.5", "better-sqlite3": "7.4.5",
"chokidar": "3.5.3", "chokidar": "3.5.3",
"cls-hooked": "4.2.2", "cls-hooked": "4.2.2",
@@ -115,6 +116,11 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@braintree/sanitize-url": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz",
"integrity": "sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg=="
},
"node_modules/@develar/schema-utils": { "node_modules/@develar/schema-utils": {
"version": "2.6.5", "version": "2.6.5",
"resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz",
@@ -807,6 +813,64 @@
"regenerator-runtime": "^0.13.3" "regenerator-runtime": "^0.13.3"
} }
}, },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
"dev": true,
"dependencies": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
"dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
"dev": true
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.17",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
"integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "3.1.0",
"@jridgewell/sourcemap-codec": "1.4.14"
}
},
"node_modules/@jsdoc/salty": { "node_modules/@jsdoc/salty": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.1.tgz", "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.1.tgz",
@@ -1877,9 +1941,9 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.2.1", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.2.tgz",
"integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==", "integrity": "sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.0", "follow-redirects": "^1.15.0",
"form-data": "^4.0.0", "form-data": "^4.0.0",
@@ -6617,9 +6681,9 @@
"dev": true "dev": true
}, },
"node_modules/json-schema": { "node_modules/json-schema": {
"version": "0.2.3", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
}, },
"node_modules/json-schema-traverse": { "node_modules/json-schema-traverse": {
"version": "0.4.1", "version": "0.4.1",
@@ -6652,17 +6716,17 @@
} }
}, },
"node_modules/jsprim": { "node_modules/jsprim": {
"version": "1.4.1", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
"engines": [
"node >=0.6.0"
],
"dependencies": { "dependencies": {
"assert-plus": "1.0.0", "assert-plus": "1.0.0",
"extsprintf": "1.3.0", "extsprintf": "1.3.0",
"json-schema": "0.2.3", "json-schema": "0.4.0",
"verror": "1.10.0" "verror": "1.10.0"
},
"engines": {
"node": ">=0.6.0"
} }
}, },
"node_modules/junk": { "node_modules/junk": {
@@ -7315,9 +7379,9 @@
} }
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.1.30", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz",
"integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==", "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==",
"bin": { "bin": {
"nanoid": "bin/nanoid.cjs" "nanoid": "bin/nanoid.cjs"
}, },
@@ -8487,9 +8551,9 @@
} }
}, },
"node_modules/request/node_modules/qs": { "node_modules/request/node_modules/qs": {
"version": "6.5.2", "version": "6.5.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==",
"engines": { "engines": {
"node": ">=0.6" "node": ">=0.6"
} }
@@ -9603,13 +9667,14 @@
} }
}, },
"node_modules/terser": { "node_modules/terser": {
"version": "5.10.0", "version": "5.16.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz",
"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", "integrity": "sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@jridgewell/source-map": "^0.3.2",
"acorn": "^8.5.0",
"commander": "^2.20.0", "commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.20" "source-map-support": "~0.5.20"
}, },
"bin": { "bin": {
@@ -9617,14 +9682,6 @@
}, },
"engines": { "engines": {
"node": ">=10" "node": ">=10"
},
"peerDependencies": {
"acorn": "^8.5.0"
},
"peerDependenciesMeta": {
"acorn": {
"optional": true
}
} }
}, },
"node_modules/terser-webpack-plugin": { "node_modules/terser-webpack-plugin": {
@@ -9667,15 +9724,6 @@
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true "dev": true
}, },
"node_modules/terser/node_modules/source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/timm": { "node_modules/timm": {
"version": "1.7.1", "version": "1.7.1",
"resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz",
@@ -10552,6 +10600,11 @@
"regenerator-runtime": "^0.13.4" "regenerator-runtime": "^0.13.4"
} }
}, },
"@braintree/sanitize-url": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz",
"integrity": "sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg=="
},
"@develar/schema-utils": { "@develar/schema-utils": {
"version": "2.6.5", "version": "2.6.5",
"resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz",
@@ -11081,6 +11134,55 @@
"regenerator-runtime": "^0.13.3" "regenerator-runtime": "^0.13.3"
} }
}, },
"@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
"dev": true,
"requires": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
"dev": true
},
"@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"dev": true
},
"@jridgewell/source-map": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
"dev": true,
"requires": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
"dev": true
},
"@jridgewell/trace-mapping": {
"version": "0.3.17",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
"integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
"dev": true,
"requires": {
"@jridgewell/resolve-uri": "3.1.0",
"@jridgewell/sourcemap-codec": "1.4.14"
}
},
"@jsdoc/salty": { "@jsdoc/salty": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.1.tgz", "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.1.tgz",
@@ -11984,9 +12086,9 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
}, },
"axios": { "axios": {
"version": "1.2.1", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.2.tgz",
"integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==", "integrity": "sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==",
"requires": { "requires": {
"follow-redirects": "^1.15.0", "follow-redirects": "^1.15.0",
"form-data": "^4.0.0", "form-data": "^4.0.0",
@@ -15619,9 +15721,9 @@
"dev": true "dev": true
}, },
"json-schema": { "json-schema": {
"version": "0.2.3", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
}, },
"json-schema-traverse": { "json-schema-traverse": {
"version": "0.4.1", "version": "0.4.1",
@@ -15648,13 +15750,13 @@
} }
}, },
"jsprim": { "jsprim": {
"version": "1.4.1", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
"requires": { "requires": {
"assert-plus": "1.0.0", "assert-plus": "1.0.0",
"extsprintf": "1.3.0", "extsprintf": "1.3.0",
"json-schema": "0.2.3", "json-schema": "0.4.0",
"verror": "1.10.0" "verror": "1.10.0"
} }
}, },
@@ -16167,9 +16269,9 @@
} }
}, },
"nanoid": { "nanoid": {
"version": "3.1.30", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz",
"integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==" "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw=="
}, },
"napi-build-utils": { "napi-build-utils": {
"version": "1.0.2", "version": "1.0.2",
@@ -17082,9 +17184,9 @@
}, },
"dependencies": { "dependencies": {
"qs": { "qs": {
"version": "6.5.2", "version": "6.5.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA=="
}, },
"tough-cookie": { "tough-cookie": {
"version": "2.5.0", "version": "2.5.0",
@@ -17946,13 +18048,14 @@
} }
}, },
"terser": { "terser": {
"version": "5.10.0", "version": "5.16.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz",
"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", "integrity": "sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@jridgewell/source-map": "^0.3.2",
"acorn": "^8.5.0",
"commander": "^2.20.0", "commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.20" "source-map-support": "~0.5.20"
}, },
"dependencies": { "dependencies": {
@@ -17961,12 +18064,6 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true "dev": true
},
"source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true
} }
} }
}, },

View File

@@ -2,7 +2,7 @@
"name": "trilium", "name": "trilium",
"productName": "Trilium Notes", "productName": "Trilium Notes",
"description": "Trilium Notes", "description": "Trilium Notes",
"version": "0.58.1-beta", "version": "0.58.3-beta",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"main": "electron.js", "main": "electron.js",
"bin": { "bin": {
@@ -14,11 +14,13 @@
}, },
"scripts": { "scripts": {
"start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev node ./src/www", "start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev node ./src/www",
"start-server-no-dir": "cross-env TRILIUM_ENV=dev node ./src/www",
"start-electron": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev electron --inspect=5858 .", "start-electron": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev electron --inspect=5858 .",
"switch-server": "rm -r ./node_modules/better-sqlite3 && npm install", "start-electron-no-dir": "cross-env TRILIUM_ENV=dev electron --inspect=5858 .",
"switch-electron": "rm -r ./node_modules/better-sqlite3 && npm install && ./node_modules/.bin/electron-rebuild", "switch-server": "rm -rf ./node_modules/better-sqlite3 && npm install",
"build-backend-docs": "rm -r ./docs/backend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/becca/entities/*.js src/services/backend_script_api.js src/services/sql.js", "switch-electron": "rm -rf ./node_modules/better-sqlite3 && npm install && ./node_modules/.bin/electron-rebuild",
"build-frontend-docs": "rm -r ./docs/frontend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/collapsible_widget.js", "build-backend-docs": "rm -rf ./docs/backend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/becca/entities/*.js src/services/backend_script_api.js src/services/sql.js",
"build-frontend-docs": "rm -rf ./docs/frontend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/collapsible_widget.js",
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs", "build-docs": "npm run build-backend-docs && npm run build-frontend-docs",
"webpack": "npx webpack -c webpack-desktop.config.js && npx webpack -c webpack-mobile.config.js && npx webpack -c webpack-setup.config.js", "webpack": "npx webpack -c webpack-desktop.config.js && npx webpack -c webpack-mobile.config.js && npx webpack -c webpack-setup.config.js",
"test-jasmine": "jasmine", "test-jasmine": "jasmine",
@@ -27,11 +29,12 @@
"postinstall": "rimraf ./node_modules/canvas" "postinstall": "rimraf ./node_modules/canvas"
}, },
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^6.0.2",
"@electron/remote": "2.0.9", "@electron/remote": "2.0.9",
"@excalidraw/excalidraw": "0.13.0", "@excalidraw/excalidraw": "0.13.0",
"archiver": "5.3.1", "archiver": "5.3.1",
"async-mutex": "0.4.0", "async-mutex": "0.4.0",
"axios": "1.2.1", "axios": "1.2.2",
"better-sqlite3": "7.4.5", "better-sqlite3": "7.4.5",
"chokidar": "3.5.3", "chokidar": "3.5.3",
"cls-hooked": "4.2.2", "cls-hooked": "4.2.2",

View File

@@ -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", () => {

View File

@@ -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];

View File

@@ -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;

View File

@@ -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
});
}
} }
} }

View File

@@ -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) {

View File

@@ -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);
} }
}); });

View File

@@ -85,7 +85,7 @@ function isValidEntityId(obj) {
return; return;
} }
if (typeof obj !== 'string' || !/^[A-Za-z0-9]{4,32}$/.test(obj)) { if (typeof obj !== 'string' || !/^[A-Za-z0-9_]{4,128}$/.test(obj)) {
return `'${obj}' is not a valid entityId. Only alphanumeric characters are allowed of length 4 to 32.`; return `'${obj}' is not a valid entityId. Only alphanumeric characters are allowed of length 4 to 32.`;
} }
} }

View File

@@ -32,7 +32,7 @@ export default class TreeContextMenu {
const isHoisted = note.noteId === appContext.tabManager.getActiveContext().hoistedNoteId; const isHoisted = note.noteId === appContext.tabManager.getActiveContext().hoistedNoteId;
const parentNote = isNotRoot ? await froca.getNote(branch.parentNoteId) : null; const parentNote = isNotRoot ? await froca.getNote(branch.parentNoteId) : null;
// some actions don't support multi-note so they are disabled when notes are selected // some actions don't support multi-note, so they are disabled when notes are selected
// the only exception is when the only selected note is the one that was right-clicked, then // the only exception is when the only selected note is the one that was right-clicked, then
// it's clear what the user meant to do. // it's clear what the user meant to do.
const selNodes = this.treeWidget.getSelectedNodes(); const selNodes = this.treeWidget.getSelectedNodes();

View File

@@ -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;
} }

View File

@@ -216,7 +216,7 @@ class Froca {
getNotesFromCache(noteIds, silentNotFoundError = false) { getNotesFromCache(noteIds, silentNotFoundError = false) {
return noteIds.map(noteId => { return noteIds.map(noteId => {
if (!this.notes[noteId] && !silentNotFoundError) { if (!this.notes[noteId] && !silentNotFoundError) {
console.trace(`Can't find note "${noteId}"`); console.trace(`Can't find note '${noteId}'`);
return null; return null;
} }
@@ -235,7 +235,7 @@ class Froca {
return noteIds.map(noteId => { return noteIds.map(noteId => {
if (!this.notes[noteId] && !silentNotFoundError) { if (!this.notes[noteId] && !silentNotFoundError) {
console.trace(`Can't find note "${noteId}"`); console.trace(`Can't find note '${noteId}'`);
return null; return null;
} else { } else {
@@ -285,7 +285,7 @@ class Froca {
getBranch(branchId, silentNotFoundError = false) { getBranch(branchId, silentNotFoundError = false) {
if (!(branchId in this.branches)) { if (!(branchId in this.branches)) {
if (!silentNotFoundError) { if (!silentNotFoundError) {
logError(`Not existing branch ${branchId}`); logError(`Not existing branch '${branchId}'`);
} }
} }
else { else {
@@ -295,13 +295,13 @@ class Froca {
async getBranchId(parentNoteId, childNoteId) { async getBranchId(parentNoteId, childNoteId) {
if (childNoteId === 'root') { if (childNoteId === 'root') {
return 'root'; return 'none_root';
} }
const child = await this.getNote(childNoteId); const child = await this.getNote(childNoteId);
if (!child) { if (!child) {
logError(`Could not find branchId for parent=${parentNoteId}, child=${childNoteId} since child does not exist`); logError(`Could not find branchId for parent '${parentNoteId}', child '${childNoteId}' since child does not exist`);
return null; return null;
} }
@@ -318,9 +318,9 @@ class Froca {
.then(row => new NoteComplement(row)) .then(row => new NoteComplement(row))
.catch(e => console.error(`Cannot get note complement for note '${noteId}'`)); .catch(e => console.error(`Cannot get note complement for note '${noteId}'`));
// we don't want to keep large payloads forever in memory so we clean that up quite quickly // we don't want to keep large payloads forever in memory, so we clean that up quite quickly
// this cache is more meant to share the data between different components within one business transaction (e.g. loading of the note into the tab context and all the components) // this cache is more meant to share the data between different components within one business transaction (e.g. loading of the note into the tab context and all the components)
// this is also a work around for missing invalidation after change // this is also a workaround for missing invalidation after change
this.noteComplementPromises[noteId].then( this.noteComplementPromises[noteId].then(
() => setTimeout(() => this.noteComplementPromises[noteId] = null, 1000) () => setTimeout(() => this.noteComplementPromises[noteId] = null, 1000)
); );

View File

@@ -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;

View File

@@ -1,8 +1,8 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js"; import NoteContextAwareWidget from "../note_context_aware_widget.js";
const TPL = `<button class="button-widget no-print" data-toggle="tooltip"> const TPL = `<button class="button-widget bx"
<span class="bx"></span> data-toggle="tooltip"
</button>`; title=""></button>`;
export default class AbstractButtonWidget extends NoteContextAwareWidget { export default class AbstractButtonWidget extends NoteContextAwareWidget {
isEnabled() { isEnabled() {
@@ -22,7 +22,6 @@ export default class AbstractButtonWidget extends NoteContextAwareWidget {
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.$iconSpan = this.$widget.find("span");
if (this.settings.onContextMenu) { if (this.settings.onContextMenu) {
this.$widget.on("contextmenu", e => { this.$widget.on("contextmenu", e => {
@@ -52,9 +51,9 @@ export default class AbstractButtonWidget extends NoteContextAwareWidget {
} }
refreshIcon() { refreshIcon() {
for (const className of this.$iconSpan[0].classList) { for (const className of this.$widget[0].classList) {
if (className.startsWith("bx-")) { if (className.startsWith("bx-")) {
this.$iconSpan.removeClass(className); this.$widget.removeClass(className);
} }
} }
@@ -62,7 +61,7 @@ export default class AbstractButtonWidget extends NoteContextAwareWidget {
? this.settings.icon() ? this.settings.icon()
: this.settings.icon; : this.settings.icon;
this.$iconSpan.addClass(icon); this.$widget.addClass(icon);
} }
initialRenderCompleteEvent() { initialRenderCompleteEvent() {

View File

@@ -39,12 +39,10 @@ export default class EditButton extends OnClickButtonWidget {
// make the edit button stand out on the first display, otherwise // make the edit button stand out on the first display, otherwise
// it's difficult to notice that the note is readonly // it's difficult to notice that the note is readonly
if (this.isVisible() && !wasVisible) { if (this.isVisible() && !wasVisible) {
this.$iconSpan.addClass("bx-tada"); this.$widget.addClass("bx-tada bx-lg");
this.$widget.css("transform", "scale(2)");
setTimeout(() => { setTimeout(() => {
this.$iconSpan.removeClass("bx-tada bx-lg"); this.$widget.removeClass("bx-tada bx-lg");
this.$widget.css("transform", "scale(1)");
}, 1700); }, 1700);
} }
} }

View File

@@ -21,12 +21,12 @@ const TPL = `
background-position: 50% 45%; background-position: 50% 45%;
width: 100%; width: 100%;
height: 100%; height: 100%;
position: relative; position: relative;
} }
.global-menu-button:hover { .global-menu-button:hover {
background-image: url("${window.glob.assetPath}/images/icon-color.png"); background-image: url("${window.glob.assetPath}/images/icon-color.png");
border: 0;
} }
.global-menu-button-update-available { .global-menu-button-update-available {

View File

@@ -320,8 +320,6 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
&& attributeService.isAffecting(attr, this.note)); && attributeService.isAffecting(attr, this.note));
if (label || relation) { if (label || relation) {
console.log("OOOO");
// probably incorrect event // probably incorrect event
// calling this.refresh() is not enough since the event needs to be propagated to children as well // calling this.refresh() is not enough since the event needs to be propagated to children as well
this.triggerEvent('noteTypeMimeChanged', {noteId: this.noteId}); this.triggerEvent('noteTypeMimeChanged', {noteId: this.noteId});

View File

@@ -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'));
} }
/** /**

View File

@@ -68,8 +68,6 @@ function updateNoteAttribute(req) {
attribute.markAsDeleted(); attribute.markAsDeleted();
} }
console.log(attribute);
attribute.save(); attribute.save();
return { return {
@@ -172,7 +170,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);

View File

@@ -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);

View File

@@ -14,8 +14,8 @@ const Attribute = require('../../becca/entities/attribute');
const htmlSanitizer = require('../../services/html_sanitizer'); const htmlSanitizer = require('../../services/html_sanitizer');
const {formatAttrForSearch} = require("../../services/attribute_formatter"); const {formatAttrForSearch} = require("../../services/attribute_formatter");
function findClippingNote(todayNote, pageUrl) { function findClippingNote(clipperInboxNote, pageUrl) {
const notes = todayNote.searchNotesInSubtree( const notes = clipperInboxNote.searchNotesInSubtree(
formatAttrForSearch({ formatAttrForSearch({
type: 'label', type: 'label',
name: "pageUrl", name: "pageUrl",
@@ -47,6 +47,7 @@ function addClipping(req) {
const clipperInbox = getClipperInboxNote(); const clipperInbox = getClipperInboxNote();
pageUrl = htmlSanitizer.sanitizeUrl(pageUrl);
let clippingNote = findClippingNote(clipperInbox, pageUrl); let clippingNote = findClippingNote(clipperInbox, pageUrl);
if (!clippingNote) { if (!clippingNote) {
@@ -57,8 +58,6 @@ function addClipping(req) {
type: 'text' type: 'text'
}).note; }).note;
pageUrl = htmlSanitizer.sanitize(pageUrl);
clippingNote.setLabel('clipType', 'clippings'); clippingNote.setLabel('clipType', 'clippings');
clippingNote.setLabel('pageUrl', pageUrl); clippingNote.setLabel('pageUrl', pageUrl);
clippingNote.setLabel('iconClass', 'bx bx-globe'); clippingNote.setLabel('iconClass', 'bx bx-globe');
@@ -96,7 +95,7 @@ function createNote(req) {
note.setLabel('clipType', clipType); note.setLabel('clipType', clipType);
if (pageUrl) { if (pageUrl) {
pageUrl = htmlSanitizer.sanitize(pageUrl); pageUrl = htmlSanitizer.sanitizeUrl(pageUrl);
note.setLabel('pageUrl', pageUrl); note.setLabel('pageUrl', pageUrl);
note.setLabel('iconClass', 'bx bx-globe'); note.setLabel('iconClass', 'bx bx-globe');

View File

@@ -13,7 +13,7 @@ function exportBranch(req, res) {
const branch = becca.getBranch(branchId); const branch = becca.getBranch(branchId);
if (!branch) { if (!branch) {
const message = `Cannot export branch ${branchId} since it does not exist.`; const message = `Cannot export branch '${branchId}' since it does not exist.`;
log.error(message); log.error(message);
res.setHeader("Content-Type", "text/plain") res.setHeader("Content-Type", "text/plain")

View File

@@ -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,

View File

@@ -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 = {

View File

@@ -1 +1 @@
module.exports = { buildDate:"2022-12-26T23:57:31+01:00", buildRevision: "7a8bbdced83c3bf906feec87b97aba4959ffa7bc" }; module.exports = { buildDate:"2023-01-04T22:36:31+01:00", buildRevision: "3a5fa2954dea0ff2ecdce9a28b3bb01f039d314d" };

View File

@@ -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 };
} }

View File

@@ -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
}; };

View File

@@ -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

View File

@@ -214,13 +214,13 @@ function exportToZip(taskContext, branch, format, res) {
} }
function findLinks(content, noteMeta) { function findLinks(content, noteMeta) {
content = content.replace(/src="[^"]*api\/images\/([a-zA-Z0-9]+)\/[^"]*"/g, (match, targetNoteId) => { content = content.replace(/src="[^"]*api\/images\/([a-zA-Z0-9_]+)\/[^"]*"/g, (match, targetNoteId) => {
const url = getTargetUrl(targetNoteId, noteMeta); const url = getTargetUrl(targetNoteId, noteMeta);
return url ? `src="${url}"` : match; return url ? `src="${url}"` : match;
}); });
content = content.replace(/href="[^"]*#root[a-zA-Z0-9\/]*\/([a-zA-Z0-9]+)\/?"/g, (match, targetNoteId) => { content = content.replace(/href="[^"]*#root[a-zA-Z0-9_\/]*\/([a-zA-Z0-9_]+)\/?"/g, (match, targetNoteId) => {
const url = getTargetUrl(targetNoteId, noteMeta); const url = getTargetUrl(targetNoteId, noteMeta);
return url ? `href="${url}"` : match; return url ? `href="${url}"` : match;

View File

@@ -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',
@@ -237,13 +244,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({
@@ -254,27 +255,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)}`);
} }
} }
@@ -299,8 +308,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();
} }
} }

View File

@@ -1,4 +1,5 @@
const sanitizeHtml = require('sanitize-html'); const sanitizeHtml = require('sanitize-html');
const sanitizeUrl = require('@braintree/sanitize-url').sanitizeUrl;
// intended mainly as protection against XSS via import // intended mainly as protection against XSS via import
// secondarily it (partly) protects against "CSS takeover" // secondarily it (partly) protects against "CSS takeover"
@@ -28,7 +29,8 @@ function sanitize(dirtyHtml) {
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
'li', 'b', 'i', 'strong', 'em', 'strike', 's', 'del', 'abbr', 'code', 'hr', 'br', 'div', 'li', 'b', 'i', 'strong', 'em', 'strike', 's', 'del', 'abbr', 'code', 'hr', 'br', 'div',
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'section', 'img', 'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'section', 'img',
'figure', 'figcaption', 'span', 'label', 'input' 'figure', 'figcaption', 'span', 'label', 'input',
'en-media' // for ENEX import
], ],
allowedAttributes: { allowedAttributes: {
'a': [ 'href', 'class', 'data-note-path' ], 'a': [ 'href', 'class', 'data-note-path' ],
@@ -41,6 +43,7 @@ function sanitize(dirtyHtml) {
'code': [ 'class' ], 'code': [ 'class' ],
'ul': [ 'class' ], 'ul': [ 'class' ],
'table': [ 'class' ], 'table': [ 'class' ],
'en-media': [ 'hash' ]
}, },
allowedSchemes: ['http', 'https', 'ftp', 'mailto', 'data', 'evernote'], allowedSchemes: ['http', 'https', 'ftp', 'mailto', 'data', 'evernote'],
transformTags, transformTags,
@@ -48,5 +51,6 @@ function sanitize(dirtyHtml) {
} }
module.exports = { module.exports = {
sanitize sanitize,
sanitizeUrl
}; };

View File

@@ -115,7 +115,7 @@ function importEnex(taskContext, file, parentNote) {
let labelName = currentTag; let labelName = currentTag;
if (labelName === 'source-url') { if (labelName === 'source-url') {
labelName = 'sourceUrl'; labelName = 'pageUrl';
} }
labelName = sanitizeAttributeName(labelName); labelName = sanitizeAttributeName(labelName);
@@ -139,7 +139,7 @@ function importEnex(taskContext, file, parentNote) {
else if (currentTag === 'source-url') { else if (currentTag === 'source-url') {
resource.attributes.push({ resource.attributes.push({
type: 'label', type: 'label',
name: 'sourceUrl', name: 'pageUrl',
value: text value: text
}); });
} }

View File

@@ -231,6 +231,13 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
absUrl += `${absUrl.length > 0 ? '/' : ''}${url}`; absUrl += `${absUrl.length > 0 ? '/' : ''}${url}`;
const {noteMeta} = getMeta(absUrl); const {noteMeta} = getMeta(absUrl);
if (!noteMeta) {
log.info(`Could not find note meta for URL '${absUrl}'.`);
return null;
}
const targetNoteId = getNoteId(noteMeta, absUrl); const targetNoteId = getNoteId(noteMeta, absUrl);
return targetNoteId; return targetNoteId;
} }
@@ -312,6 +319,10 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
const targetNoteId = getNoteIdFromRelativeUrl(url, filePath); const targetNoteId = getNoteIdFromRelativeUrl(url, filePath);
if (!targetNoteId) {
return match;
}
return `src="api/images/${targetNoteId}/${path.basename(url)}"`; return `src="api/images/${targetNoteId}/${path.basename(url)}"`;
}); });
@@ -329,6 +340,10 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
const targetNoteId = getNoteIdFromRelativeUrl(url, filePath); const targetNoteId = getNoteIdFromRelativeUrl(url, filePath);
if (!targetNoteId) {
return match;
}
return `href="#root/${targetNoteId}"`; return `href="#root/${targetNoteId}"`;
}); });

View File

@@ -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
} }
} }
}); });

View File

@@ -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,

View File

@@ -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);

View File

@@ -24,6 +24,7 @@ class SearchContext {
this.fuzzyAttributeSearch = !!params.fuzzyAttributeSearch; this.fuzzyAttributeSearch = !!params.fuzzyAttributeSearch;
this.highlightedTokens = []; this.highlightedTokens = [];
this.originalQuery = ""; this.originalQuery = "";
this.fulltextQuery = ""; // complete fulltext part
// if true, becca does not have (up-to-date) information needed to process the query // if true, becca does not have (up-to-date) information needed to process the query
// and some extra data needs to be loaded before executing // and some extra data needs to be loaded before executing
this.dbLoadNeeded = false; this.dbLoadNeeded = false;

View File

@@ -17,18 +17,22 @@ class SearchResult {
return this.notePathArray[this.notePathArray.length - 1]; return this.notePathArray[this.notePathArray.length - 1];
} }
computeScore(tokens) { computeScore(fulltextQuery, tokens) {
this.score = 0; this.score = 0;
const note = becca.notes[this.noteId];
if (note.title.toLowerCase() === fulltextQuery) {
this.score += 100; // high reward for exact match #3470
}
// notes with matches on its own note title as opposed to ancestors or descendants
this.addScoreForStrings(tokens, note.title, 1.5);
// matches in attributes don't get extra points and thus are implicitly valued less than note path matches // matches in attributes don't get extra points and thus are implicitly valued less than note path matches
this.addScoreForStrings(tokens, this.notePathTitle, 1); this.addScoreForStrings(tokens, this.notePathTitle, 1);
// add one more time for note title alone (already contained in the notePathTitle),
// thus preferring notes with matches on its own note title as opposed to ancestors or descendants
const note = becca.notes[this.noteId];
this.addScoreForStrings(tokens, note.title, 1.5);
if (note.isInHiddenSubtree()) { if (note.isInHiddenSubtree()) {
this.score = this.score / 2; this.score = this.score / 2;
} }

View File

@@ -1,6 +1,7 @@
function lex(str) { function lex(str) {
str = str.toLowerCase(); str = str.toLowerCase();
let fulltextQuery = "";
const fulltextTokens = []; const fulltextTokens = [];
const expressionTokens = []; const expressionTokens = [];
@@ -37,6 +38,8 @@ function lex(str) {
expressionTokens.push(rec); expressionTokens.push(rec);
} else { } else {
fulltextTokens.push(rec); fulltextTokens.push(rec);
fulltextQuery = str.substr(0, endIndex + 1);
} }
currentWord = ''; currentWord = '';
@@ -129,7 +132,10 @@ function lex(str) {
finishWord(str.length - 1); finishWord(str.length - 1);
fulltextQuery = fulltextQuery.trim();
return { return {
fulltextQuery,
fulltextTokens, fulltextTokens,
expressionTokens expressionTokens
} }

View File

@@ -170,7 +170,7 @@ function findResultsWithExpression(expression, searchContext) {
.filter(note => !!note); .filter(note => !!note);
for (const res of searchResults) { for (const res of searchResults) {
res.computeScore(searchContext.highlightedTokens); res.computeScore(searchContext.fulltextQuery, searchContext.highlightedTokens);
} }
if (!noteSet.sorted) { if (!noteSet.sorted) {
@@ -195,7 +195,9 @@ function findResultsWithExpression(expression, searchContext) {
} }
function parseQueryToExpression(query, searchContext) { function parseQueryToExpression(query, searchContext) {
const {fulltextTokens, expressionTokens} = lex(query); const {fulltextQuery, fulltextTokens, expressionTokens} = lex(query);
searchContext.fulltextQuery = fulltextQuery;
let structuredExpressionTokens; let structuredExpressionTokens;
try { try {

View File

@@ -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,

View File

@@ -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 = {

View File

@@ -291,6 +291,10 @@ function deferred() {
} }
function removeDiacritic(str) { function removeDiacritic(str) {
if (!str) {
return "";
}
return str.normalize("NFD").replace(/\p{Diacritic}/gu, ""); return str.normalize("NFD").replace(/\p{Diacritic}/gu, "");
} }