Compare commits

...

15 Commits

Author SHA1 Message Date
zadam
4d22959e28 release 0.42.4 2020-05-31 10:33:12 +02:00
zadam
50a28d8c51 the node you start dragging should be included even if not selected 2020-05-31 10:32:35 +02:00
zadam
e25b633ec4 better error logging in backup 2020-05-31 10:24:59 +02:00
zadam
75bd395669 fix extra refresh because of duplicated sync event, closes #1063 2020-05-30 22:35:18 +02:00
zadam
5e353a5612 improved drag & drop 2020-05-30 10:30:21 +02:00
zadam
6b359b7796 return 401 when auth request is out of sync, closes #1056 2020-05-29 22:06:36 +02:00
zadam
13f9d037dc safer backup to file using VACUUM INTO + possibility to explicitly ask for backup now 2020-05-29 21:55:08 +02:00
zadam
1911d64c1c fix long filename overflowing, closes #1052 2020-05-29 20:36:48 +02:00
zadam
d4c3f1b3f2 upgrade to ckeditor 19.1.0 2020-05-27 22:08:06 +02:00
zadam
3db84daf94 fix hiding autocompletes after closing tab, closes #1034 2020-05-22 19:30:21 +02:00
zadam
2526715aa4 release 0.42.3 2020-05-20 08:54:55 +02:00
zadam
04c573e212 set default executors 2020-05-20 08:54:37 +02:00
zadam
58f4f5d1e6 fix deadlock after "cut to note", closes #1030 2020-05-19 22:58:08 +02:00
zadam
fa5d982a55 fix exporting root note, closes #1024 2020-05-17 21:07:54 +02:00
zadam
108afe8896 fix incorrect processing of sync rows, closes #1019 2020-05-14 13:08:06 +02:00
26 changed files with 280 additions and 153 deletions

4
.idea/dataSources.xml generated
View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="SQLite - document.db" uuid="d0fd879f-1e1d-4d5c-9c21-0e5cf9ab2976">
<data-source source="LOCAL" name="document.db" uuid="b0b03187-36c8-4ec1-bdab-fd4273cd692e">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/../trilium-data/document.db</jdbc-url>
<jdbc-url>jdbc:sqlite:$USER_HOME$/trilium-data/document.db</jdbc-url>
</data-source>
</component>
</project>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

