mirror of
https://github.com/zadam/trilium.git
synced 2025-10-29 17:26:38 +01:00
Compare commits
33 Commits
v0.24.0-be
...
v0.24.3-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acd001501b | ||
|
|
0019865807 | ||
|
|
137ffcc4e3 | ||
|
|
585398ad5c | ||
|
|
50401954d1 | ||
|
|
32a9df8489 | ||
|
|
5bf5d1cac4 | ||
|
|
3608857f25 | ||
|
|
16a1dc12df | ||
|
|
9c834229b9 | ||
|
|
3fd45b15e7 | ||
|
|
f20ab45576 | ||
|
|
77a89d85c8 | ||
|
|
30249a353e | ||
|
|
eb9bae9010 | ||
|
|
0c7ae527c5 | ||
|
|
fef4705e2f | ||
|
|
568c2c997f | ||
|
|
d6b5cd6ead | ||
|
|
00ce379962 | ||
|
|
b1ed022771 | ||
|
|
ad6cb6ba34 | ||
|
|
2e76de5f34 | ||
|
|
4f23f2515a | ||
|
|
8e16cc2326 | ||
|
|
49bca04ebb | ||
|
|
05e9669eaf | ||
|
|
62a250a7fc | ||
|
|
8299524682 | ||
|
|
7691a59977 | ||
|
|
3db2f6784d | ||
|
|
48684d0509 | ||
|
|
1ee8d9fd93 |
@@ -1,4 +1,4 @@
|
||||
FROM node:10.12.0
|
||||
FROM node:10.13.0
|
||||
|
||||
RUN apt-get update && apt-get install -y nasm
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ fi
|
||||
|
||||
VERSION=$1
|
||||
PKG_DIR=dist/trilium-linux-x64-server
|
||||
NODE_VERSION=10.12.0
|
||||
NODE_VERSION=10.13.0
|
||||
|
||||
rm -r $PKG_DIR
|
||||
mkdir $PKG_DIR
|
||||
|
||||
12
bin/build.sh
12
bin/build.sh
@@ -11,15 +11,21 @@ rm -r dist/*
|
||||
echo "Rebuilding binaries for linux-ia32"
|
||||
./node_modules/.bin/electron-rebuild --arch=ia32
|
||||
|
||||
./node_modules/.bin/electron-packager . --out=dist --platform=linux --arch=ia32 --overwrite
|
||||
./node_modules/.bin/electron-packager . --out=dist --executable-name=trilium --platform=linux --arch=ia32 --overwrite
|
||||
|
||||
./node_modules/.bin/electron-packager . --out=dist --platform=win32 --arch=x64 --overwrite
|
||||
mv "./dist/Trilium Notes-linux-ia32" ./dist/trilium-linux-ia32
|
||||
|
||||
./node_modules/.bin/electron-packager . --out=dist --executable-name=trilium --platform=win32 --arch=x64 --overwrite --icon=src/public/images/app-icons/win/icon.ico
|
||||
|
||||
mv "./dist/Trilium Notes-win32-x64" ./dist/trilium-win32-x64
|
||||
|
||||
# we build x64 as second so that we keep X64 binaries in node_modules for local development and server build
|
||||
echo "Rebuilding binaries for linux-x64"
|
||||
./node_modules/.bin/electron-rebuild --arch=x64
|
||||
|
||||
./node_modules/.bin/electron-packager . --out=dist --platform=linux --arch=x64 --overwrite
|
||||
./node_modules/.bin/electron-packager . --out=dist --executable-name=trilium --platform=linux --arch=x64 --overwrite
|
||||
|
||||
mv "./dist/Trilium Notes-linux-x64" ./dist/trilium-linux-x64
|
||||
|
||||
echo "Copying required windows binaries"
|
||||
|
||||
|
||||
BIN
db/demo.tar
BIN
db/demo.tar
Binary file not shown.
@@ -1,3 +1,9 @@
|
||||
-- first fix deleted status of existing images
|
||||
UPDATE note_images SET isDeleted = 1 WHERE noteId IN (SELECT noteId FROM notes WHERE isDeleted = 1);
|
||||
|
||||
-- we don't need set data to null because table is going to be dropped anyway and we want image size into attribute
|
||||
UPDATE images SET isDeleted = 1 WHERE imageId NOT IN (SELECT imageId FROM note_images WHERE isDeleted = 0);
|
||||
|
||||
-- allow null for note content (for deleted notes)
|
||||
CREATE TABLE IF NOT EXISTS "notes_mig" (
|
||||
`noteId` TEXT NOT NULL,
|
||||
|
||||
1
db/migrations/0117__fix_attributes_of_deleted_notes.sql
Normal file
1
db/migrations/0117__fix_attributes_of_deleted_notes.sql
Normal file
@@ -0,0 +1 @@
|
||||
UPDATE attributes SET isDeleted = 1 WHERE noteId IN (SELECT noteId FROM notes WHERE isDeleted = 1);
|
||||
1
db/migrations/0118__fix_broken_relations.sql
Normal file
1
db/migrations/0118__fix_broken_relations.sql
Normal file
@@ -0,0 +1 @@
|
||||
UPDATE attributes SET isDeleted = 1 WHERE type = 'relation' AND value NOT IN (SELECT noteId FROM notes WHERE notes.isDeleted = 0);
|
||||
1
db/migrations/0119__rename_mirror_to_inverse.sql
Normal file
1
db/migrations/0119__rename_mirror_to_inverse.sql
Normal file
@@ -0,0 +1 @@
|
||||
UPDATE attributes SET value = replace(value, 'mirrorRelation', 'inverseRelation') WHERE type = 'relation-definition';
|
||||
@@ -288,7 +288,7 @@
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -730,7 +730,7 @@
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -3814,7 +3814,7 @@ transactional by default.
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -511,7 +511,7 @@ Each note can have multiple (at least one) branches, meaning it can be placed in
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -216,7 +216,7 @@
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -358,7 +358,7 @@ this is different concept than attribute/relation.</div>
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -7297,7 +7297,7 @@ Cache is note instance scoped.
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -403,7 +403,7 @@
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -311,7 +311,7 @@
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -288,7 +288,7 @@
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -75,7 +75,7 @@ module.exports = ApiToken;</code></pre>
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -151,7 +151,7 @@ module.exports = Attribute;</code></pre>
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -105,7 +105,7 @@ module.exports = Branch;</code></pre>
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -93,7 +93,7 @@ module.exports = Entity;</code></pre>
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -92,7 +92,7 @@ module.exports = Link;</code></pre>
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -651,7 +651,7 @@ module.exports = Note;</code></pre>
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -91,7 +91,7 @@ module.exports = NoteRevision;</code></pre>
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -78,7 +78,7 @@ module.exports = Option;</code></pre>
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -75,7 +75,7 @@ module.exports = RecentNote;</code></pre>
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -594,7 +594,7 @@
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -278,7 +278,7 @@ module.exports = BackendScriptApi;</code></pre>
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -719,7 +719,7 @@
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:28 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -2846,7 +2846,7 @@ Internally this serializes the anonymous function into string and sends it to ba
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:28 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -279,7 +279,7 @@
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:28 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -1316,7 +1316,7 @@ Its notable omission is the note content.</div>
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:28 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -76,7 +76,7 @@ export default Branch;</code></pre>
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:28 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -64,7 +64,7 @@ export default NoteFull;</code></pre>
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:28 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -128,7 +128,7 @@ export default NoteShort;</code></pre>
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:28 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -339,7 +339,7 @@
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:28 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:28 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -271,7 +271,7 @@ export default FrontendScriptApi;</code></pre>
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:28 GMT+0100 (Central European Standard Time)
|
||||
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
|
||||
@@ -70,6 +70,8 @@ app.on('activate', () => {
|
||||
});
|
||||
|
||||
app.on('ready', async () => {
|
||||
app.setAppUserModelId('com.github.zadam.trilium');
|
||||
|
||||
mainWindow = await createMainWindow();
|
||||
|
||||
const result = globalShortcut.register('CommandOrControl+Alt+P', cls.wrap(async () => {
|
||||
|
||||
7
jsdoc-conf.json
Normal file
7
jsdoc-conf.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"templates": {
|
||||
"default": {
|
||||
"includeDate": false
|
||||
}
|
||||
}
|
||||
}
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "trilium",
|
||||
"version": "0.23.1",
|
||||
"version": "0.24.2-beta",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
12
package.json
12
package.json
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.24.0-beta",
|
||||
"version": "0.24.3-beta",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
@@ -13,12 +14,9 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node ./src/www",
|
||||
"test-electron": "xo",
|
||||
"rebuild-electron": "electron-rebuild",
|
||||
"start-electron": "electron . --disable-gpu",
|
||||
"build-electron": "electron-packager . --out=dist --asar --overwrite --platform=win32,linux --arch=ia32,x64 --app-version= --icon=src/public/app-icons/win/icon.ico",
|
||||
"build-backend-docs": "jsdoc -d ./docs/backend_api src/entities/*.js src/services/backend_script_api.js",
|
||||
"build-frontend-docs": "jsdoc -d ./docs/frontend_api src/public/javascripts/entities/*.js src/public/javascripts/services/frontend_script_api.js",
|
||||
"build-backend-docs": "jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/entities/*.js src/services/backend_script_api.js",
|
||||
"build-frontend-docs": "jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/javascripts/entities/*.js src/public/javascripts/services/frontend_script_api.js",
|
||||
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -64,7 +62,7 @@
|
||||
"tar-stream": "1.6.2",
|
||||
"turndown": "5.0.1",
|
||||
"unescape": "1.0.1",
|
||||
"ws": "6.1.0",
|
||||
"ws": "6.1.2",
|
||||
"xml2js": "0.4.19"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -72,7 +72,7 @@ function AttributesModel() {
|
||||
|
||||
attr.relationDefinition = (attr.type === 'relation-definition' && attr.value) ? attr.value : {
|
||||
multiplicityType: "singlevalue",
|
||||
mirrorRelation: "",
|
||||
inverseRelation: "",
|
||||
isPromoted: true
|
||||
};
|
||||
|
||||
@@ -114,7 +114,7 @@ function AttributesModel() {
|
||||
|
||||
function isValid() {
|
||||
for (let attributes = self.ownedAttributes(), i = 0; i < attributes.length; i++) {
|
||||
if (self.isEmptyName(i)) {
|
||||
if (self.isEmptyName(i) || self.isEmptyRelationTarget(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -191,7 +191,7 @@ function AttributesModel() {
|
||||
},
|
||||
relationDefinition: {
|
||||
multiplicityType: "singlevalue",
|
||||
mirrorRelation: "",
|
||||
inverseRelation: "",
|
||||
isPromoted: true
|
||||
}
|
||||
}));
|
||||
@@ -209,7 +209,35 @@ function AttributesModel() {
|
||||
this.isEmptyName = function(index) {
|
||||
const cur = self.ownedAttributes()[index]();
|
||||
|
||||
return cur.name.trim() === "" && !cur.isDeleted && (cur.attributeId !== "" || cur.labelValue !== "" || cur.relationValue);
|
||||
if (cur.name.trim() || cur.isDeleted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cur.attributeId) {
|
||||
// name is empty and attribute already exists so this is NO-GO
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cur.type === 'relation-definition' || cur.type === 'label-definition') {
|
||||
// for definitions there's no possible empty value so we always require name
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cur.type === 'label' && cur.labelValue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cur.type === 'relation' && cur.relationValue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
this.isEmptyRelationTarget = function(index) {
|
||||
const cur = self.ownedAttributes()[index]();
|
||||
|
||||
return cur.type === "relation" && !cur.isDeleted && cur.name && !cur.relationValue;
|
||||
};
|
||||
|
||||
this.getTargetAttribute = function(target) {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
const $dialog = $("#prompt-dialog");
|
||||
const $question = $("#prompt-dialog-question");
|
||||
const $answer = $("#prompt-dialog-answer");
|
||||
const $dialogBody = $dialog.find(".modal-body");
|
||||
|
||||
let $question;
|
||||
let $answer;
|
||||
|
||||
const $form = $("#prompt-dialog-form");
|
||||
|
||||
let resolve;
|
||||
@@ -11,8 +14,21 @@ function ask({ message, defaultValue, shown }) {
|
||||
|
||||
shownCb = shown;
|
||||
|
||||
$question.text(message);
|
||||
$answer.val(defaultValue || "");
|
||||
$question = $("<label>")
|
||||
.prop("for", "prompt-dialog-answer")
|
||||
.text(message);
|
||||
|
||||
$answer = $("<input>")
|
||||
.prop("type", "text")
|
||||
.prop("id", "prompt-dialog-answer")
|
||||
.addClass("form-control")
|
||||
.val(defaultValue || "");
|
||||
|
||||
$dialogBody.empty().append(
|
||||
$("<div>")
|
||||
.addClass("form-group")
|
||||
.append($question)
|
||||
.append($answer));
|
||||
|
||||
$dialog.modal();
|
||||
|
||||
|
||||
@@ -50,7 +50,12 @@ async function execute(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const sqlQuery = codeEditor.getValue();
|
||||
// execute the selected text or the whole content if there's no selection
|
||||
let sqlQuery = codeEditor.getSelection();
|
||||
|
||||
if (!sqlQuery) {
|
||||
sqlQuery = codeEditor.getValue();
|
||||
}
|
||||
|
||||
const result = await server.post("sql/execute", {
|
||||
query: sqlQuery
|
||||
|
||||
@@ -15,6 +15,8 @@ function initAttributeNameAutocomplete({ $el, attributeType, open }) {
|
||||
minLength: 0
|
||||
}, [{
|
||||
displayKey: 'name',
|
||||
// disabling cache is important here because otherwise cache can stay intact when switching between attribute type which will lead to autocomplete displaying attribute names for incorrect attribute type
|
||||
cache: false,
|
||||
source: async (term, cb) => {
|
||||
const type = typeof attributeType === "function" ? attributeType() : attributeType;
|
||||
|
||||
|
||||
8
src/public/javascripts/services/bootstrap.js
vendored
8
src/public/javascripts/services/bootstrap.js
vendored
@@ -103,7 +103,13 @@ if (utils.isElectron()) {
|
||||
});
|
||||
}
|
||||
|
||||
$("#export-note-to-markdown-button").click(() => exportService.exportSubtree(noteDetailService.getCurrentNoteId(), 'markdown-single'));
|
||||
$("#export-note-to-markdown-button").click(function () {
|
||||
if ($(this).hasClass("disabled")) {
|
||||
return;
|
||||
}
|
||||
|
||||
exportService.exportSubtree(noteDetailService.getCurrentNoteId(), 'markdown-single')
|
||||
});
|
||||
|
||||
treeService.showTree();
|
||||
|
||||
|
||||
@@ -10,15 +10,13 @@ const dragAndDropSetup = {
|
||||
|
||||
node.setSelected(true);
|
||||
|
||||
const selectedNodes = treeService.getSelectedNodes().map(node => {
|
||||
return {
|
||||
noteId: node.data.noteId,
|
||||
title: node.title
|
||||
}
|
||||
});
|
||||
|
||||
// this is for dragging notes into relation map
|
||||
data.dataTransfer.setData("text", JSON.stringify(selectedNodes));
|
||||
// we allow to drag only one note at a time because it multi-drag conflicts with multiple single drags
|
||||
// in UX and single drag is probably more useful
|
||||
data.dataTransfer.setData("text", JSON.stringify({
|
||||
noteId: node.data.noteId,
|
||||
title: node.title
|
||||
}));
|
||||
|
||||
// This function MUST be defined to enable dragging for the tree.
|
||||
// Return false to cancel dragging of node.
|
||||
|
||||
@@ -25,9 +25,21 @@ function registerEntrypoints() {
|
||||
$("#jump-to-note-dialog-button").click(jumpToNoteDialog.showDialog);
|
||||
utils.bindShortcut('ctrl+j', jumpToNoteDialog.showDialog);
|
||||
|
||||
$("#show-note-revisions-button").click(noteRevisionsDialog.showCurrentNoteRevisions);
|
||||
$("#show-note-revisions-button").click(function() {
|
||||
if ($(this).hasClass("disabled")) {
|
||||
return;
|
||||
}
|
||||
|
||||
$("#show-source-button").click(noteSourceDialog.showDialog);
|
||||
noteRevisionsDialog.showCurrentNoteRevisions();
|
||||
});
|
||||
|
||||
$("#show-source-button").click(function() {
|
||||
if ($(this).hasClass("disabled")) {
|
||||
return;
|
||||
}
|
||||
|
||||
noteSourceDialog.showDialog();
|
||||
});
|
||||
|
||||
$("#recent-changes-button").click(recentChangesDialog.showDialog);
|
||||
|
||||
@@ -85,12 +97,14 @@ function registerEntrypoints() {
|
||||
|
||||
$(document).bind('keydown', 'ctrl+f', () => {
|
||||
if (utils.isElectron()) {
|
||||
const searchInPage = require('electron-in-page-search').default;
|
||||
const remote = require('electron').remote;
|
||||
alert("In page search doesn't work in this beta");
|
||||
|
||||
const inPageSearch = searchInPage(remote.getCurrentWebContents());
|
||||
|
||||
inPageSearch.openSearchWindow();
|
||||
// const searchInPage = require('electron-in-page-search').default;
|
||||
// const remote = require('electron').remote;
|
||||
//
|
||||
// const inPageSearch = searchInPage(remote.getCurrentWebContents());
|
||||
//
|
||||
// inPageSearch.openSearchWindow();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ function initNoteAutocomplete($el, options) {
|
||||
$el.on('autocomplete:selected', (event, suggestion) => $el.setSelectedPath(suggestion.path));
|
||||
$el.on('autocomplete:closed', () => {
|
||||
if (!$el.val().trim()) {
|
||||
$el.setSelectedPath("");
|
||||
clearText($el);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,7 +32,10 @@ async function show() {
|
||||
lint: true,
|
||||
gutters: ["CodeMirror-lint-markers"],
|
||||
lineNumbers: true,
|
||||
tabindex: 100
|
||||
tabindex: 100,
|
||||
// we linewrap partly also because without it horizontal scrollbar displays only when you scroll
|
||||
// all the way to the bottom of the note. With line wrap there's no horizontal scrollbar so no problem
|
||||
lineWrapping: true
|
||||
});
|
||||
|
||||
onNoteChange(noteDetailService.noteChanged);
|
||||
@@ -43,7 +46,9 @@ async function show() {
|
||||
const currentNote = noteDetailService.getCurrentNote();
|
||||
|
||||
// this needs to happen after the element is shown, otherwise the editor won't be refreshed
|
||||
codeEditor.setValue(currentNote.content);
|
||||
// CodeMirror breaks pretty badly on null so even though it shouldn't happen (guarded by consistency check)
|
||||
// we provide fallback
|
||||
codeEditor.setValue(currentNote.content || "");
|
||||
|
||||
const info = CodeMirror.findModeByMIME(currentNote.mime);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import infoService from "./info.js";
|
||||
import server from "./server.js";
|
||||
|
||||
const $component = $('#note-detail-image');
|
||||
const $imageWrapper = $('#note-detail-image-wrapper');
|
||||
const $imageView = $('#note-detail-image-view');
|
||||
|
||||
const $imageDownloadButton = $("#image-download");
|
||||
@@ -39,10 +40,10 @@ function selectImage(element) {
|
||||
}
|
||||
|
||||
$copyToClipboardButton.click(() => {
|
||||
$component.attr('contenteditable','true');
|
||||
$imageWrapper.attr('contenteditable','true');
|
||||
|
||||
try {
|
||||
selectImage($component.get(0));
|
||||
selectImage($imageWrapper.get(0));
|
||||
|
||||
const success = document.execCommand('copy');
|
||||
|
||||
@@ -55,7 +56,7 @@ $copyToClipboardButton.click(() => {
|
||||
}
|
||||
finally {
|
||||
window.getSelection().removeAllRanges();
|
||||
$component.removeAttr('contenteditable');
|
||||
$imageWrapper.removeAttr('contenteditable');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const $relationMapContainer = $("#relation-map-container");
|
||||
const $createChildNote = $("#relation-map-create-child-note");
|
||||
const $zoomInButton = $("#relation-map-zoom-in");
|
||||
const $zoomOutButton = $("#relation-map-zoom-out");
|
||||
const $centerButton = $("#relation-map-center");
|
||||
const $resetPanZoomButton = $("#relation-map-reset-pan-zoom");
|
||||
|
||||
let mapData;
|
||||
let jsPlumbInstance;
|
||||
@@ -50,7 +50,7 @@ const biDirectionalOverlays = [
|
||||
} ]
|
||||
];
|
||||
|
||||
const mirrorOverlays = [
|
||||
const inverseRelationsOverlays = [
|
||||
[ "Arrow", {
|
||||
location: 1,
|
||||
id: "arrow",
|
||||
@@ -117,6 +117,15 @@ async function show() {
|
||||
|
||||
}
|
||||
|
||||
function clearMap() {
|
||||
// delete all endpoints and connections
|
||||
// this is done at this point (after async operations) to reduce flicker to the minimum
|
||||
jsPlumbInstance.deleteEveryEndpoint();
|
||||
|
||||
// without this we still end up with note boxes remaining in the canvas
|
||||
$relationMapContainer.empty();
|
||||
}
|
||||
|
||||
async function loadNotesAndRelations() {
|
||||
const noteIds = mapData.notes.map(note => note.noteId);
|
||||
const data = await server.post("notes/relation-map", {noteIds});
|
||||
@@ -125,12 +134,12 @@ async function loadNotesAndRelations() {
|
||||
|
||||
for (const relation of data.relations) {
|
||||
const match = relations.find(rel =>
|
||||
rel.name === data.mirrorRelations[relation.name]
|
||||
rel.name === data.inverseRelations[relation.name]
|
||||
&& ((rel.sourceNoteId === relation.sourceNoteId && rel.targetNoteId === relation.targetNoteId)
|
||||
|| (rel.sourceNoteId === relation.targetNoteId && rel.targetNoteId === relation.sourceNoteId)));
|
||||
|
||||
if (match) {
|
||||
match.type = relation.type = relation.name === data.mirrorRelations[relation.name] ? 'biDirectional' : 'mirror';
|
||||
match.type = relation.type = relation.name === data.inverseRelations[relation.name] ? 'biDirectional' : 'inverse';
|
||||
relation.render = false; // don't render second relation
|
||||
} else {
|
||||
relation.type = 'uniDirectional';
|
||||
@@ -142,11 +151,9 @@ async function loadNotesAndRelations() {
|
||||
|
||||
mapData.notes = mapData.notes.filter(note => note.noteId in data.noteTitles);
|
||||
|
||||
// delete all endpoints and connections
|
||||
// this is done at this point (after async operations) to reduce flicker to the minimum
|
||||
jsPlumbInstance.deleteEveryEndpoint();
|
||||
|
||||
jsPlumbInstance.batch(async function () {
|
||||
clearMap();
|
||||
|
||||
for (const note of mapData.notes) {
|
||||
const title = data.noteTitles[note.noteId];
|
||||
|
||||
@@ -166,9 +173,9 @@ async function loadNotesAndRelations() {
|
||||
|
||||
connection.id = relation.attributeId;
|
||||
|
||||
if (relation.type === 'mirror') {
|
||||
if (relation.type === 'inverse') {
|
||||
connection.getOverlay("label-source").setLabel(relation.name);
|
||||
connection.getOverlay("label-target").setLabel(data.mirrorRelations[relation.name]);
|
||||
connection.getOverlay("label-target").setLabel(data.inverseRelations[relation.name]);
|
||||
}
|
||||
else {
|
||||
connection.getOverlay("label").setLabel(relation.name);
|
||||
@@ -208,10 +215,17 @@ function initPanZoom() {
|
||||
|
||||
mapData.notes.push({ noteId: clipboard.noteId, x, y });
|
||||
|
||||
saveData();
|
||||
|
||||
clipboard = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
filterKey: function(e, dx, dy, dz) {
|
||||
// if ALT is pressed then panzoom should bubble the event up
|
||||
// this is to preserve ALT-LEFT, ALT-RIGHT navigation working
|
||||
return e.altKey;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -226,6 +240,10 @@ function initPanZoom() {
|
||||
|
||||
pzInstance.moveTo(mapData.transform.x, mapData.transform.y);
|
||||
}
|
||||
else {
|
||||
// set to initial coordinates
|
||||
pzInstance.moveTo(0, 0);
|
||||
}
|
||||
|
||||
$zoomInButton.click(() => pzInstance.zoomTo(0, 0, 1.2));
|
||||
$zoomOutButton.click(() => pzInstance.zoomTo(0, 0, 0.8));
|
||||
@@ -244,11 +262,7 @@ function saveCurrentTransform() {
|
||||
|
||||
function cleanup() {
|
||||
if (jsPlumbInstance) {
|
||||
// delete all endpoints and connections
|
||||
jsPlumbInstance.deleteEveryEndpoint();
|
||||
|
||||
// without this we still end up with note boxes remaining in the canvas
|
||||
$relationMapContainer.empty();
|
||||
clearMap();
|
||||
}
|
||||
|
||||
if (pzInstance) {
|
||||
@@ -276,7 +290,7 @@ function initJsPlumbInstance () {
|
||||
|
||||
jsPlumbInstance.registerConnectionType("biDirectional", { anchor:"Continuous", connector:"StateMachine", overlays: biDirectionalOverlays });
|
||||
|
||||
jsPlumbInstance.registerConnectionType("mirror", { anchor:"Continuous", connector:"StateMachine", overlays: mirrorOverlays });
|
||||
jsPlumbInstance.registerConnectionType("inverse", { anchor:"Continuous", connector:"StateMachine", overlays: inverseRelationsOverlays });
|
||||
|
||||
jsPlumbInstance.registerConnectionType("link", { anchor:"Continuous", connector:"StateMachine", overlays: linkOverlays });
|
||||
|
||||
@@ -312,8 +326,6 @@ function connectionContextMenuHandler(connection, event) {
|
||||
async function connectionCreatedHandler(info, originalEvent) {
|
||||
const connection = info.connection;
|
||||
|
||||
const isRelation = relations.some(rel => rel.attributeId === connection.id);
|
||||
|
||||
connection.bind("contextmenu", (obj, event) => {
|
||||
if (connection.getType().includes("link")) {
|
||||
// don't create context menu if it's a link since there's nothing to do with link from relation map
|
||||
@@ -362,9 +374,7 @@ async function connectionCreatedHandler(info, originalEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const attribute = await server.put(`notes/${sourceNoteId}/relations/${name}/to/${targetNoteId}`);
|
||||
|
||||
relations.push({ attributeId: attribute.attributeId , targetNoteId, sourceNoteId, name });
|
||||
await server.put(`notes/${sourceNoteId}/relations/${name}/to/${targetNoteId}`);
|
||||
|
||||
await refresh();
|
||||
}
|
||||
@@ -512,43 +522,20 @@ function getZoom() {
|
||||
async function dropNoteOntoRelationMapHandler(ev) {
|
||||
ev.preventDefault();
|
||||
|
||||
const notes = JSON.parse(ev.originalEvent.dataTransfer.getData("text"));
|
||||
const note = JSON.parse(ev.originalEvent.dataTransfer.getData("text"));
|
||||
|
||||
let {x, y} = getMousePosition(ev);
|
||||
|
||||
// modifying position so that cursor is on the top-center of the box
|
||||
const startX = x -= 80;
|
||||
y -= 15;
|
||||
const exists = mapData.notes.some(n => n.noteId === note.noteId);
|
||||
|
||||
const currentNoteId = treeService.getCurrentNode().data.noteId;
|
||||
if (exists) {
|
||||
await infoDialog.info(`Note "${note.title}" is already placed into the diagram`);
|
||||
|
||||
for (const note of notes) {
|
||||
if (note.noteId === currentNoteId) {
|
||||
// we don't allow placing current (relation map) into itself
|
||||
// the reason is that when dragging notes from the tree, the relation map is always selected
|
||||
// since it's focused.
|
||||
continue;
|
||||
}
|
||||
|
||||
const exists = mapData.notes.some(n => n.noteId === note.noteId);
|
||||
|
||||
if (exists) {
|
||||
await infoDialog.info(`Note "${note.title}" is already placed into the diagram`);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
mapData.notes.push({noteId: note.noteId, x, y});
|
||||
|
||||
if (x - startX > 1000) {
|
||||
x = startX;
|
||||
y += 200;
|
||||
}
|
||||
else {
|
||||
x += 200;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
mapData.notes.push({noteId: note.noteId, x, y});
|
||||
|
||||
saveData();
|
||||
|
||||
await refresh();
|
||||
@@ -565,40 +552,10 @@ function getMousePosition(evt) {
|
||||
};
|
||||
}
|
||||
|
||||
$centerButton.click(() => {
|
||||
if (mapData.notes.length === 0) {
|
||||
return; // nothing to recenter on
|
||||
}
|
||||
|
||||
let totalX = 0, totalY = 0;
|
||||
|
||||
for (const note of mapData.notes) {
|
||||
totalX += note.x;
|
||||
totalY += note.y;
|
||||
}
|
||||
|
||||
let averageX = totalX / mapData.notes.length;
|
||||
let averageY = totalY / mapData.notes.length;
|
||||
|
||||
// find note with smallest X, Y difference from the average (most central note)
|
||||
const {noteId} = mapData.notes.map(note => {
|
||||
return {
|
||||
noteId: note.noteId,
|
||||
diff: Math.abs(note.x - averageX) + Math.abs(note.y - averageY)
|
||||
}
|
||||
}).reduce((min, val) => min.diff <= val.min ? min : val, { diff: 9999999999 });
|
||||
|
||||
const $noteBox = $("#" + noteIdToId(noteId));
|
||||
|
||||
const clientRect = $noteBox[0].getBoundingClientRect();
|
||||
const cx = clientRect.left + clientRect.width / 2;
|
||||
const cy = clientRect.top + clientRect.height / 2;
|
||||
|
||||
const container = $component[0].getBoundingClientRect();
|
||||
const dx = container.width / 2 - cx;
|
||||
const dy = container.height / 2 - cy;
|
||||
|
||||
pzInstance.moveBy(dx, dy, true);
|
||||
$resetPanZoomButton.click(() => {
|
||||
// reset to initial pan & zoom state
|
||||
pzInstance.zoomTo(0, 0, 1 / getZoom());
|
||||
pzInstance.moveTo(0, 0);
|
||||
});
|
||||
|
||||
$component.on("drop", dropNoteOntoRelationMapHandler);
|
||||
|
||||
@@ -41,6 +41,10 @@ function setupTooltip() {
|
||||
if ($(this).is(":hover")) {
|
||||
$(this).tooltip({
|
||||
delay: {"show": 300, "hide": 100},
|
||||
container: 'body',
|
||||
placement: 'auto',
|
||||
trigger: 'manual',
|
||||
boundariesElement: 'window',
|
||||
title: html,
|
||||
html: true
|
||||
});
|
||||
@@ -50,7 +54,7 @@ function setupTooltip() {
|
||||
});
|
||||
|
||||
$(document).on("mouseleave", "a", function() {
|
||||
$(this).tooltip('hide');
|
||||
$(this).tooltip('dispose');
|
||||
});
|
||||
|
||||
// close any tooltip after click, this fixes the problem that sometimes tooltips remained on the screen
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
|
||||
#relation-map-wrapper {
|
||||
position: relative;
|
||||
overflow: hidden !important;
|
||||
height: 4000px; /* we need to set fixed dimentions. This number is probably enough to cover any screen */
|
||||
width: 4000px;
|
||||
height: 100%;
|
||||
outline: none; /* remove dotted outline on click */
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ body {
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
flex-basis: content;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.note-detail-component {
|
||||
@@ -557,6 +558,10 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
|
||||
max-height: 250px;
|
||||
}
|
||||
|
||||
.tooltip-inner figure.image-style-side {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.tooltip.show {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ async function updateNoteAttributes(req) {
|
||||
attributeEntity.isInheritable = attribute.isInheritable;
|
||||
attributeEntity.isDeleted = attribute.isDeleted;
|
||||
|
||||
if (attributeEntity.type === 'relation' && !attributeEntity.value.trim()) {
|
||||
if (attributeEntity.type === 'relation' && !attribute.value.trim()) {
|
||||
// relation should never have empty target
|
||||
attributeEntity.isDeleted = true;
|
||||
}
|
||||
|
||||
@@ -117,8 +117,8 @@ async function getRelationMap(req) {
|
||||
// noteId => title
|
||||
noteTitles: {},
|
||||
relations: [],
|
||||
// relation name => mirror relation name
|
||||
mirrorRelations: {},
|
||||
// relation name => inverse relation name
|
||||
inverseRelations: {},
|
||||
links: []
|
||||
};
|
||||
|
||||
@@ -143,8 +143,8 @@ async function getRelationMap(req) {
|
||||
}; }));
|
||||
|
||||
for (const relationDefinition of await note.getRelationDefinitions()) {
|
||||
if (relationDefinition.value.mirrorRelation) {
|
||||
resp.mirrorRelations[relationDefinition.name] = relationDefinition.value.mirrorRelation;
|
||||
if (relationDefinition.value.inverseRelation) {
|
||||
resp.inverseRelations[relationDefinition.name] = relationDefinition.value.inverseRelation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
const build = require('./build');
|
||||
const packageJson = require('../../package');
|
||||
|
||||
const APP_DB_VERSION = 116;
|
||||
const APP_DB_VERSION = 119;
|
||||
const SYNC_VERSION = 2;
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -1 +1 @@
|
||||
module.exports = { buildDate:"2018-11-16T23:30:52+01:00", buildRevision: "90eb1b53fbe915c4658617772aea4347a107a722" };
|
||||
module.exports = { buildDate:"2018-11-20T13:01:41+01:00", buildRevision: "0019865807db83621dab71b206d5ea80ba29f002" };
|
||||
|
||||
@@ -209,6 +209,16 @@ async function runAllChecks() {
|
||||
AND type != 'relation-map'`,
|
||||
"Note has invalid type", errorList);
|
||||
|
||||
await runCheck(`
|
||||
SELECT
|
||||
noteId
|
||||
FROM
|
||||
notes
|
||||
WHERE
|
||||
isDeleted = 0
|
||||
AND content IS NULL`,
|
||||
"Note content is null even though it is not deleted", errorList);
|
||||
|
||||
await runCheck(`
|
||||
SELECT
|
||||
parentNoteId
|
||||
@@ -275,8 +285,9 @@ async function runAllChecks() {
|
||||
LEFT JOIN notes AS sourceNote ON sourceNote.noteId = links.noteId AND sourceNote.isDeleted = 0
|
||||
LEFT JOIN notes AS targetNote ON targetNote.noteId = links.noteId AND targetNote.isDeleted = 0
|
||||
WHERE
|
||||
sourceNote.noteId IS NULL
|
||||
OR targetNote.noteId IS NULL`,
|
||||
links.isDeleted = 0
|
||||
AND (sourceNote.noteId IS NULL
|
||||
OR targetNote.noteId IS NULL)`,
|
||||
"Link to source/target note link is broken", errorList);
|
||||
|
||||
await runSyncRowChecks("notes", "noteId", errorList);
|
||||
|
||||
@@ -4,8 +4,20 @@ const sanitize = require("sanitize-filename");
|
||||
const TurndownService = require('turndown');
|
||||
|
||||
async function exportSingleMarkdown(note, res) {
|
||||
const turndownService = new TurndownService();
|
||||
const markdown = turndownService.turndown(note.content);
|
||||
if (note.type !== 'text' && note.type !== 'code') {
|
||||
return [400, `Note type ${note.type} cannot be exported as single markdown file.`];
|
||||
}
|
||||
|
||||
let markdown;
|
||||
|
||||
if (note.type === 'code') {
|
||||
markdown = '```\n' + note.content + "\n```";
|
||||
}
|
||||
else if (note.type === 'text') {
|
||||
const turndownService = new TurndownService();
|
||||
markdown = turndownService.turndown(note.content);
|
||||
}
|
||||
|
||||
const name = sanitize(note.title);
|
||||
|
||||
res.setHeader('Content-Disposition', 'file; filename="' + name + '.md"');
|
||||
|
||||
@@ -25,7 +25,7 @@ async function exportToMarkdown(branch, res) {
|
||||
return;
|
||||
}
|
||||
|
||||
saveDataFile(childFileName, note);
|
||||
saveNote(childFileName, note);
|
||||
|
||||
const childNotes = await note.getChildNotes();
|
||||
|
||||
@@ -40,11 +40,7 @@ async function exportToMarkdown(branch, res) {
|
||||
return childFileName;
|
||||
}
|
||||
|
||||
function saveDataFile(childFileName, note) {
|
||||
if (note.type !== 'text' && note.type !== 'code') {
|
||||
return;
|
||||
}
|
||||
|
||||
function saveTextNote(childFileName, note) {
|
||||
if (note.content.trim().length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -65,6 +61,19 @@ async function exportToMarkdown(branch, res) {
|
||||
pack.entry({name: childFileName + ".md", size: markdown.length}, markdown);
|
||||
}
|
||||
|
||||
function saveFileNote(childFileName, note) {
|
||||
pack.entry({name: childFileName, size: note.content.length}, note.content);
|
||||
}
|
||||
|
||||
function saveNote(childFileName, note) {
|
||||
if (note.type === 'text' || note.type === 'code') {
|
||||
saveTextNote(childFileName, note);
|
||||
}
|
||||
else if (note.type === 'image' || note.type === 'file') {
|
||||
saveFileNote(childFileName, note);
|
||||
}
|
||||
}
|
||||
|
||||
function saveDirectory(childFileName) {
|
||||
pack.entry({name: childFileName, type: 'directory'});
|
||||
}
|
||||
|
||||
@@ -12,6 +12,11 @@ async function exportToOpml(branch, res) {
|
||||
async function exportNoteInner(branchId) {
|
||||
const branch = await repository.getBranch(branchId);
|
||||
const note = await branch.getNote();
|
||||
|
||||
if (await note.hasLabel('excludeFromExport')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const title = (branch.prefix ? (branch.prefix + ' - ') : '') + note.title;
|
||||
|
||||
const preparedTitle = prepareText(title);
|
||||
|
||||
@@ -59,7 +59,7 @@ eventService.subscribe(eventService.CHILD_NOTE_CREATED, async ({ parentNote, chi
|
||||
await runAttachedRelations(parentNote, 'runOnChildNoteCreation', childNote);
|
||||
});
|
||||
|
||||
async function processMirrorRelations(entityName, entity, handler) {
|
||||
async function processInverseRelations(entityName, entity, handler) {
|
||||
if (entityName === 'attributes' && entity.type === 'relation') {
|
||||
const note = await entity.getNote();
|
||||
const attributes = (await note.getAttributes(entity.name)).filter(relation => relation.type === 'relation-definition');
|
||||
@@ -67,7 +67,7 @@ async function processMirrorRelations(entityName, entity, handler) {
|
||||
for (const attribute of attributes) {
|
||||
const definition = attribute.value;
|
||||
|
||||
if (definition.mirrorRelation && definition.mirrorRelation.trim()) {
|
||||
if (definition.inverseRelation && definition.inverseRelation.trim()) {
|
||||
const targetNote = await entity.getTargetNote();
|
||||
|
||||
await handler(definition, note, targetNote);
|
||||
@@ -77,13 +77,17 @@ async function processMirrorRelations(entityName, entity, handler) {
|
||||
}
|
||||
|
||||
eventService.subscribe(eventService.ENTITY_CHANGED, async ({ entityName, entity }) => {
|
||||
await processMirrorRelations(entityName, entity, async (definition, note, targetNote) => {
|
||||
// we need to make sure that also target's mirror attribute exists and if note, then create it
|
||||
if (!await targetNote.hasRelation(definition.mirrorRelation)) {
|
||||
await processInverseRelations(entityName, entity, async (definition, note, targetNote) => {
|
||||
// we need to make sure that also target's inverse attribute exists and if note, then create it
|
||||
// inverse attribute has to target our note as well
|
||||
const hasInverseAttribute = (await targetNote.getRelations(definition.inverseRelation))
|
||||
.some(attr => attr.value === note.noteId);
|
||||
|
||||
if (!hasInverseAttribute) {
|
||||
await new Attribute({
|
||||
noteId: targetNote.noteId,
|
||||
type: 'relation',
|
||||
name: definition.mirrorRelation,
|
||||
name: definition.inverseRelation,
|
||||
value: note.noteId,
|
||||
isInheritable: entity.isInheritable
|
||||
}).save();
|
||||
@@ -94,16 +98,21 @@ eventService.subscribe(eventService.ENTITY_CHANGED, async ({ entityName, entity
|
||||
});
|
||||
|
||||
eventService.subscribe(eventService.ENTITY_DELETED, async ({ entityName, entity }) => {
|
||||
await processMirrorRelations(entityName, entity, async (definition, note, targetNote) => {
|
||||
// if one mirror attribute is deleted then the other should be deleted as well
|
||||
const relations = await targetNote.getRelations(definition.mirrorRelation);
|
||||
await processInverseRelations(entityName, entity, async (definition, note, targetNote) => {
|
||||
// if one inverse attribute is deleted then the other should be deleted as well
|
||||
const relations = await targetNote.getRelations(definition.inverseRelation);
|
||||
let deletedSomething = false;
|
||||
|
||||
for (const relation of relations) {
|
||||
relation.isDeleted = true;
|
||||
await relation.save();
|
||||
if (relation.value === note.noteId) {
|
||||
relation.isDeleted = true;
|
||||
await relation.save();
|
||||
|
||||
deletedSomething = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (relations.length > 0) {
|
||||
if (deletedSomething) {
|
||||
targetNote.invalidateAttributeCache();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -31,6 +31,11 @@ async function importTar(fileBuffer, parentNote) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// we allow references to root and they don't need translation
|
||||
if (origNoteId === 'root') {
|
||||
return origNoteId;
|
||||
}
|
||||
|
||||
if (!ctx.noteIdMap[origNoteId]) {
|
||||
ctx.noteIdMap[origNoteId] = utils.newEntityId();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ const sqlInit = require('./sql_init');
|
||||
const optionService = require('./options');
|
||||
const fs = require('fs-extra');
|
||||
const log = require('./log');
|
||||
const utils = require('./utils');
|
||||
const resourceDir = require('./resource_dir');
|
||||
|
||||
async function migrate() {
|
||||
@@ -72,7 +73,7 @@ async function migrate() {
|
||||
log.error("error during migration to version " + mig.dbVersion + ": " + e.stack);
|
||||
log.error("migration failed, crashing hard"); // this is not very user friendly :-/
|
||||
|
||||
process.exit(1);
|
||||
utils.crash();
|
||||
}
|
||||
finally {
|
||||
// make sure foreign keys are enabled even if migration script disables them
|
||||
|
||||
@@ -68,6 +68,10 @@ async function createNewNote(parentNoteId, noteData) {
|
||||
noteData.type = noteData.type || parentNote.type;
|
||||
noteData.mime = noteData.mime || parentNote.mime;
|
||||
|
||||
if (noteData.type === 'text' || noteData.type === 'code') {
|
||||
noteData.content = noteData.content || "";
|
||||
}
|
||||
|
||||
const note = await new Note({
|
||||
noteId: noteData.noteId, // optionally can force specific noteId
|
||||
title: noteData.title,
|
||||
@@ -173,7 +177,7 @@ async function protectNoteRevisions(note) {
|
||||
}
|
||||
|
||||
function findImageLinks(content, foundLinks) {
|
||||
const re = /src="\/api\/images\/([a-zA-Z0-9]+)\//g;
|
||||
const re = /src="[^"]*\/api\/images\/([a-zA-Z0-9]+)\//g;
|
||||
let match;
|
||||
|
||||
while (match = re.exec(content)) {
|
||||
@@ -182,11 +186,13 @@ function findImageLinks(content, foundLinks) {
|
||||
targetNoteId: match[1]
|
||||
});
|
||||
}
|
||||
return match;
|
||||
|
||||
// removing absolute references to server to keep it working between instances
|
||||
return content.replace(/src="[^"]*\/api\/images\//g, 'src="/api/images/');
|
||||
}
|
||||
|
||||
function findHyperLinks(content, foundLinks) {
|
||||
const re = /href="#root[a-zA-Z0-9\/]*\/([a-zA-Z0-9]+)\/?"/g;
|
||||
const re = /href="[^"]*#root[a-zA-Z0-9\/]*\/([a-zA-Z0-9]+)\/?"/g;
|
||||
let match;
|
||||
|
||||
while (match = re.exec(content)) {
|
||||
@@ -196,7 +202,8 @@ function findHyperLinks(content, foundLinks) {
|
||||
});
|
||||
}
|
||||
|
||||
return match;
|
||||
// removing absolute references to server to keep it working between instances
|
||||
return content.replace(/href="[^"]*#root/g, 'href="#root');
|
||||
}
|
||||
|
||||
function findRelationMapLinks(content, foundLinks) {
|
||||
@@ -210,19 +217,19 @@ function findRelationMapLinks(content, foundLinks) {
|
||||
}
|
||||
}
|
||||
|
||||
async function saveLinks(note) {
|
||||
async function saveLinks(note, content) {
|
||||
if (note.type !== 'text' && note.type !== 'relation-map') {
|
||||
return;
|
||||
return content;
|
||||
}
|
||||
|
||||
const foundLinks = [];
|
||||
|
||||
if (note.type === 'text') {
|
||||
findImageLinks(note.content, foundLinks);
|
||||
findHyperLinks(note.content, foundLinks);
|
||||
content = findImageLinks(content, foundLinks);
|
||||
content = findHyperLinks(content, foundLinks);
|
||||
}
|
||||
else if (note.type === 'relation-map') {
|
||||
findRelationMapLinks(note.content, foundLinks);
|
||||
findRelationMapLinks(content, foundLinks);
|
||||
}
|
||||
else {
|
||||
throw new Error("Unrecognized type " + note.type);
|
||||
@@ -258,6 +265,8 @@ async function saveLinks(note) {
|
||||
unusedLink.isDeleted = true;
|
||||
await unusedLink.save();
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
async function saveNoteRevision(note) {
|
||||
@@ -306,6 +315,8 @@ async function updateNote(noteId, noteUpdates) {
|
||||
|
||||
const noteTitleChanged = note.title !== noteUpdates.title;
|
||||
|
||||
noteUpdates.content = await saveLinks(note, noteUpdates.content);
|
||||
|
||||
note.title = noteUpdates.title;
|
||||
note.setContent(noteUpdates.content);
|
||||
note.isProtected = noteUpdates.isProtected;
|
||||
@@ -315,8 +326,6 @@ async function updateNote(noteId, noteUpdates) {
|
||||
await triggerNoteTitleChanged(note);
|
||||
}
|
||||
|
||||
await saveLinks(note);
|
||||
|
||||
await protectNoteRevisions(note);
|
||||
}
|
||||
|
||||
@@ -374,19 +383,12 @@ async function deleteNote(branch) {
|
||||
async function cleanupDeletedNotes() {
|
||||
const cutoffDate = new Date(new Date().getTime() - 48 * 3600 * 1000);
|
||||
|
||||
const notesForCleanup = await repository.getEntities("SELECT * FROM notes WHERE isDeleted = 1 AND content != '' AND dateModified <= ?", [dateUtils.dateStr(cutoffDate)]);
|
||||
// it's better to not use repository for this because it will complain about saving protected notes
|
||||
// out of protected session
|
||||
|
||||
for (const note of notesForCleanup) {
|
||||
note.content = null;
|
||||
await note.save();
|
||||
}
|
||||
await sql.execute("UPDATE notes SET content = NULL WHERE isDeleted = 1 AND content IS NOT NULL AND dateModified <= ?", [dateUtils.dateStr(cutoffDate)]);
|
||||
|
||||
const notesRevisionsForCleanup = await repository.getEntities("SELECT note_revisions.* FROM notes JOIN note_revisions USING(noteId) WHERE notes.isDeleted = 1 AND note_revisions.content != '' AND notes.dateModified <= ?", [dateUtils.dateStr(cutoffDate)]);
|
||||
|
||||
for (const noteRevision of notesRevisionsForCleanup) {
|
||||
noteRevision.content = null;
|
||||
await noteRevision.save();
|
||||
}
|
||||
await sql.execute("UPDATE note_revisions SET content = NULL WHERE note_revisions.content IS NOT NULL AND noteId IN (SELECT noteId FROM notes WHERE isDeleted = 1 AND notes.dateModified <= ?)", [dateUtils.dateStr(cutoffDate)]);
|
||||
}
|
||||
|
||||
// first cleanup kickoff 5 minutes after startup
|
||||
|
||||
@@ -118,6 +118,15 @@ function escapeRegExp(str) {
|
||||
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
|
||||
}
|
||||
|
||||
function crash() {
|
||||
if (isElectron()) {
|
||||
require('electron').app.exit(1);
|
||||
}
|
||||
else {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
randomSecureToken,
|
||||
randomString,
|
||||
@@ -137,5 +146,6 @@ module.exports = {
|
||||
stripTags,
|
||||
intersection,
|
||||
union,
|
||||
escapeRegExp
|
||||
escapeRegExp,
|
||||
crash
|
||||
};
|
||||
@@ -22,5 +22,7 @@
|
||||
|
||||
<br/><br/>
|
||||
|
||||
<img id="note-detail-image-view" />
|
||||
<div id="note-detail-image-wrapper">
|
||||
<img id="note-detail-image-view" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -7,9 +7,9 @@
|
||||
</button>
|
||||
|
||||
<button type="button"
|
||||
class="btn icon-button floating-button jam jam-align-center"
|
||||
title="Re-center view on notes"
|
||||
id="relation-map-center" style="right: 100px;"></button>
|
||||
class="btn icon-button floating-button jam jam-crop"
|
||||
title="Reset pan & zoom to initial coordinates and magnification"
|
||||
id="relation-map-reset-pan-zoom" style="right: 100px;"></button>
|
||||
|
||||
<div class="btn-group floating-button" style="right: 20px;">
|
||||
<button type="button"
|
||||
|
||||
@@ -48,6 +48,10 @@
|
||||
<input class="form-control relation-target-note-id"
|
||||
placeholder="search for note by its name"
|
||||
data-bind="noteAutocomplete, value: relationValue, valueUpdate: 'blur', event: { blur: $parent.attributeChanged }">
|
||||
|
||||
<div style="color: red" data-bind="if: $parent.isEmptyRelationTarget($index())">Relation target note
|
||||
can't be empty.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-bind="visible: type == 'label-definition'">
|
||||
@@ -72,9 +76,9 @@
|
||||
</label>
|
||||
<br/>
|
||||
<label>
|
||||
Mirror relation:
|
||||
Inverse relation:
|
||||
|
||||
<input type="text" value="true" class="attribute-name" data-bind="value: relationDefinition.mirrorRelation"/>
|
||||
<input type="text" value="true" class="attribute-name" data-bind="value: relationDefinition.inverseRelation"/>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -10,10 +10,6 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="prompt-dialog-answer" id="prompt-dialog-question"></label>
|
||||
<input type="text" class="form-control" id="prompt-dialog-answer" placeholder="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary btn-sm" id="prompt-dialog-ok-button">OK <kbd>enter</kbd></button>
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
<a class="dropdown-item show-attributes-button"><kbd>Alt+A</kbd> Attributes</a>
|
||||
<a class="dropdown-item" id="show-source-button" data-bind="css: { disabled: type() != 'text' }">HTML source</a>
|
||||
<a class="dropdown-item" id="upload-file-button">Upload file</a>
|
||||
<a class="dropdown-item" id="export-note-to-markdown-button" data-bind="css: { disabled: type() != 'text' }">Export as markdown</a>
|
||||
<a class="dropdown-item" id="export-note-to-markdown-button" data-bind="css: { disabled: type() != 'text' && type() != 'code' }">Export as markdown</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user