Compare commits

..

35 Commits

Author SHA1 Message Date
azivner b036852b2d release 0.26.0-beta 2018-12-18 22:49:48 +01:00
azivner f693dc31e8 mention note hoisting in the readme 2018-12-18 20:50:03 +01:00
azivner af8f5b11b8 better error handling in request 2018-12-18 20:39:56 +01:00
azivner 8ca943f87b fix hoistedNoteId option init 2018-12-18 20:34:24 +01:00
azivner 35cc9da626 note about system proxy in the settings 2018-12-17 22:58:53 +01:00
azivner 44cc86a1a5 added support for explicit proxy 2018-12-17 22:54:54 +01:00
azivner 7c3bbfd45e using http service for sync setup as well, removed request(-promise) dependency 2018-12-17 22:12:26 +01:00
azivner a1f939e3a0 refactoring of network code into separate service 2018-12-17 21:34:02 +01:00
azivner 4ca7886090 uniform requests through electron's net and node http/https to support different proxy setups 2018-12-16 23:30:53 +01:00
azivner b942163748 sync with electron net API (for system proxy support) - working, but WIP 2018-12-16 21:19:12 +01:00
azivner 5b6d15acb3 note path might not always include root explicitly 2018-12-16 10:41:47 +01:00
azivner 841420360e quick unhoist link/button 2018-12-15 20:29:08 +01:00
azivner a680bb4612 unhoist note when activating note outside of the hoisted note subtree 2018-12-13 23:28:48 +01:00
azivner f763e13996 fix searching without hoisting 2018-12-13 21:43:13 +01:00
azivner e73e1262ae Merge branch 'stable' into hoist 2018-12-13 21:18:44 +01:00
azivner b774d56cf7 note titles in jump to note start from hoisted note instead of root 2018-12-13 21:18:35 +01:00
azivner bae55f2e8b release 0.25.2 2018-12-12 21:30:06 +01:00
azivner 6c16cdb011 recent notes and jump to note are filtered by hoisted note 2018-12-12 21:28:38 +01:00
azivner 86fcbb0354 keyboard shortcut for hoisting, hoisted note is always expanded and has arrow icon 2018-12-12 20:54:58 +01:00
azivner 17d030e800 continuing work on hoisting notes 2018-12-12 20:39:56 +01:00
azivner 6fbf28b30d hoisting notes WIP 2018-12-11 21:53:56 +01:00
azivner 8171b68b18 electron 4.0 beta 9, context menu padding 2018-12-11 19:50:31 +01:00
azivner d30e3a4052 bind standard shortcuts on both command and ctrl modifiers, #254 2018-12-10 20:44:50 +01:00
azivner 87aab7ac5b fix attributes leaking into imported notes, #241 2018-12-10 20:29:49 +01:00
azivner c46f5805b1 removed the note from readme about missing mac build 2018-12-09 21:50:29 +01:00
azivner dd6a29dfc0 no ia32 release upload 2018-12-09 21:34:08 +01:00
azivner a0933c7f10 release 0.25.1-beta 2018-12-09 21:22:33 +01:00
azivner 492adc2909 fix broken drag & drop, closes 252 2018-12-09 21:18:53 +01:00
azivner 90a52f57b5 recent notes route accepts slash in the param (problem with nginx etc.) 2018-12-09 14:30:19 +01:00
azivner a1469854e8 Allow Trilium to work from non-root document paths, closes #253 2018-12-09 13:37:28 +01:00
azivner e2c2993255 changes in build - linux-x64 also uses prebuilt binaries compatible with glibc 2.27 so e.g. ubuntu 18.04 works again. Also dropped build for linux-ia32 2018-12-09 11:41:32 +01:00
azivner 270d63b265 deactivated cut to note functionality in ckeditor build 2018-12-08 10:54:10 +01:00
azivner 1d004be91d ckeditor upgrade to 11.2.0 2018-12-08 09:48:36 +01:00
azivner b8a74c727e copy paste, undo, redo, select all shortcuts supported on mac build 2018-12-02 14:04:53 +01:00
azivner c87b592277 fix in the release script 2018-12-02 08:42:11 +01:00
54 changed files with 521 additions and 286 deletions
+3 -3
View File
@@ -10,7 +10,7 @@ Trilium Notes is a hierarchical note taking application with focus on building l
* Notes can be arranged into arbitrarily deep tree. Single note can be placed into multiple places in the tree (see [cloning](https://github.com/zadam/trilium/wiki/Cloning-notes))
* Rich WYSIWYG note editing including e.g. tables and images with markdown [autoformat](https://github.com/zadam/trilium/wiki/Text-editor#autoformat)
* Support for editing [notes with source code](https://github.com/zadam/trilium/wiki/Code-notes), including syntax highlighting
* Fast and easy [navigation between notes](https://github.com/zadam/trilium/wiki/Note-navigation)
* Fast and easy [navigation between notes](https://github.com/zadam/trilium/wiki/Note-navigation), full text search and [note hoisting](https://github.com/zadam/trilium/wiki/Note-hoisting)
* Seamless [note versioning](https://github.com/zadam/trilium/wiki/Note-revisions)
* Note [attributes](https://github.com/zadam/trilium/wiki/Attributes) can be used for note organization, querying and advanced [scripting](https://github.com/zadam/trilium/wiki/Scripts)
* [Synchronization](https://github.com/zadam/trilium/wiki/Synchronization) with self-hosted sync server
@@ -23,9 +23,9 @@ Trilium Notes is a hierarchical note taking application with focus on building l
## Builds
Trilium is provided as either desktop application (Linux, Windows) or web application hosted on your server (Linux). Mac is planned but not available at the moment.
Trilium is provided as either desktop application (Linux, Windows, Mac) or web application hosted on your server (Linux).
* If you want to use Trilium on the desktop, download binary release for your platform (currently Linux and Windows are supported) from [latest release](https://github.com/zadam/trilium/releases/latest), unzip the package and run ```trilium``` executable.
* If you want to use Trilium on the desktop, download binary release for your platform from [latest release](https://github.com/zadam/trilium/releases/latest), unzip the package and run ```trilium``` executable.
* If you want to install Trilium on server, follow [this page](https://github.com/zadam/trilium/wiki/Server-installation).
* Currently only recent Chrome and Firefox are supported (tested) browsers.
-19
View File
@@ -1,19 +0,0 @@
#!/usr/bin/env bash
BUILD_DIR=./dist/trilium-linux-ia32
rm -rf $BUILD_DIR
echo "Rebuilding binaries for linux-ia32"
./node_modules/.bin/electron-rebuild --arch=ia32
./node_modules/.bin/electron-packager . --out=dist --executable-name=trilium --platform=linux --arch=ia32 --overwrite
mv "./dist/Trilium Notes-linux-ia32" $BUILD_DIR
rm -r $BUILD_DIR/resources/app/bin/deps
# removing software WebGL binaries because they are pretty huge and not necessary
rm -r $BUILD_DIR/swiftshader
echo "Packaging linux ia32 electron distribution..."
VERSION=`jq -r ".version" package.json`
7z a $BUILD_DIR-${VERSION}.7z $BUILD_DIR
+4
View File
@@ -11,6 +11,10 @@ echo "Rebuilding binaries for linux-x64"
mv "./dist/Trilium Notes-linux-x64" $BUILD_DIR
rm -r "$BUILD_DIR/resources/app/node_modules/sqlite3/lib/binding/*"
cp -r bin/deps/linux-x64/sqlite/electron* "$BUILD_DIR/resources/app/node_modules/sqlite3/lib/binding/"
rm -r $BUILD_DIR/resources/app/bin/deps
# removing software WebGL binaries because they are pretty huge and not necessary
rm -r $BUILD_DIR/swiftshader
+4 -4
View File
@@ -14,10 +14,10 @@ MAC_RES_DIR=$BUILD_DIR/Trilium\ Notes.app/Contents/Resources/app
rm -r "$MAC_RES_DIR/node_modules/sqlite3/lib/binding/*"
cp -r bin/deps/mac/sqlite/* "$MAC_RES_DIR/node_modules/sqlite3/lib/binding/"
cp bin/deps/mac/image/cjpeg "$MAC_RES_DIR/node_modules/mozjpeg/vendor/"
cp bin/deps/mac/image/pngquant "$MAC_RES_DIR/node_modules/pngquant-bin/vendor/"
cp bin/deps/mac/image/gifsicle "$MAC_RES_DIR/node_modules/giflossy/vendor/"
cp -r bin/deps/mac-x64/sqlite/* "$MAC_RES_DIR/node_modules/sqlite3/lib/binding/"
cp bin/deps/mac-x64/image/cjpeg "$MAC_RES_DIR/node_modules/mozjpeg/vendor/"
cp bin/deps/mac-x64/image/pngquant "$MAC_RES_DIR/node_modules/pngquant-bin/vendor/"
cp bin/deps/mac-x64/image/gifsicle "$MAC_RES_DIR/node_modules/giflossy/vendor/"
rm -r "$MAC_RES_DIR/bin/deps"
+5 -1
View File
@@ -24,10 +24,14 @@ cp -r ../../config-sample.ini ./
rm -r ./node_modules/electron*
rm -r ./node_modules/sqlite3/lib/binding/*
cp -r ../../bin/deps/linux-x64/sqlite/node* ./node_modules/sqlite3/lib/binding/
printf "#/bin/sh\n./node/bin/node src/www" > trilium.sh
chmod 755 trilium.sh
cd ..
VERSION=`jq -r ".version" package.json`
VERSION=`jq -r ".version" ../package.json`
7z a trilium-linux-x64-server-${VERSION}.7z trilium-linux-x64-server
+4 -4
View File
@@ -11,10 +11,10 @@ echo "Copying required windows binaries"
WIN_RES_DIR=$BUILD_DIR/resources/app
cp -r bin/deps/win/sqlite/* $WIN_RES_DIR/node_modules/sqlite3/lib/binding/
cp bin/deps/win/image/cjpeg.exe $WIN_RES_DIR/node_modules/mozjpeg/vendor/
cp bin/deps/win/image/pngquant.exe $WIN_RES_DIR/node_modules/pngquant-bin/vendor/
cp bin/deps/win/image/gifsicle.exe $WIN_RES_DIR/node_modules/giflossy/vendor/
cp -r bin/deps/win-x64/sqlite/* $WIN_RES_DIR/node_modules/sqlite3/lib/binding/
cp bin/deps/win-x64/image/cjpeg.exe $WIN_RES_DIR/node_modules/mozjpeg/vendor/
cp bin/deps/win-x64/image/pngquant.exe $WIN_RES_DIR/node_modules/pngquant-bin/vendor/
cp bin/deps/win-x64/image/gifsicle.exe $WIN_RES_DIR/node_modules/giflossy/vendor/
rm -r $WIN_RES_DIR/bin/deps
# removing software WebGL binaries because they are pretty huge and not necessary
+2 -2
View File
@@ -8,11 +8,11 @@ echo "Deleting existing builds"
rm -r dist/*
bin/build-linux-ia32.sh
bin/build-win-x64.sh
bin/build-mac-x64.sh
# building X64 linux as the last so electron-rebuild will prepare X64 binaries for local development
bin/build-linux-x64.sh
bin/build-server.sh
Binary file not shown.
Binary file not shown.
+1 -11
View File
@@ -43,7 +43,6 @@ git push origin $TAG
bin/build.sh
LINUX_X64_BUILD=trilium-linux-x64-$VERSION.7z
LINUX_IA32_BUILD=trilium-linux-ia32-$VERSION.7z
WINDOWS_X64_BUILD=trilium-windows-x64-$VERSION.7z
MAC_X64_BUILD=trilium-mac-x64-$VERSION.7z
SERVER_BUILD=trilium-linux-x64-server-$VERSION.7z
@@ -67,13 +66,6 @@ github-release upload \
--name "$LINUX_X64_BUILD" \
--file "dist/$LINUX_X64_BUILD"
echo "Uploading linux ia32 build"
github-release upload \
--tag $TAG \
--name "$LINUX_IA32_BUILD" \
--file "dist/$LINUX_IA32_BUILD"
echo "Uploading windows x64 build"
github-release upload \
@@ -88,9 +80,7 @@ github-release upload \
--name "$MAC_X64_BUILD" \
--file "dist/$MAC_X64_BUILD"
echo "Packaging server version"
bin/build-server.sh $VERSION
echo "Uploading linux x64 server build"
github-release upload \
--tag $TAG \
@@ -0,0 +1,2 @@
INSERT INTO options (name, value, dateCreated, dateModified, isSynced)
VALUES ('hoistedNoteId', 'root', '2018-12-11T18:31:00.874Z', '2018-12-11T18:31:00.874Z', 0);
+64 -114
View File
@@ -1,6 +1,6 @@
{
"name": "trilium",
"version": "0.24.5",
"version": "0.25.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -671,9 +671,9 @@
"integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0="
},
"asar": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/asar/-/asar-0.14.5.tgz",
"integrity": "sha512-2Di/TnY1sridHFKMFgxBh0Wk0gVxSZN4qQhRhjJn3UywZAvP5MHI0RNVSkpzmJ+n6t0BC8w/+1257wtSgQ3Kdg==",
"version": "0.14.6",
"resolved": "https://registry.npmjs.org/asar/-/asar-0.14.6.tgz",
"integrity": "sha512-ZqybKcdO5At6y3ge2RHxVImc6Eltb2t3sxT7lk4T4zjZBSFUuIGCIZY6f41dCjlvJSizN5QPRr8YTgMhpgBjLg==",
"dev": true,
"requires": {
"chromium-pickle-js": "^0.2.0",
@@ -682,7 +682,7 @@
"glob": "^6.0.4",
"minimatch": "^3.0.3",
"mkdirp": "^0.5.0",
"mksnapshot": "^0.3.0",
"mksnapshot": "^0.3.4",
"tmp": "0.0.28"
},
"dependencies": {
@@ -969,7 +969,8 @@
"bluebird": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz",
"integrity": "sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg=="
"integrity": "sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg==",
"dev": true
},
"bmp-js": {
"version": "0.1.0",
@@ -2034,7 +2035,7 @@
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
"dev": true
}
@@ -2374,9 +2375,9 @@
"integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ=="
},
"electron": {
"version": "4.0.0-beta.8",
"resolved": "https://registry.npmjs.org/electron/-/electron-4.0.0-beta.8.tgz",
"integrity": "sha512-zo0Tf3t1uary5O9Skdlo37axs+vvh9CPC+MkvZnnX3MA/3kWIc+I6jN+AL4bHUdxsMVtjbY8KnusjnCjKSb0sg==",
"version": "4.0.0-beta.9",
"resolved": "https://registry.npmjs.org/electron/-/electron-4.0.0-beta.9.tgz",
"integrity": "sha512-BPFkN4BFQy88x2ZHVmzI03i1mUgaQF/uROPb/TlGB/WNAD3v2OvA9Ak9yZ5ADNnwhlR28DtUGs/MuZfDZHZBoQ==",
"dev": true,
"requires": {
"@types/node": "^8.0.24",
@@ -2526,10 +2527,20 @@
}
}
},
"electron-notarize": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-0.0.5.tgz",
"integrity": "sha512-YzrqZ6RDQ7Wt2RWlxzRoQUuxnTeXrfp7laH7XKcmQqrZ6GaAr50DMPvFMpqDKdrZSHSbcgZgB7ktIQbjvITmCQ==",
"dev": true,
"requires": {
"debug": "^4.1.0",
"fs-extra": "^7.0.0"
}
},
"electron-osx-sign": {
"version": "0.4.10",
"resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.4.10.tgz",
"integrity": "sha1-vk87ibKnWh3F8eckkIGrKSnKOiY=",
"version": "0.4.11",
"resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.4.11.tgz",
"integrity": "sha512-VVd40nrnVqymvFrY9ZkOYgHJOvexHHYTR3di/SN+mjJ0OWhR1I8BRVj3U+Yamw6hnkZZNKZp52rqL5EFAAPFkQ==",
"dev": true,
"requires": {
"bluebird": "^3.5.0",
@@ -2537,7 +2548,7 @@
"debug": "^2.6.8",
"isbinaryfile": "^3.0.2",
"minimist": "^1.2.0",
"plist": "^2.1.0"
"plist": "^3.0.1"
},
"dependencies": {
"debug": {
@@ -2552,69 +2563,50 @@
}
},
"electron-packager": {
"version": "12.2.0",
"resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-12.2.0.tgz",
"integrity": "sha512-T5W/FIK4VXhYIOWxkehmz6zXt2S/sA9JZ3AL+/jeKCicQY6QVQ0K8B7W801L+GPTwbgTPycHjO+iqEf1BhZ+Iw==",
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-13.0.1.tgz",
"integrity": "sha512-fXfldaZ1wihpPaMTSGMxvCeETJwVArlnMmKafVXLJbbZwS+WTjY4iL7ju9WMQ0LNGuiiIwSMCQFxt5iA087mqg==",
"dev": true,
"requires": {
"asar": "^0.14.0",
"debug": "^3.0.0",
"debug": "^4.0.1",
"electron-download": "^4.1.1",
"electron-osx-sign": "^0.4.1",
"electron-notarize": "^0.0.5",
"electron-osx-sign": "^0.4.11",
"extract-zip": "^1.0.3",
"fs-extra": "^5.0.0",
"fs-extra": "^7.0.0",
"galactus": "^0.2.1",
"get-package-info": "^1.0.0",
"nodeify": "^1.0.1",
"parse-author": "^2.0.0",
"pify": "^3.0.0",
"plist": "^2.0.0",
"pify": "^4.0.0",
"plist": "^3.0.0",
"rcedit": "^1.0.0",
"resolve": "^1.1.6",
"sanitize-filename": "^1.6.0",
"semver": "^5.3.0",
"yargs-parser": "^10.0.0"
"yargs-parser": "^11.0.0"
},
"dependencies": {
"camelcase": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
"integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz",
"integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==",
"dev": true
},
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
},
"fs-extra": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz",
"integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
}
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
"pify": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"dev": true
},
"yargs-parser": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz",
"integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
"integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==",
"dev": true,
"requires": {
"camelcase": "^4.1.0"
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
@@ -4305,7 +4297,7 @@
},
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
},
@@ -5608,12 +5600,6 @@
"resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz",
"integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU="
},
"is-promise": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-1.0.1.tgz",
"integrity": "sha1-MVc3YcBX4zwukaq56W2gjO++duU=",
"dev": true
},
"is-proto-prop": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-proto-prop/-/is-proto-prop-1.0.1.tgz",
@@ -6542,19 +6528,19 @@
"dev": true
},
"mksnapshot": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/mksnapshot/-/mksnapshot-0.3.1.tgz",
"integrity": "sha1-JQHAVldDbXQs6Vik/5LHfkDdN+Y=",
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/mksnapshot/-/mksnapshot-0.3.4.tgz",
"integrity": "sha512-FgUTiWiY+35LgL95P/MDYrBuQO5o0s3MmaWKX6ZJWoX4vMOY9vPsAv763l1OSSelL9jPsBQ/wf4bzfqTLNPSFg==",
"dev": true,
"requires": {
"decompress-zip": "0.3.0",
"fs-extra": "0.26.7",
"request": "^2.79.0"
"request": "2.x"
},
"dependencies": {
"fs-extra": {
"version": "0.26.7",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz",
"resolved": "http://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz",
"integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=",
"dev": true,
"requires": {
@@ -6582,9 +6568,9 @@
"integrity": "sha1-mi3sg4Bvuy2XXyK+7IWcoms5OqE="
},
"moment": {
"version": "2.22.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
"integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y="
"version": "2.23.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.23.0.tgz",
"integrity": "sha512-3IE39bHVqFbWWaPOMHZF98Q9c3LDKGTmypMiTM2QygGXXElkFWIH7GxfmlwmY2vwa+wmNsoYZmG2iusf1ZjJoA=="
},
"mozjpeg": {
"version": "6.0.1",
@@ -7286,16 +7272,6 @@
"resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz",
"integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8="
},
"nodeify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/nodeify/-/nodeify-1.0.1.tgz",
"integrity": "sha1-ZKtpp7268DzhB7TwM1yHwLnpGx0=",
"dev": true,
"requires": {
"is-promise": "~1.0.0",
"promise": "~1.3.0"
}
},
"nopt": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
@@ -8041,26 +8017,20 @@
"integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q=="
},
"plist": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/plist/-/plist-2.1.0.tgz",
"integrity": "sha1-V8zbeggh3yGDEhejytVOPhRqECU=",
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.1.tgz",
"integrity": "sha512-GpgvHHocGRyQm74b6FWEZZVRroHKE1I0/BTjAmySaohK+cUn+hZpbqXkc3KWgW3gQYkqcQej35FohcT0FRlkRQ==",
"dev": true,
"requires": {
"base64-js": "1.2.0",
"xmlbuilder": "8.2.2",
"base64-js": "^1.2.3",
"xmlbuilder": "^9.0.7",
"xmldom": "0.1.x"
},
"dependencies": {
"base64-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.0.tgz",
"integrity": "sha1-o5mS1yNYSBGYK+XikLtqU9hnAPE=",
"dev": true
},
"xmlbuilder": {
"version": "8.2.2",
"resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz",
"integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz",
"integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==",
"dev": true
}
}
@@ -8490,15 +8460,6 @@
}
}
},
"promise": {
"version": "1.3.0",
"resolved": "http://registry.npmjs.org/promise/-/promise-1.3.0.tgz",
"integrity": "sha1-5cyaTIJ45GZP/twBx9qEhCsEAXU=",
"dev": true,
"requires": {
"is-promise": "~1"
}
},
"proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
@@ -8826,17 +8787,6 @@
}
}
},
"request-promise": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.2.tgz",
"integrity": "sha1-0epG1lSm7k+O5qT+oQGMIpEZBLQ=",
"requires": {
"bluebird": "^3.5.0",
"request-promise-core": "1.1.1",
"stealthy-require": "^1.1.0",
"tough-cookie": ">=2.3.3"
}
},
"request-promise-core": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz",
+4 -6
View File
@@ -2,7 +2,7 @@
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.25.0-beta",
"version": "0.26.0-beta",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -45,13 +45,11 @@
"ini": "1.3.5",
"jimp": "0.6.0",
"mime-types": "^2.1.21",
"moment": "2.22.2",
"moment": "2.23.0",
"multer": "1.4.1",
"open": "0.0.5",
"rand-token": "0.4.0",
"rcedit": "1.1.1",
"request": "2.88.0",
"request-promise": "4.2.2",
"rimraf": "2.6.2",
"sanitize-filename": "1.6.1",
"sax": "^1.2.4",
@@ -67,9 +65,9 @@
},
"devDependencies": {
"devtron": "1.4.0",
"electron": "4.0.0-beta.8",
"electron": "4.0.0-beta.9",
"electron-compile": "6.4.3",
"electron-packager": "12.2.0",
"electron-packager": "13.0.1",
"electron-rebuild": "1.8.2",
"lorem-ipsum": "1.0.6",
"tape": "4.9.1",
+26 -1
View File
@@ -32,6 +32,7 @@ import tooltip from './tooltip.js';
import bundle from "./bundle.js";
import treeCache from "./tree_cache.js";
import libraryLoader from "./library_loader.js";
import hoistedNoteService from './hoisted_note.js';
// required for CKEditor image upload plugin
window.glob.getCurrentNode = treeService.getCurrentNode;
@@ -84,6 +85,8 @@ $(document).on("click", "button[data-help-page]", e => {
$("#logout-button").toggle(!utils.isElectron());
$("#tree").on("click", ".unhoist-button", hoistedNoteService.unhoist);
if (utils.isElectron()) {
require('electron').ipcRenderer.on('create-day-sub-note', async function(event, parentNoteId) {
// this might occur when day note had to be created
@@ -104,6 +107,28 @@ if (utils.isElectron()) {
});
}
function exec(cmd) {
document.execCommand(cmd);
return false;
}
if (utils.isElectron() && utils.isMac()) {
utils.bindShortcut('ctrl+c', () => exec("copy"));
utils.bindShortcut('ctrl+v', () => exec('paste'));
utils.bindShortcut('ctrl+x', () => exec('cut'));
utils.bindShortcut('ctrl+a', () => exec('selectAll'));
utils.bindShortcut('ctrl+z', () => exec('undo'));
utils.bindShortcut('ctrl+y', () => exec('redo'));
utils.bindShortcut('meta+c', () => exec("copy"));
utils.bindShortcut('meta+v', () => exec('paste'));
utils.bindShortcut('meta+x', () => exec('cut'));
utils.bindShortcut('meta+a', () => exec('selectAll'));
utils.bindShortcut('meta+z', () => exec('undo'));
utils.bindShortcut('meta+y', () => exec('redo'));
}
$("#export-note-button").click(function () {
if ($(this).hasClass("disabled")) {
return;
@@ -118,4 +143,4 @@ entrypoints.registerEntrypoints();
tooltip.setupTooltip();
bundle.executeStartupBundles();
bundle.executeStartupBundles();
@@ -4,6 +4,10 @@ function initContextMenu(event, contextMenuItems, selectContextMenuItem) {
$contextMenuContainer.empty();
for (const item of contextMenuItems) {
if (item.hidden) {
continue;
}
if (item.title === '----') {
$contextMenuContainer.append($("<div>").addClass("dropdown-divider"));
} else {
@@ -4,6 +4,7 @@ import treeChangesService from './branches.js';
const dragAndDropSetup = {
autoExpandMS: 600,
dragStart: (node, data) => {
// don't allow dragging root node
if (node.data.noteId === 'root') {
return false;
}
@@ -22,11 +23,7 @@ const dragAndDropSetup = {
// Return false to cancel dragging of node.
return true;
},
dragEnter: (node, data) => {
// we don't allow moving root to any other location in the tree
// we allow it to be placed on the relation map though, that's handled in a different drop handler
return node.data.noteId === 'root';
}, // allow drop on any node
dragEnter: (node, data) => true, // allow drop on any node
dragDrop: (node, data) => {
// This function MUST be defined to enable dropping of items on the tree.
// data.hitMode is 'before', 'after', or 'over'.
@@ -95,7 +95,7 @@ function registerEntrypoints() {
}
});
$(document).bind('keydown', 'ctrl+f', () => {
function openInPageSearch() {
if (utils.isElectron()) {
const $searchWindowWebview = $(".electron-in-page-search-window");
$searchWindowWebview.show();
@@ -113,7 +113,13 @@ function registerEntrypoints() {
return false;
}
});
}
utils.bindShortcut('ctrl+f', openInPageSearch);
if (utils.isMac()) {
utils.bindShortcut('meta+f', openInPageSearch);
}
// FIXME: do we really need these at this point?
utils.bindShortcut("ctrl+shift+up", () => {
@@ -0,0 +1,33 @@
import optionsInit from './options_init.js';
import server from "./server.js";
import tree from "./tree.js";
let hoistedNoteId;
optionsInit.optionsReady.then(options => {
hoistedNoteId = options['hoistedNoteId'];
});
async function getHoistedNoteId() {
await optionsInit.optionsReady;
return hoistedNoteId;
}
async function setHoistedNoteId(noteId) {
hoistedNoteId = noteId;
await server.put('options/hoistedNoteId/' + noteId);
await tree.reload();
}
async function unhoist() {
await setHoistedNoteId('root');
}
export default {
getHoistedNoteId,
setHoistedNoteId,
unhoist
}
+32 -8
View File
@@ -14,6 +14,8 @@ import treeBuilder from "./tree_builder.js";
import treeKeyBindings from "./tree_keybindings.js";
import Branch from '../entities/branch.js';
import NoteShort from '../entities/note_short.js';
import hoistedNoteService from '../services/hoisted_note.js';
import confirmDialog from "../dialogs/confirm.js";
const $tree = $("#tree");
const $createTopLevelNoteButton = $("#create-top-level-note-button");
@@ -88,10 +90,11 @@ async function expandToNote(notePath, expandOpts) {
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
let parentNoteId = 'none';
let parentNoteId = null;
for (const childNoteId of runPath) {
const node = getNodesByNoteId(childNoteId).find(node => node.data.parentNoteId === parentNoteId);
// for first node (!parentNoteId) it doesn't matter which node is found
const node = getNodesByNoteId(childNoteId).find(node => !parentNoteId || node.data.parentNoteId === parentNoteId);
if (!node) {
console.error(`Can't find node for noteId=${childNoteId} with parentNoteId=${parentNoteId}`);
@@ -111,6 +114,17 @@ async function expandToNote(notePath, expandOpts) {
async function activateNote(notePath, newNote) {
utils.assertArguments(notePath);
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
if (hoistedNoteId !== 'root' && !notePath.includes(hoistedNoteId)) {
if (!await confirmDialog.confirm("Requested note is outside of hoisted note subtree. Do you want to unhoist?")) {
return;
}
// unhoist so we can activate the note
await hoistedNoteService.unhoist();
}
if (glob.activeDialog) {
glob.activeDialog.modal('hide');
}
@@ -143,6 +157,8 @@ async function getRunPath(notePath) {
path.push('root');
}
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
const effectivePath = [];
let childNoteId = null;
let i = 0;
@@ -195,13 +211,12 @@ async function getRunPath(notePath) {
}
}
if (parentNoteId === 'none') {
effectivePath.push(parentNoteId);
childNoteId = parentNoteId;
if (parentNoteId === hoistedNoteId) {
break;
}
else {
effectivePath.push(parentNoteId);
childNoteId = parentNoteId;
}
}
return effectivePath.reverse();
@@ -402,6 +417,15 @@ function initFancyTree(tree) {
},
clones: {
highlightActiveClones: true
},
renderNode: async function (event, data) {
const node = data.node;
if (node.data.noteId !== 'root' && node.data.noteId === await hoistedNoteService.getHoistedNoteId()) {
const unhoistButton = $('<span>&nbsp; (<a class="unhoist-button">unhoist</a>)</span>');
$(node.span).append(unhoistButton);
}
}
});
@@ -545,7 +569,7 @@ async function createNote(node, parentNoteId, target, isProtected, saveSelection
branchId: branchEntity.branchId,
isProtected: isProtected,
extraClasses: await treeBuilder.getExtraClasses(noteEntity),
icon: treeBuilder.getIcon(noteEntity)
icon: await treeBuilder.getIcon(noteEntity)
};
if (target === 'after') {
@@ -4,13 +4,26 @@ import Branch from "../entities/branch.js";
import server from "./server.js";
import treeCache from "./tree_cache.js";
import messagingService from "./messaging.js";
import hoistedNoteService from "./hoisted_note.js";
async function prepareTree(noteRows, branchRows, relations) {
utils.assertArguments(noteRows, branchRows, relations);
treeCache.load(noteRows, branchRows, relations);
return [ await prepareNode(await treeCache.getBranch('root')) ];
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
let hoistedBranch;
if (hoistedNoteId === 'root') {
hoistedBranch = await treeCache.getBranch('root');
}
else {
const hoistedNote = await treeCache.getNote(hoistedNoteId);
hoistedBranch = (await hoistedNote.getBranches())[0];
}
return [ await prepareNode(hoistedBranch) ];
}
async function prepareBranch(note) {
@@ -22,10 +35,15 @@ async function prepareBranch(note) {
}
}
function getIcon(note) {
async function getIcon(note) {
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
if (note.noteId === 'root') {
return "jam jam-chevrons-right";
}
else if (note.noteId === hoistedNoteId) {
return "jam jam-arrow-up";
}
else if (note.type === 'text') {
if (note.hasChildren()) {
return "jam jam-folder";
@@ -57,6 +75,7 @@ function getIcon(note) {
async function prepareNode(branch) {
const note = await branch.getNote();
const title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title;
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
const node = {
noteId: note.noteId,
@@ -65,9 +84,9 @@ async function prepareNode(branch) {
isProtected: note.isProtected,
title: utils.escapeHtml(title),
extraClasses: await getExtraClasses(note),
icon: getIcon(note),
icon: await getIcon(note),
refKey: note.noteId,
expanded: note.type !== 'search' && branch.isExpanded
expanded: (note.type !== 'search' && branch.isExpanded) || hoistedNoteId === note.noteId
};
if (note.hasChildren() || note.type === 'search') {
@@ -135,10 +154,6 @@ async function getExtraClasses(note) {
const extraClasses = [];
if (note.noteId === 'root') {
extraClasses.push("tree-root");
}
if (note.isProtected) {
extraClasses.push("protected");
}
@@ -10,7 +10,7 @@ import exportDialog from '../dialogs/export.js';
import infoService from "./info.js";
import treeCache from "./tree_cache.js";
import syncService from "./sync.js";
import contextMenuService from "./context_menu.js";
import hoistedNoteService from './hoisted_note.js';
const $tree = $("#tree");
@@ -83,6 +83,8 @@ const contextMenuItems = [
{title: "Insert child note <kbd>Ctrl+P</kbd>", cmd: "insertChildNote", uiIcon: "plus"},
{title: "Delete", cmd: "delete", uiIcon: "trash"},
{title: "----"},
{title: "Hoist note <kbd>CTRL-H</kbd>", cmd: "hoist", uiIcon: "arrow-up"},
{title: "Unhoist note <kbd>CTRL-H</kbd>", cmd: "unhoist", uiIcon: "arrow-up"},
{title: "Edit branch prefix <kbd>F2</kbd>", cmd: "editBranchPrefix", uiIcon: "pencil"},
{title: "----"},
{title: "Protect subtree", cmd: "protectSubtree", uiIcon: "shield-check"},
@@ -101,6 +103,16 @@ const contextMenuItems = [
{title: "Sort alphabetically <kbd>Alt+S</kbd>", cmd: "sortAlphabetically", uiIcon: "arrows-v"}
];
function hideItem(cmd, hidden) {
const item = contextMenuItems.find(item => item.cmd === cmd);
if (!item) {
throw new Error(`Command ${cmd} has not been found!`);
}
item.hidden = hidden;
}
function enableItem(cmd, enabled) {
const item = contextMenuItems.find(item => item.cmd === cmd);
@@ -130,6 +142,11 @@ async function getContextMenuItems(event) {
enableItem("export", note.type !== 'search');
enableItem("editBranchPrefix", isNotRoot && parentNote.type !== 'search');
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
hideItem("hoist", note.noteId === hoistedNoteId);
hideItem("unhoist", note.noteId !== hoistedNoteId || !isNotRoot);
// Activate node on right-click
node.setActive();
@@ -194,6 +211,12 @@ function selectContextMenuItem(event, cmd) {
else if (cmd === "sortAlphabetically") {
treeService.sortAlphabetically(node.data.noteId);
}
else if (cmd === "hoist") {
hoistedNoteService.setHoistedNoteId(node.data.noteId);
}
else if (cmd === "unhoist") {
hoistedNoteService.unhoist();
}
else {
messagingService.logError("Unknown command: " + cmd);
}
@@ -4,6 +4,7 @@ import treeChangesService from "./branches.js";
import contextMenuService from "./tree_context_menu.js";
import treeService from "./tree.js";
import editBranchPrefixDialog from "../dialogs/branch_prefix.js";
import hoistedNoteService from "./hoisted_note.js";
const keyBindings = {
"del": node => {
@@ -113,6 +114,18 @@ const keyBindings = {
node.getParent().setActive().then(treeService.clearSelectedNodes);
}
},
"ctrl+h": node => {
hoistedNoteService.getHoistedNoteId().then(hoistedNoteId => {
if (node.data.noteId === hoistedNoteId) {
hoistedNoteService.unhoist();
}
else {
hoistedNoteService.setHoistedNoteId(node.data.noteId);
}
});
return false;
},
// code below shouldn't be necessary normally, however there's some problem with interaction with context menu plugin
// after opening context menu, standard shortcuts don't work, but they are detected here
// so we essentially takeover the standard handling with our implementation.
+5
View File
@@ -46,6 +46,10 @@ function isElectron() {
return window && window.process && window.process.type;
}
function isMac() {
return navigator.platform.indexOf('Mac') > -1;
}
function assertArguments() {
for (const i in arguments) {
if (!arguments[i]) {
@@ -150,6 +154,7 @@ export default {
formatDateTime,
now,
isElectron,
isMac,
assertArguments,
isTopLevelNode,
isRootNode,
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+9 -3
View File
@@ -107,7 +107,7 @@ body {
(it kept growing with content) */
#container:not(.distraction-free-mode) #note-detail-wrapper.protected {
/* DON'T COLLAPSE THE RULES INTO SINGLE ONE, BACKGROUND WON'T DISPLAY */
background: url('/images/shield.svg') no-repeat;
background: url('../images/shield.svg') no-repeat;
background-size: contain;
background-position: center;
}
@@ -531,8 +531,8 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
padding: 20px;
}
.context-menu-container {
font-size: small;
#context-menu-container {
padding: 3px 0 0;
}
#context-menu-container .dropdown-item {
@@ -698,4 +698,10 @@ div[data-notify="container"] {
#export-form .form-check-label {
padding: 2px;
}
.unhoist-button {
text-decoration: underline !important;
color: blue !important;
cursor: pointer !important;
}
+11 -1
View File
@@ -3,6 +3,8 @@
const noteCacheService = require('../../services/note_cache');
const repository = require('../../services/repository');
const log = require('../../services/log');
const utils = require('../../services/utils');
const optionService = require('../../services/options');
async function getAutocomplete(req) {
const query = req.query.query;
@@ -16,7 +18,7 @@ async function getAutocomplete(req) {
results = await getRecentNotes(currentNoteId);
}
else {
results = noteCacheService.findNotes(query);
results = await noteCacheService.findNotes(query);
}
const msTaken = Date.now() - timestampStarted;
@@ -29,6 +31,13 @@ async function getAutocomplete(req) {
}
async function getRecentNotes(currentNoteId) {
let extraCondition = '';
const hoistedNoteId = await optionService.getOption('hoistedNoteId');
if (hoistedNoteId !== 'root') {
extraCondition = `AND recent_notes.notePath LIKE '%${utils.sanitizeSql(hoistedNoteId)}%'`;
}
const recentNotes = await repository.getEntities(`
SELECT
recent_notes.*
@@ -39,6 +48,7 @@ async function getRecentNotes(currentNoteId) {
recent_notes.isDeleted = 0
AND branches.isDeleted = 0
AND branches.noteId != ?
${extraCondition}
ORDER BY
dateCreated DESC
LIMIT 200`, [currentNoteId]);
+1 -1
View File
@@ -29,7 +29,7 @@ async function loginSync(req) {
const syncVersion = req.body.syncVersion;
if (syncVersion !== appInfo.syncVersion) {
return [400, { message: 'Non-matching sync versions, local is version ' + appInfo.syncVersion }];
return [400, { message: `Non-matching sync versions, local is version ${appInfo.syncVersion}, remote is ${syncVersion}` }];
}
const documentSecret = await options.getOption('documentSecret');
+1 -1
View File
@@ -6,7 +6,7 @@ const log = require('../../services/log');
// options allowed to be updated directly in options dialog
const ALLOWED_OPTIONS = ['protectedSessionTimeout', 'noteRevisionSnapshotTimeInterval',
'zoomFactor', 'theme', 'syncServerHost', 'syncServerTimeout', 'syncProxy', 'leftPaneMinWidth', 'leftPaneWidthPercent'];
'zoomFactor', 'theme', 'syncServerHost', 'syncServerTimeout', 'syncProxy', 'leftPaneMinWidth', 'leftPaneWidthPercent', 'hoistedNoteId'];
async function getOptions() {
return await optionService.getOptionsMap(ALLOWED_OPTIONS);
+1 -1
View File
@@ -20,7 +20,7 @@ async function searchNotes(req) {
let searchTextResults = null;
if (searchText.trim().length > 0) {
searchTextResults = noteCacheService.findNotes(searchText);
searchTextResults = await noteCacheService.findNotes(searchText);
let fullTextNoteIds = await getFullTextResults(searchText);
+8 -2
View File
@@ -38,6 +38,7 @@ async function getNotes(noteIds) {
async function getRelations(noteIds) {
// we need to fetch both parentNoteId and noteId matches because we can have loaded child
// of which only some of the parents has been loaded.
// also now with note hoisting, it is possible to have the note displayed without its parent chain being loaded
const relations = await sql.getManyRows(`SELECT branchId, noteId AS 'childNoteId', parentNoteId, notePosition FROM branches WHERE isDeleted = 0
AND (parentNoteId IN (???) OR noteId IN (???))`, noteIds);
@@ -50,18 +51,23 @@ async function getRelations(noteIds) {
}
async function getTree() {
const hoistedNoteId = await optionService.getOption('hoistedNoteId');
// we fetch all branches of notes, even if that particular branch isn't visible
// this allows us to e.g. detect and properly display clones
const branches = await sql.getRows(`
WITH RECURSIVE
tree(branchId, noteId, isExpanded) AS (
SELECT branchId, noteId, isExpanded FROM branches WHERE branchId = 'root'
SELECT branchId, noteId, isExpanded FROM branches WHERE noteId = ?
UNION ALL
SELECT branches.branchId, branches.noteId, branches.isExpanded FROM branches
JOIN tree ON branches.parentNoteId = tree.noteId
WHERE tree.isExpanded = 1 AND branches.isDeleted = 0
)
SELECT branches.* FROM tree JOIN branches USING(noteId) WHERE branches.isDeleted = 0 ORDER BY branches.notePosition`);
SELECT branches.* FROM tree JOIN branches USING(noteId) WHERE branches.isDeleted = 0 ORDER BY branches.notePosition`, [hoistedNoteId]);
// we also want root branch in there because all the paths start with root
branches.push(await sql.getRow(`SELECT * FROM branches WHERE branchId = 'root'`));
const noteIds = Array.from(new Set(branches.map(b => b.noteId)));
+4 -2
View File
@@ -151,7 +151,7 @@ function register(app) {
apiRoute(GET, '/api/recent-changes', recentChangesApiRoute.getRecentChanges);
apiRoute(GET, '/api/options', optionsApiRoute.getOptions);
apiRoute(PUT, '/api/options/:name/:value', optionsApiRoute.updateOption);
apiRoute(PUT, '/api/options/:name/:value*', optionsApiRoute.updateOption);
apiRoute(PUT, '/api/options', optionsApiRoute.updateOptions);
apiRoute(POST, '/api/password/change', passwordApiRoute.changePassword);
@@ -169,7 +169,9 @@ function register(app) {
apiRoute(GET, '/api/event-log', eventLogRoute.getEventLog);
apiRoute(PUT, '/api/recent-notes/:branchId/:notePath', recentNotesRoute.addRecentNote);
// * at the end means this will match params containing slash as well
// this is a problem with nginx (and possibly other proxies) which translate escaped slash back to the literal slash character
apiRoute(PUT, '/api/recent-notes/:branchId/:notePath*', recentNotesRoute.addRecentNote);
apiRoute(GET, '/api/app-info', appInfoRoute.getAppInfo);
route(GET, '/api/setup/status', [], setupApiRoute.getStatus, apiResultHandler);
+1 -1
View File
@@ -3,7 +3,7 @@
const build = require('./build');
const packageJson = require('../../package');
const APP_DB_VERSION = 120;
const APP_DB_VERSION = 121;
const SYNC_VERSION = 2;
module.exports = {
+1 -1
View File
@@ -1 +1 @@
module.exports = { buildDate:"2018-12-02T00:30:07+01:00", buildRevision: "350cb52c07668f67c4d9c7700ceb9f91445a1acb" };
module.exports = { buildDate:"2018-12-18T22:49:48+01:00", buildRevision: "f693dc31e82f1822b7393d12b53afdc0d06f5297" };
+19
View File
@@ -0,0 +1,19 @@
const optionService = require('./options');
const sqlInit = require('./sql_init');
const eventService = require('./events');
let hoistedNoteId = 'root';
eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entity}) => {
if (entityName === 'options' && entity.name === 'hoistedNoteId') {
hoistedNoteId = entity.value;
}
});
sqlInit.dbReady.then(async () => {
hoistedNoteId = await optionService.getOption('hoistedNoteId');
});
module.exports = {
getHoistedNoteId: () => hoistedNoteId
};
+5 -1
View File
@@ -54,7 +54,11 @@ async function importEnex(file, parentNote) {
function extractContent(enNote) {
// [] thing is workaround for https://github.com/Leonidas-from-XIV/node-xml2js/issues/484
let content = xmlBuilder.buildObject([enNote]);
content = content.substr(3, content.length - 7).trim();
const endOfFirstTagIndex = content.indexOf('>');
// strip the <0> and </0> tags
content = content.substr(endOfFirstTagIndex + 1, content.length - endOfFirstTagIndex - 5).trim();
// workaround for https://github.com/ckeditor/ckeditor5-list/issues/116
content = content.replace(/<li>\s+<div>/g, "<li>");
+24 -11
View File
@@ -4,6 +4,7 @@ const eventService = require('./events');
const repository = require('./repository');
const protectedSessionService = require('./protected_session');
const utils = require('./utils');
const hoistedNoteService = require('./hoisted_note');
let loaded = false;
let noteTitles = {};
@@ -63,7 +64,7 @@ function highlightResults(results, allTokens) {
}
}
function findNotes(query) {
async function findNotes(query) {
if (!noteTitles || !query.length) {
return [];
}
@@ -72,7 +73,7 @@ function findNotes(query) {
// filtering '/' because it's used as separator
const allTokens = query.trim().toLowerCase().split(" ").filter(token => token !== '/');
const tokens = allTokens.slice();
const results = [];
let results = [];
let noteIds = Object.keys(noteTitles);
@@ -120,6 +121,10 @@ function findNotes(query) {
}
}
if (hoistedNoteService.getHoistedNoteId() !== 'root') {
results = results.filter(res => res.pathArray.includes(hoistedNoteService.getHoistedNoteId()));
}
// sort results by depth of the note. This is based on the assumption that more important results
// are closer to the note root.
results.sort((a, b) => {
@@ -214,21 +219,25 @@ function getNoteTitle(noteId, parentNoteId) {
function getNoteTitleArrayForPath(path) {
const titles = [];
if (path[0] === 'root') {
if (path.length === 1) {
return [ getNoteTitle('root') ];
}
else {
path = path.slice(1);
}
if (path[0] === hoistedNoteService.getHoistedNoteId() && path.length === 1) {
return [ getNoteTitle(hoistedNoteService.getHoistedNoteId()) ];
}
let parentNoteId = 'root';
let hoistedNotePassed = false;
for (const noteId of path) {
const title = getNoteTitle(noteId, parentNoteId);
// start collecting path segment titles only after hoisted note
if (hoistedNotePassed) {
const title = getNoteTitle(noteId, parentNoteId);
titles.push(title);
}
if (noteId === hoistedNoteService.getHoistedNoteId()) {
hoistedNotePassed = true;
}
titles.push(title);
parentNoteId = noteId;
}
@@ -246,6 +255,10 @@ function getSomePath(noteId, path) {
path.push(noteId);
path.reverse();
if (!path.includes(hoistedNoteService.getHoistedNoteId())) {
return false;
}
return path;
}
+1
View File
@@ -31,6 +31,7 @@ async function initSyncedOptions(username, password) {
async function initNotSyncedOptions(initialized, startNotePath = 'root', syncServerHost = '', syncProxy = '') {
await optionService.createOption('startNotePath', startNotePath, false);
await optionService.createOption('hoistedNoteId', 'root', false);
await optionService.createOption('lastDailyBackupDate', dateUtils.nowDate(), false);
await optionService.createOption('lastWeeklyBackupDate', dateUtils.nowDate(), false);
await optionService.createOption('lastMonthlyBackupDate', dateUtils.nowDate(), false);
+4 -3
View File
@@ -94,9 +94,10 @@ async function updateEntity(entity) {
const primaryKey = entity[primaryKeyName];
if (entity.isChanged && (entityName !== 'options' || entity.isSynced)) {
await syncTableService.addEntitySync(entityName, primaryKey);
if (entity.isChanged) {
if (entityName !== 'options' || entity.isSynced) {
await syncTableService.addEntitySync(entityName, primaryKey);
}
if (!cls.isEntityEventsDisabled()) {
const eventPayload = {
+118
View File
@@ -0,0 +1,118 @@
"use strict";
const utils = require('./utils');
const log = require('./log');
const url = require('url');
// this service provides abstraction over node's HTTP/HTTPS and electron net.client APIs
// this allows to support system proxy
function exec(opts) {
const client = getClient(opts);
const parsedTargetUrl = url.parse(opts.url);
return new Promise(async (resolve, reject) => {
try {
const headers = {
Cookie: (opts.cookieJar && opts.cookieJar.header) || "",
'Content-Type': 'application/json'
};
if (opts.auth) {
const token = new Buffer(opts.auth.user + ":" + opts.auth.pass).toString('base64');
headers['Authorization'] = `Basic ${token}`;
}
let host = parsedTargetUrl.hostname;
let protocol = parsedTargetUrl.protocol;
let port = parsedTargetUrl.port;
let path = parsedTargetUrl.path;
if (opts.proxy) {
// see https://stackoverflow.com/questions/3862813/how-can-i-use-an-http-proxy-with-node-js-http-client
const parsedProxyUrl = url.parse(opts.proxy);
protocol = parsedProxyUrl.protocol;
host = parsedProxyUrl.hostname;
port = parsedProxyUrl.port;
path = opts.url;
headers['Host'] = parsedTargetUrl.host; // host also includes port
}
const request = client.request({
method: opts.method,
// url is used by electron net module
url: opts.url,
// 4 fields below are used by http and https node modules
protocol,
host,
port,
path,
timeout: opts.timeout,
headers
});
request.on('response', response => {
if (![200, 201, 204].includes(response.statusCode)) {
reject(generateError(opts, response.statusCode + ' ' + response.statusMessage));
}
if (opts.cookieJar && response.headers['set-cookie']) {
opts.cookieJar.header = response.headers['set-cookie'];
}
let responseStr = '';
response.on('data', chunk => responseStr += chunk);
response.on('end', () => {
try {
const jsonObj = responseStr.trim() ? JSON.parse(responseStr) : null;
resolve(jsonObj);
}
catch (e) {
log.error("Failed to deserialize sync response: " + responseStr);
reject(generateError(opts, e.message));
}
});
});
request.end(opts.body ? JSON.stringify(opts.body) : undefined);
}
catch (e) {
reject(generateError(opts, e.message));
}
})
}
function getClient(opts) {
// it's not clear how to explicitly configure proxy (as opposed to system proxy)
// so in that case we always use node's modules
if (utils.isElectron() && !opts.proxy) {
return require('electron').net;
}
else {
// in case there's explicit proxy then we need to use protocol of the proxy since we're actually
// connecting to the proxy server and not to the end-target server
const {protocol} = url.parse(opts.proxy || opts.url);
if (protocol === 'http:' || protocol === 'https:') {
return require(protocol.substr(0, protocol.length - 1));
}
else {
throw new Error(`Unrecognized protocol "${protocol}"`);
}
}
}
function generateError(opts, message) {
return new Error(`Request to ${opts.method} ${opts.url} failed, error: ${message}`);
}
module.exports = {
exec
};
+12 -25
View File
@@ -1,10 +1,10 @@
const rp = require('request-promise');
const syncService = require('./sync');
const log = require('./log');
const sqlInit = require('./sql_init');
const repository = require('./repository');
const optionService = require('./options');
const syncOptions = require('./sync_options');
const request = require('./request');
async function hasSyncServerSchemaAndSeed() {
const response = await requestToSyncServer('GET', '/api/setup/status');
@@ -37,23 +37,13 @@ async function sendSeedToSyncServer() {
}
async function requestToSyncServer(method, path, body = null) {
const rpOpts = {
uri: await syncOptions.getSyncServerHost() + path,
method: method,
json: true
};
if (body) {
rpOpts.body = body;
}
const syncProxy = await syncOptions.getSyncProxy();
if (syncProxy) {
rpOpts.proxy = syncProxy;
}
return await rp(rpOpts);
return await request.exec({
method,
url: await syncOptions.getSyncServerHost() + path,
body,
proxy: await syncOptions.getSyncProxy(),
timeout: await syncOptions.getSyncTimeout()
});
}
async function setupSyncFromSyncServer(syncServerHost, syncProxy, username, password) {
@@ -68,19 +58,16 @@ async function setupSyncFromSyncServer(syncServerHost, syncProxy, username, pass
log.info("Getting document options from sync server.");
// response is expected to contain documentId and documentSecret options
const options = await rp.get({
uri: syncServerHost + '/api/setup/sync-seed',
const options = await request.exec({
method: 'get',
url: syncServerHost + '/api/setup/sync-seed',
auth: {
'user': username,
'pass': password
},
json: true
proxy: syncProxy
});
if (syncProxy) {
options.proxy = syncProxy;
}
await sqlInit.createDatabaseForSync(options, syncServerHost, syncProxy);
triggerSync();
+17 -27
View File
@@ -1,7 +1,7 @@
"use strict";
const url = require('url');
const log = require('./log');
const rp = require('request-promise');
const sql = require('./sql');
const sqlInit = require('./sql_init');
const optionService = require('./options');
@@ -14,6 +14,7 @@ const appInfo = require('./app_info');
const syncOptions = require('./sync_options');
const syncMutexService = require('./sync_mutex');
const cls = require('./cls');
const request = require('./request');
let proxyToggle = true;
@@ -49,7 +50,7 @@ async function sync() {
catch (e) {
proxyToggle = !proxyToggle;
if (e.message.indexOf('ECONNREFUSED') !== -1) {
if (e.message && e.message.indexOf('ECONNREFUSED') !== -1) {
log.info("No connection to sync server.");
return {
@@ -84,7 +85,7 @@ async function doLogin() {
const documentSecret = await optionService.getOption('documentSecret');
const hash = utils.hmac(documentSecret, timestamp);
const syncContext = { cookieJar: rp.jar() };
const syncContext = { cookieJar: {} };
const resp = await syncRequest(syncContext, 'POST', '/api/login/sync', {
timestamp: timestamp,
@@ -111,6 +112,10 @@ async function pullSync(syncContext) {
const resp = await syncRequest(syncContext, 'GET', changesUri);
stats.outstandingPulls = resp.maxSyncId - lastSyncedPull;
if (stats.outstandingPulls < 0) {
stats.outstandingPulls = 0;
}
const rows = resp.syncs;
if (rows.length === 0) {
@@ -212,30 +217,15 @@ async function checkContentHash(syncContext) {
await contentHashService.checkContentHashes(resp.hashes);
}
async function syncRequest(syncContext, method, uri, body) {
const fullUri = await syncOptions.getSyncServerHost() + uri;
try {
const options = {
method: method,
uri: fullUri,
jar: syncContext.cookieJar,
json: true,
body: body,
timeout: await syncOptions.getSyncTimeout()
};
const syncProxy = await syncOptions.getSyncProxy();
if (syncProxy && proxyToggle) {
options.proxy = syncProxy;
}
return await rp(options);
}
catch (e) {
throw new Error(`Request to ${method} ${fullUri} failed, error: ${e.message}`);
}
async function syncRequest(syncContext, method, requestPath, body) {
return await request.exec({
method,
url: await syncOptions.getSyncServerHost() + requestPath,
cookieJar: syncContext.cookieJar,
timeout: await syncOptions.getSyncTimeout(),
body,
proxy: proxyToggle ? await syncOptions.getSyncProxy() : null
});
}
const primaryKeys = {
+1 -1
View File
@@ -17,6 +17,6 @@ async function get(name) {
module.exports = {
getSyncServerHost: async () => await get('syncServerHost'),
isSyncSetup: async () => !!await get('syncServerHost'),
getSyncTimeout: async () => await get('syncServerTimeout'),
getSyncTimeout: async () => parseInt(await get('syncServerTimeout')),
getSyncProxy: async () => await get('syncProxy')
};
+2
View File
@@ -147,6 +147,8 @@
<div class="form-group">
<label for="sync-proxy">Sync proxy server (optional)</label>
<input class="form-control" id="sync-proxy" placeholder="https://<host>:<port>">
<p><strong>Note:</strong> If you leave proxy setting blank, system proxy will be used (applies to desktop/electron build only)</p>
</div>
<div style="display: flex; justify-content: space-between;">
+14 -14
View File
@@ -201,7 +201,7 @@
<% include dialogs/confirm.ejs %>
</div>
<webview class="electron-in-page-search-window" nodeintegration disablewebsecurity src="/libraries/electron-in-page-search/search-window.html"></webview>
<webview class="electron-in-page-search-window" nodeintegration disablewebsecurity src="libraries/electron-in-page-search/search-window.html"></webview>
<script type="text/javascript">
window.baseApiUrl = 'api/';
@@ -216,29 +216,29 @@
<!-- Required for correct loading of scripts in Electron -->
<script>if (typeof module === 'object') {window.module = module; module = undefined;}</script>
<script src="/libraries/jquery.min.js"></script>
<script src="libraries/jquery.min.js"></script>
<link href="/libraries/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<script src="/libraries/bootstrap/js/bootstrap.bundle.min.js"></script>
<link href="libraries/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<script src="libraries/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="/libraries/bootstrap-notify.min.js"></script>
<script src="libraries/bootstrap-notify.min.js"></script>
<!-- Include Fancytree skin and library -->
<link href="/libraries/fancytree/skin-win8/ui.fancytree.css" rel="stylesheet">
<script src="/libraries/fancytree/jquery.fancytree-all-deps.min.js"></script>
<link href="libraries/fancytree/skin-win8/ui.fancytree.css" rel="stylesheet">
<script src="libraries/fancytree/jquery.fancytree-all-deps.min.js"></script>
<script src="/libraries/jquery.hotkeys.js"></script>
<script src="/libraries/jquery.fancytree.hotkeys.js"></script>
<script src="libraries/jquery.hotkeys.js"></script>
<script src="libraries/jquery.fancytree.hotkeys.js"></script>
<script src="/libraries/knockout.min.js"></script>
<script src="libraries/knockout.min.js"></script>
<script src="/libraries/autocomplete.jquery.min.js"></script>
<script src="libraries/autocomplete.jquery.min.js"></script>
<link href="/stylesheets/style.css" rel="stylesheet">
<link href="stylesheets/style.css" rel="stylesheet">
<script src="/javascripts/services/bootstrap.js" crossorigin type="module"></script>
<script src="javascripts/services/bootstrap.js" crossorigin type="module"></script>
<link rel="stylesheet" type="text/css" href="/libraries/jam/css/jam.min.css">
<link rel="stylesheet" type="text/css" href="libraries/jam/css/jam.min.css">
<script type="text/javascript">
// we hide container initally because otherwise it is rendered first without CSS and then flickers into
+2
View File
@@ -83,6 +83,8 @@
<div class="form-group">
<label for="sync-proxy">Proxy server (optional)</label>
<input type="text" id="sync-proxy" class="form-control" data-bind="value: syncProxy" placeholder="https://<hostname>:<port>">
<p><strong>Note:</strong> If you leave proxy setting blank, system proxy will be used (applies to desktop/electron build only)</p>
</div>
<div class="form-group">
<label for="username">Username</label>