88
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "trilium",
"version": "0.42.1",
"version": "0.42.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -1263,7 +1263,7 @@
"dependencies": {
"file-type": {
"version": "3.9.0",
"resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
"integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek="
}
}
@@ -1539,7 +1539,7 @@
},
"uuid": {
"version": "2.0.3",
"resolved": "http://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
"integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho="
}
}
@@ -1573,7 +1573,7 @@
"dependencies": {
"semver": {
"version": "4.3.6",
"resolved": "http://registry.npmjs.org/semver/-/semver-4.3.6.tgz",
"resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz",
"integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto="
}
}
@@ -1593,7 +1593,7 @@
},
"bl": {
"version": "1.2.2",
"resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
"requires": {
"readable-stream": "^2.3.5",
@@ -1853,12 +1853,12 @@
"dependencies": {
"file-type": {
"version": "3.9.0",
"resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
"integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek="
},
"uuid": {
"version": "2.0.3",
"resolved": "http://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
"integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho="
}
}
@@ -1973,7 +1973,7 @@
},
"readable-stream": {
"version": "1.1.14",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": {
"core-util-is": "~1.0.0",
@@ -2148,7 +2148,7 @@
},
"chalk": {
"version": "1.1.3",
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"requires": {
"ansi-styles": "^2.2.1",
@@ -2465,7 +2465,7 @@
},
"commander": {
"version": "2.8.1",
"resolved": "http://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
"integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=",
"requires": {
"graceful-readlink": ">= 1.0.0"
@@ -3128,7 +3128,7 @@
},
"readable-stream": {
"version": "1.1.14",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": {
"core-util-is": "~1.0.0",
@@ -3345,9 +3345,9 @@
}
},
"electron": {
"version": "9.0.0-beta.24",
"resolved": "https://registry.npmjs.org/electron/-/electron-9.0.0-beta.24.tgz",
"integrity": "sha512-25L3XMqm/1CCaV5CgU5ZkhKXw9830WeipJrTW0+VC5XTKp/3xHwhxyQ5G1kQnOTJd7IGwOamvw237D6e1YKnng==",
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-9.0.0.tgz",
"integrity": "sha512-JsaSQNPh+XDYkLj8APtVKTtvpb86KIG57W5OOss4TNrn8L3isC9LsCITwfnVmGIXHhvX6oY/weCtN5hAAytjVg==",
"dev": true,
"requires": {
"@electron/get": "^1.0.1",
@@ -4957,7 +4957,7 @@
},
"get-stream": {
"version": "3.0.0",
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
},
"getpass": {
@@ -5221,7 +5221,7 @@
},
"got": {
"version": "5.7.1",
"resolved": "http://registry.npmjs.org/got/-/got-5.7.1.tgz",
"resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz",
"integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=",
"requires": {
"create-error-class": "^3.0.1",
@@ -5869,7 +5869,7 @@
},
"into-stream": {
"version": "3.1.0",
"resolved": "http://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz",
"resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz",
"integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=",
"requires": {
"from2": "^2.1.1",
@@ -6021,7 +6021,7 @@
},
"is-obj": {
"version": "1.0.1",
"resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
"integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8="
},
"is-object": {
@@ -6621,7 +6621,7 @@
},
"load-json-file": {
"version": "1.1.0",
"resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"requires": {
"graceful-fs": "^4.1.2",
@@ -7130,7 +7130,7 @@
},
"minimist": {
"version": "1.2.0",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
},
"minipass": {
@@ -7230,7 +7230,7 @@
},
"mkdirp": {
"version": "0.5.1",
"resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"requires": {
"minimist": "0.0.8"
@@ -7238,7 +7238,7 @@
"dependencies": {
"minimist": {
"version": "0.0.8",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
}
}
@@ -7432,7 +7432,7 @@
},
"get-stream": {
"version": "3.0.0",
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
},
"got": {
@@ -7468,7 +7468,7 @@
},
"p-cancelable": {
"version": "0.4.1",
"resolved": "http://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz",
"integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ=="
},
"p-event": {
@@ -7592,7 +7592,7 @@
"dependencies": {
"file-type": {
"version": "3.9.0",
"resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
"integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek="
}
}
@@ -7617,7 +7617,7 @@
"dependencies": {
"get-stream": {
"version": "3.0.0",
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
},
"pify": {
@@ -7674,7 +7674,7 @@
},
"get-stream": {
"version": "2.3.1",
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
"integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=",
"requires": {
"object-assign": "^4.0.1",
@@ -7704,7 +7704,7 @@
"dependencies": {
"get-stream": {
"version": "3.0.0",
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
}
}
@@ -7744,7 +7744,7 @@
},
"pify": {
"version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
},
"prepend-http": {
@@ -7849,7 +7849,7 @@
},
"readable-stream": {
"version": "1.1.14",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": {
"core-util-is": "~1.0.0",
@@ -8227,7 +8227,7 @@
},
"onetime": {
"version": "1.1.0",
"resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k="
},
"open": {
@@ -8379,7 +8379,7 @@
},
"p-is-promise": {
"version": "1.1.0",
"resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz",
"resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz",
"integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4="
},
"p-limit": {
@@ -8860,7 +8860,7 @@
},
"get-stream": {
"version": "3.0.0",
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
}
}
@@ -9144,7 +9144,7 @@
"dependencies": {
"file-type": {
"version": "3.9.0",
"resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
"integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek="
}
}
@@ -9169,7 +9169,7 @@
"dependencies": {
"get-stream": {
"version": "3.0.0",
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
},
"pify": {
@@ -9207,7 +9207,7 @@
},
"get-stream": {
"version": "3.0.0",
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
}
}
@@ -9259,7 +9259,7 @@
},
"get-stream": {
"version": "2.3.1",
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
"integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=",
"requires": {
"object-assign": "^4.0.1",
@@ -9289,7 +9289,7 @@
"dependencies": {
"get-stream": {
"version": "3.0.0",
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
}
}
@@ -9477,7 +9477,7 @@
},
"query-string": {
"version": "5.1.1",
"resolved": "http://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
"integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==",
"requires": {
"decode-uri-component": "^0.2.0",
@@ -9616,7 +9616,7 @@
},
"readable-stream": {
"version": "2.3.6",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
@@ -10484,7 +10484,7 @@
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"requires": {
"ansi-regex": "^2.0.0"
@@ -10509,7 +10509,7 @@
},
"strip-dirs": {
"version": "1.1.1",
"resolved": "http://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
"resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
"integrity": "sha1-lgu9EoeETzl1pFWKoQOoJV4kVqA=",
"requires": {
"chalk": "^1.0.0",
@@ -10767,7 +10767,7 @@
},
"through": {
"version": "2.3.8",
"resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
},
"through2": {
@@ -10786,7 +10786,7 @@
},
"readable-stream": {
"version": "1.0.34",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
"requires": {
"core-util-is": "~1.0.0",

View File

@@ -2,7 +2,7 @@
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.42.2",
"version": "0.42.4",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -78,7 +78,7 @@
"yazl": "^2.5.1"
},
"devDependencies": {
"electron": "9.0.0-beta.24",
"electron": "9.0.0",
"electron-builder": "22.6.0",
"electron-packager": "14.2.1",
"electron-rebuild": "1.10.1",

View File

@@ -24,6 +24,13 @@ const TPL = `
<p>This action will create a new copy of the database and anonymise it (remove all note content and leave only structure and metadata)
for sharing online for debugging purposes without fear of leaking your personal data.</p>
<h4>Backup database</h4>
<button id="backup-database-button" class="btn">Backup database</button>
<br/>
<br/>
<h4>Vacuum database</h4>
<p>This will rebuild database which will typically result in smaller database file. No data will be actually changed.</p>
@@ -37,6 +44,7 @@ export default class AdvancedOptions {
this.$forceFullSyncButton = $("#force-full-sync-button");
this.$fillSyncRowsButton = $("#fill-sync-rows-button");
this.$anonymizeButton = $("#anonymize-button");
this.$backupDatabaseButton = $("#backup-database-button");
this.$vacuumDatabaseButton = $("#vacuum-database-button");
this.$findAndFixConsistencyIssuesButton = $("#find-and-fix-consistency-issues-button");
@@ -58,16 +66,22 @@ export default class AdvancedOptions {
toastService.showMessage("Created anonymized database");
});
this.$backupDatabaseButton.on('click', async () => {
const {backupFile} = await server.post('database/backup-database');
toastService.showMessage("Database has been backed up to " + backupFile, 10000);
});
this.$vacuumDatabaseButton.on('click', async () => {
await server.post('cleanup/vacuum-database');
await server.post('database/vacuum-database');
toastService.showMessage("Database has been vacuumed");
});
this.$findAndFixConsistencyIssuesButton.on('click', async () => {
await server.post('cleanup/find-and-fix-consistency-issues');
await server.post('database/find-and-fix-consistency-issues');
toastService.showMessage("Consistency issues should be fixed.");
});
}
}
}

View File

@@ -17,6 +17,7 @@ class AppContext extends Component {
super();
this.isMainWindow = isMainWindow;
this.executors = [];
}
setLayout(layout) {

View File

@@ -45,7 +45,7 @@ async function moveAfterBranch(branchIdsToMove, afterBranchId) {
}
}
async function moveToParentNote(branchIdsToMove, newParentNoteId) {
async function moveToParentNote(branchIdsToMove, newParentBranchId) {
branchIdsToMove = filterRootNote(branchIdsToMove);
for (const branchIdToMove of branchIdsToMove) {
@@ -56,7 +56,7 @@ async function moveToParentNote(branchIdsToMove, newParentNoteId) {
continue;
}
const resp = await server.put(`branches/${branchIdToMove}/move-to/${newParentNoteId}`);
const resp = await server.put(`branches/${branchIdToMove}/move-to/${newParentBranchId}`);
if (!resp.success) {
alert(resp.message);
@@ -225,4 +225,4 @@ export default {
moveNodeUpInHierarchy,
cloneNoteAfter,
cloneNoteTo
};
};

View File

@@ -4,7 +4,7 @@ export default class LoadResults {
this.noteIdToSourceId = {};
this.sourceIdToNoteIds = {};
this.branches = [];
this.attributes = [];
@@ -103,10 +103,10 @@ export default class LoadResults {
/**
* @return {boolean} true if there are changes which could affect the attributes (including inherited ones)
* notably changes in note itself should not have any effect on attributes
*/
hasAttributeRelatedChanges() {
return Object.keys(this.noteIdToSourceId).length === 0
&& this.branches.length === 0
return this.branches.length === 0
&& this.attributes.length === 0;
}
@@ -119,4 +119,4 @@ export default class LoadResults {
&& this.contentNoteIdToSourceId.length === 0
&& this.options.length === 0;
}
}
}

View File

@@ -238,7 +238,7 @@ export default class TabManager extends Component {
}
this.tabsUpdate.scheduleUpdate();
this.setCurrentNotePathToHash();
}
@@ -249,6 +249,9 @@ export default class TabManager extends Component {
return;
}
// close dangling autocompletes after closing the tab
$(".aa-input").autocomplete("close");
await this.triggerEvent('beforeTabRemove', {tabId});
if (this.tabContexts.length <= 1) {
@@ -267,9 +270,6 @@ export default class TabManager extends Component {
this.children = this.children.filter(tc => tc.tabId !== tabId);
// remove dangling autocompletes after closing the tab
$(".algolia-autocomplete").remove();
this.triggerEvent('tabRemoved', {tabId});
this.tabsUpdate.scheduleUpdate();
@@ -346,4 +346,4 @@ export default class TabManager extends Component {
}
}
}
}
}

View File

@@ -162,6 +162,13 @@ function getNoteIdFromNotePath(notePath) {
}
function getNoteIdAndParentIdFromNotePath(notePath) {
if (notePath === 'root') {
return {
noteId: 'root',
parentNoteId: 'none'
};
}
let parentNoteId = 'root';
let noteId = '';
@@ -286,4 +293,4 @@ export default {
getNotePathTitle,
getHashValueFromAddress,
parseNotePath
};
};

View File

@@ -39,7 +39,18 @@ let consumeQueuePromise = null;
// most sync events are sent twice - once immediatelly after finishing the transaction and once during the scheduled ping
// but we want to process only once
const receivedSyncIds = new Set();
const processedSyncIds = new Set();
function logRows(syncRows) {
const filteredRows = syncRows.filter(row =>
!processedSyncIds.has(row.id)
&& row.entityName !== 'recent_notes'
&& (row.entityName !== 'options' || row.entityId !== 'openTabs'));
if (filteredRows.length > 0) {
console.debug(utils.now(), "Sync data: ", filteredRows);
}
}
async function handleMessage(event) {
const message = JSON.parse(event.data);
@@ -55,20 +66,9 @@ async function handleMessage(event) {
$outstandingSyncsCount.html(message.outstandingSyncs);
if (syncRows.length > 0) {
const filteredRows = syncRows.filter(row =>
!receivedSyncIds.has(row.id)
&& row.entityName !== 'recent_notes'
&& (row.entityName !== 'options' || row.entityId !== 'openTabs'));
logRows(syncRows);
if (filteredRows.length > 0) {
console.debug(utils.now(), "Sync data: ", filteredRows);
}
for (const row of filteredRows) {
receivedSyncIds.add(row.id);
}
syncDataQueue.push(...filteredRows);
syncDataQueue.push(...syncRows);
// we set lastAcceptedSyncId even before sync processing and send ping so that backend can start sending more updates
lastAcceptedSyncId = Math.max(lastAcceptedSyncId, syncRows[syncRows.length - 1].id);
@@ -142,13 +142,21 @@ async function runSafely(syncHandler, syncData) {
}
}
/**
* TODO: we should rethink the fact that each sync row is sent twice (once at the end of transaction, once periodically)
* and we keep both lastProcessedSyncId and processedSyncIds
* it even seems incorrect that when transaction sync rows are received, we incorrectly increase lastProcessedSyncId
* and then some syncs might lost (or are *all* sync rows sent from transactions?)
*/
async function consumeSyncData() {
if (syncDataQueue.length > 0) {
const allSyncData = syncDataQueue;
const allSyncRows = syncDataQueue;
syncDataQueue = [];
const nonProcessedSyncRows = allSyncRows.filter(sync => !processedSyncIds.has(sync.id));
try {
await processSyncRows(allSyncData);
await processSyncRows(nonProcessedSyncRows);
}
catch (e) {
logError(`Encountered error ${e.message}: ${e.stack}, reloading frontend.`);
@@ -157,7 +165,11 @@ async function consumeSyncData() {
utils.reloadApp();
}
lastProcessedSyncId = Math.max(lastProcessedSyncId, allSyncData[allSyncData.length - 1].id);
for (const syncRow of nonProcessedSyncRows) {
processedSyncIds.add(syncRow.id);
}
lastProcessedSyncId = Math.max(lastProcessedSyncId, allSyncRows[allSyncRows.length - 1].id);
}
checkSyncIdListeners();
@@ -221,18 +233,18 @@ subscribeToMessages(message => {
async function processSyncRows(syncRows) {
const missingNoteIds = [];
syncRows.forEach(({entityName, entity}) => {
if (entityName === 'branches' && !(entity.parentNoteId in treeCache.notes)) {
for (const {entityName, entity} of syncRows) {
if (entityName === 'branches' && !(entity.parentNoteId in treeCache.notes)) {
missingNoteIds.push(entity.parentNoteId);
}
else if (entityName === 'attributes'
&& entity.type === 'relation'
&& entity.name === 'template'
&& !(entity.noteId in treeCache.notes)) {
}
else if (entityName === 'attributes'
&& entity.type === 'relation'
&& entity.name === 'template'
&& !(entity.noteId in treeCache.notes)) {
missingNoteIds.push(entity.value);
}
});
missingNoteIds.push(entity.value);
}
}
if (missingNoteIds.length > 0) {
await treeCache.reloadNotes(missingNoteIds);
@@ -240,16 +252,16 @@ async function processSyncRows(syncRows) {
const loadResults = new LoadResults(treeCache);
syncRows.filter(sync => sync.entityName === 'notes').forEach(sync => {
for (const sync of syncRows.filter(sync => sync.entityName === 'notes')) {
const note = treeCache.notes[sync.entityId];
if (note) {
note.update(sync.entity);
loadResults.addNote(sync.entityId, sync.sourceId);
}
});
}
syncRows.filter(sync => sync.entityName === 'branches').forEach(sync => {
for (const sync of syncRows.filter(sync => sync.entityName === 'branches')) {
let branch = treeCache.branches[sync.entityId];
const childNote = treeCache.notes[sync.entity.noteId];
const parentNote = treeCache.notes[sync.entity.parentNoteId];
@@ -295,9 +307,9 @@ async function processSyncRows(syncRows) {
}
}
}
});
}
syncRows.filter(sync => sync.entityName === 'note_reordering').forEach(sync => {
for (const sync of syncRows.filter(sync => sync.entityName === 'note_reordering')) {
for (const branchId in sync.positions) {
const branch = treeCache.branches[branchId];
@@ -307,10 +319,10 @@ async function processSyncRows(syncRows) {
}
loadResults.addNoteReordering(sync.entityId, sync.sourceId);
});
}
// missing reloading the relation target note
syncRows.filter(sync => sync.entityName === 'attributes').forEach(sync => {
for (const sync of syncRows.filter(sync => sync.entityName === 'attributes')) {
let attribute = treeCache.attributes[sync.entityId];
const sourceNote = treeCache.notes[sync.entity.noteId];
const targetNote = sync.entity.type === 'relation' && treeCache.notes[sync.entity.value];
@@ -346,27 +358,27 @@ async function processSyncRows(syncRows) {
}
}
}
});
}
syncRows.filter(sync => sync.entityName === 'note_contents').forEach(sync => {
for (const sync of syncRows.filter(sync => sync.entityName === 'note_contents')) {
delete treeCache.noteComplementPromises[sync.entityId];
loadResults.addNoteContent(sync.entityId, sync.sourceId);
});
}
syncRows.filter(sync => sync.entityName === 'note_revisions').forEach(sync => {
for (const sync of syncRows.filter(sync => sync.entityName === 'note_revisions')) {
loadResults.addNoteRevision(sync.entityId, sync.noteId, sync.sourceId);
});
}
syncRows.filter(sync => sync.entityName === 'options').forEach(sync => {
for (const sync of syncRows.filter(sync => sync.entityName === 'options')) {
if (sync.entity.name === 'openTabs') {
return; // only noise
continue; // only noise
}
options.set(sync.entity.name, sync.entity.value);
loadResults.addOption(sync.entity.name);
});
}
if (!loadResults.isEmpty()) {
if (loadResults.hasAttributeRelatedChanges()) {

View File

@@ -9,9 +9,11 @@ class ZoomService extends Component {
constructor() {
super();
options.initializedPromise.then(() => {
this.setZoomFactor(options.getFloat('zoomFactor'));
});
if (utils.isElectron()) {
options.initializedPromise.then(() => {
this.setZoomFactor(options.getFloat('zoomFactor'));
});
}
}
setZoomFactor(zoomFactor) {

View File

@@ -82,6 +82,10 @@ export default class Component {
let release;
try {
if (this.mutex.isLocked()) {
console.debug("Mutex locked for", this.constructor.name);
}
release = await this.mutex.acquire();
await fun.call(this, data);
@@ -93,4 +97,4 @@ export default class Component {
}
}
}
}
}

View File

@@ -308,9 +308,10 @@ export default class NoteDetailWidget extends TabAwareWidget {
return;
}
await noteCreateService.createNote(note.noteId, {
// without await as this otherwise causes deadlock through component mutex
noteCreateService.createNote(note.noteId, {
isProtected: note.isProtected,
saveSelection: true
});
}
}
}

View File

@@ -265,6 +265,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
const notes = this.getSelectedOrActiveNodes(node).map(node => ({
noteId: node.data.noteId,
branchId: node.data.branchId,
title: node.title
}));
@@ -304,17 +305,28 @@ export default class NoteTreeWidget extends TabAwareWidget {
});
}
else {
const jsonStr = dataTransfer.getData("text");
let notes = null;
try {
notes = JSON.parse(jsonStr);
}
catch (e) {
console.error(`Cannot parse ${jsonStr} into notes for drop`);
return;
}
// This function MUST be defined to enable dropping of items on the tree.
// data.hitMode is 'before', 'after', or 'over'.
const selectedBranchIds = this.getSelectedOrActiveNodes().map(node => node.data.branchId);
const selectedBranchIds = notes.map(note => note.branchId);
if (data.hitMode === "before") {
branchService.moveBeforeBranch(selectedBranchIds, node.data.branchId);
} else if (data.hitMode === "after") {
branchService.moveAfterBranch(selectedBranchIds, node.data.branchId);
} else if (data.hitMode === "over") {
branchService.moveToParentNote(selectedBranchIds, node.data.noteId);
branchService.moveToParentNote(selectedBranchIds, node.data.branchId);
} else {
throw new Error("Unknown hitMode=" + data.hitMode);
}
@@ -571,13 +583,18 @@ export default class NoteTreeWidget extends TabAwareWidget {
/** @return {FancytreeNode[]} */
getSelectedOrActiveNodes(node = null) {
const notes = this.getSelectedNodes(true);
const nodes = this.getSelectedNodes(true);
if (notes.length === 0) {
notes.push(node ? node : this.getActiveNode());
// the node you start dragging should be included even if not selected
if (node && !nodes.find(n => n.key === node.key)) {
nodes.push(node);
}
return notes;
if (nodes.length === 0) {
nodes.push(this.getActiveNode());
}
return nodes;
}
async setExpandedStatusForSubtree(node, isExpanded) {

View File

@@ -5,17 +5,23 @@ import TypeWidget from "./type_widget.js";
const TPL = `
<div class="note-detail-file note-detail-printable">
<style>
.file-table td {
overflow-wrap: anywhere;
}
</style>
<table class="file-table">
<tr>
<th nowrap>Note ID:</th>
<th>Note ID:</th>
<td class="file-note-id"></td>
<th nowrap>Original file name:</th>
<th>Original file name:</th>
<td class="file-filename"></td>
</tr>
<tr>
<th nowrap>File type:</th>
<th>File type:</th>
<td class="file-filetype"></td>
<th nowrap>File size:</th>
<th>File size:</th>
<td class="file-filesize"></td>
</tr>
</table>
@@ -94,7 +100,7 @@ export default class FileTypeWidget extends TypeWidget {
toastService.showError("Upload of a new file revision failed.");
}
});
return this.$widget;
}
@@ -130,4 +136,4 @@ export default class FileTypeWidget extends TypeWidget {
getFileUrl() {
return utils.getUrlForDownload("api/notes/" + this.noteId + "/download");
}
}
}

View File

@@ -14,25 +14,33 @@ const TaskContext = require('../../services/task_context');
*/
async function moveBranchToParent(req) {
const {branchId, parentNoteId} = req.params;
const {branchId, parentBranchId} = req.params;
const parentBranch = await repository.getBranch(parentBranchId);
const branchToMove = await repository.getBranch(branchId);
if (branchToMove.parentNoteId === parentNoteId) {
if (!parentBranch || !branchToMove) {
return [400, `One or both branches ${branchId}, ${parentBranchId} have not been found`];
}
if (branchToMove.parentNoteId === parentBranch.noteId) {
return { success: true }; // no-op
}
const validationResult = await treeService.validateParentChild(parentNoteId, branchToMove.noteId, branchId);
const validationResult = await treeService.validateParentChild(parentBranch.noteId, branchToMove.noteId, branchId);
if (!validationResult.success) {
return [200, validationResult];
}
const maxNotePos = await sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [parentNoteId]);
const maxNotePos = await sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [parentBranch.noteId]);
const newNotePos = maxNotePos === null ? 0 : maxNotePos + 10;
const newBranch = branchToMove.createClone(parentNoteId, newNotePos);
newBranch.isExpanded = true;
// expanding so that the new placement of the branch is immediately visible
parentBranch.isExpanded = true;
await parentBranch.save();
const newBranch = branchToMove.createClone(parentBranch.noteId, newNotePos);
await newBranch.save();
branchToMove.isDeleted = true;
@@ -178,4 +186,4 @@ module.exports = {
setExpandedForSubtree,
deleteBranch,
setPrefix
};
};

View File

@@ -2,8 +2,15 @@
const sql = require('../../services/sql');
const log = require('../../services/log');
const backupService = require('../../services/backup');
const consistencyChecksService = require('../../services/consistency_checks');
async function backupDatabase() {
return {
backupFile: await backupService.backupNow("now")
};
}
async function vacuumDatabase() {
await sql.execute("VACUUM");
@@ -15,6 +22,7 @@ async function findAndFixConsistencyIssues() {
}
module.exports = {
backupDatabase,
vacuumDatabase,
findAndFixConsistencyIssues
};
};

View File

@@ -11,6 +11,14 @@ async function exportBranch(req, res) {
const {branchId, type, format, version, taskId} = req.params;
const branch = await repository.getBranch(branchId);
if (!branch) {
const message = `Cannot export branch ${branchId} since it does not exist.`;
log.error(message);
res.status(500).send(message);
return;
}
const taskContext = new TaskContext(taskId, 'export');
try {
@@ -39,4 +47,4 @@ async function exportBranch(req, res) {
module.exports = {
exportBranch
};
};

View File

@@ -16,7 +16,7 @@ const ApiToken = require('../../entities/api_token');
async function loginSync(req) {
if (!await sqlInit.schemaExists()) {
return [400, { message: "DB schema does not exist, can't sync." }];
return [500, { message: "DB schema does not exist, can't sync." }];
}
const timestampStr = req.body.timestamp;
@@ -27,7 +27,7 @@ async function loginSync(req) {
// login token is valid for 5 minutes
if (Math.abs(timestamp.getTime() - now.getTime()) > 5 * 60 * 1000) {
return [400, { message: 'Auth request time is out of sync, please check that both client and server have correct time.' }];
return [401, { message: 'Auth request time is out of sync, please check that both client and server have correct time.' }];
}
const syncVersion = req.body.syncVersion;
@@ -102,4 +102,4 @@ module.exports = {
loginSync,
loginToProtectedSession,
token
};
};

View File

@@ -25,7 +25,7 @@ const importRoute = require('./api/import');
const setupApiRoute = require('./api/setup');
const sqlRoute = require('./api/sql');
const anonymizationRoute = require('./api/anonymization');
const cleanupRoute = require('./api/cleanup');
const databaseRoute = require('./api/database');
const imageRoute = require('./api/image');
const attributesRoute = require('./api/attributes');
const scriptRoute = require('./api/script');
@@ -123,7 +123,7 @@ function register(app) {
apiRoute(POST, '/api/tree/load', treeApiRoute.load);
apiRoute(PUT, '/api/branches/:branchId/set-prefix', branchesApiRoute.setPrefix);
apiRoute(PUT, '/api/branches/:branchId/move-to/:parentNoteId', branchesApiRoute.moveBranchToParent);
apiRoute(PUT, '/api/branches/:branchId/move-to/:parentBranchId', branchesApiRoute.moveBranchToParent);
apiRoute(PUT, '/api/branches/:branchId/move-before/:beforeBranchId', branchesApiRoute.moveBranchBeforeNote);
apiRoute(PUT, '/api/branches/:branchId/move-after/:afterBranchId', branchesApiRoute.moveBranchAfterNote);
apiRoute(PUT, '/api/branches/:branchId/expanded/:expanded', branchesApiRoute.setExpanded);
@@ -222,10 +222,13 @@ function register(app) {
apiRoute(POST, '/api/sql/execute', sqlRoute.execute);
apiRoute(POST, '/api/anonymization/anonymize', anonymizationRoute.anonymize);
// VACUUM requires execution outside of transaction
route(POST, '/api/cleanup/vacuum-database', [auth.checkApiAuthOrElectron, csrfMiddleware], cleanupRoute.vacuumDatabase, apiResultHandler, false);
// backup requires execution outside of transaction
route(POST, '/api/database/backup-database', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.backupDatabase, apiResultHandler, false);
route(POST, '/api/cleanup/find-and-fix-consistency-issues', [auth.checkApiAuthOrElectron, csrfMiddleware], cleanupRoute.findAndFixConsistencyIssues, apiResultHandler, false);
// VACUUM requires execution outside of transaction
route(POST, '/api/database/vacuum-database', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.vacuumDatabase, apiResultHandler, false);
route(POST, '/api/database/find-and-fix-consistency-issues', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.findAndFixConsistencyIssues, apiResultHandler, false);
apiRoute(POST, '/api/script/exec', scriptRoute.exec);
apiRoute(POST, '/api/script/run/:noteId', scriptRoute.run);
@@ -267,4 +270,4 @@ function register(app) {
module.exports = {
register
};
};

View File

@@ -28,14 +28,43 @@ async function periodBackup(optionName, fileName, periodInSeconds) {
}
}
const BACKUP_ATTEMPT_COUNT = 50;
async function backupNow(name) {
const sql = require('./sql');
// we don't want to backup DB in the middle of sync with potentially inconsistent DB state
await syncMutexService.doExclusively(async () => {
return await syncMutexService.doExclusively(async () => {
const backupFile = `${dataDir.BACKUP_DIR}/backup-${name}.db`;
fs.copySync(dataDir.DOCUMENT_PATH, backupFile);
try {
fs.unlinkSync(backupFile);
}
catch (e) {} // unlink throws exception if the file did not exist
log.info("Created backup at " + backupFile);
let success = false;
let attemptCount = 0
for (; attemptCount < BACKUP_ATTEMPT_COUNT && !success; attemptCount++) {
try {
await sql.executeNoWrap(`VACUUM INTO '${backupFile}'`);
success++;
}
catch (e) {
log.info(`Backup attempt ${attemptCount + 1} failed with "${e.message}", retrying...`);
}
// we re-try since VACUUM is very picky and it can't run if there's any other query currently running
// which is difficult to guarantee so we just re-try
}
if (attemptCount === BACKUP_ATTEMPT_COUNT) {
log.error(`Creating backup ${backupFile} failed`);
}
else {
log.info("Created backup at " + backupFile);
}
return backupFile;
});
}
@@ -44,12 +73,12 @@ if (!fs.existsSync(dataDir.BACKUP_DIR)) {
}
sqlInit.dbReady.then(() => {
setInterval(cls.wrap(regularBackup), 60 * 60 * 1000);
setInterval(cls.wrap(regularBackup), 4 * 60 * 60 * 1000);
// kickoff backup immediately
setTimeout(cls.wrap(regularBackup), 1000);
// kickoff first backup soon after start up
setTimeout(cls.wrap(regularBackup), 5 * 60 * 1000);
});
module.exports = {
backupNow
};
};

View File

@@ -1 +1 @@
module.exports = { buildDate:"2020-05-12T16:46:45+02:00", buildRevision: "4f50864ec8346a12d7845cb4c91a3de3b1043d34" };
module.exports = { buildDate:"2020-05-31T10:33:12+02:00", buildRevision: "50a28d8c5198606ee5d92696095c1c97397592e8" };

View File

@@ -662,7 +662,9 @@ async function scanForLinks(note) {
const content = await note.getContent();
const newContent = await saveLinks(note, content);
await note.setContent(newContent);
if (content !== newContent) {
await note.setContent(newContent);
}
}
catch (e) {
log.error(`Could not scan for links note ${note.noteId}: ${e.message} ${e.stack}`);

View File

@@ -153,6 +153,10 @@ async function execute(query, params = []) {
return await wrap(async db => db.run(query, ...params), query);
}
async function executeNoWrap(query, params = []) {
await dbConnection.run(query, ...params);
}
async function executeMany(query, params) {
// essentially just alias
await getManyRows(query, params);
@@ -264,6 +268,7 @@ module.exports = {
getMap,
getColumn,
execute,
executeNoWrap,
executeMany,
executeScript,
transactional,