Compare commits

...

85 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
azivner
cfa926602a release 0.25.0-beta 2018-12-02 00:30:07 +01:00
zadam
350cb52c07 Merge pull request #249 from nicoulaj/master
fix #248: crossorigin attribute is needed when including with type="module"
2018-12-02 00:27:59 +01:00
azivner
af195beb7e initial mac support + some other build changes 2018-12-02 00:26:45 +01:00
Julien Nicoulaud
cb00d42546 fix #248: crossorigin attribute is needed when including with type="module"
For more info, see:
 * https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cors-settings-attributes
 * https://jakearchibald.com/2017/es-modules-in-browsers
2018-12-01 23:33:23 +01:00
azivner
ee0b0c3dfe minor export fixes 2018-11-30 22:43:03 +01:00
azivner
ddcb4a0e10 added another mime type for JS 2018-11-30 22:28:30 +01:00
azivner
8cbb29ee25 default location for data dir is now .local for linux and Roaming for windows 2018-11-30 19:56:20 +01:00
azivner
9da11ac144 electron 4 beta 8 2018-11-30 17:50:38 +01:00
azivner
ba7c8e77e5 add support for number precision in promoted attributes, closes #245 2018-11-30 17:36:41 +01:00
azivner
4577b03fc9 promoted attributes now use saved indicator (little check) instead of notification 2018-11-30 17:18:19 +01:00
azivner
72d83aa85e fix note cache - note entity after initialization doesn't contain ciphertext so decryption is not necessary 2018-11-30 15:51:55 +01:00
azivner
8ee80cb5f1 using dashes instead of underscores in header names to avoid the fact that nginx by default drops all headers with underscore in the header name 2018-11-30 15:49:35 +01:00
azivner
4cc08bff8b minor node updates in docker and server package 2018-11-30 13:56:36 +01:00
zadam
295cfb2d75 Merge pull request #247 from gabe565/master
Smaller Docker image
2018-11-30 13:38:47 +01:00
azivner
7775376b33 disable "cut to note" feature because of issues with CKEditor 2018-11-30 11:37:33 +01:00
azivner
9f64e994dc fix minor UI issue causing children overview not being decrypted right after entering protected session 2018-11-30 10:33:40 +01:00
azivner
ee1e4fc710 added "update pojo" stage in entity saving to avoid encrypting data on protected note directly on the entity 2018-11-30 10:20:03 +01:00
Gabe Cook
5ea62c710d Move devtron to devDependencies and update package-lock.json 2018-11-30 05:46:55 +00:00
Gabe Cook
aeca31d06a Add .git, bin, and docs to the .dockerignore 2018-11-30 05:46:49 +00:00
Gabe Cook
9cb9ea6ab5 Smaller Docker image by switching the base image to node:10.13.0-alpine and consolidating scripts 2018-11-30 05:41:53 +00:00
azivner
e37dd69827 link to Patterns of personal knowledge base 2018-11-28 22:05:23 +01:00
azivner
6c51696d1a also don't show "No results" for attr autocomplete 2018-11-27 19:43:34 +01:00
azivner
27787c8f37 attribute name and value autocompletes should not have autoselect since it's OK to enter value not in the autocomplete 2018-11-27 19:40:19 +01:00
azivner
e910595545 updated readme 2018-11-27 19:18:24 +01:00
azivner
a616739805 release 0.24.5 2018-11-27 15:34:15 +01:00
azivner
bea28de6a0 tab on autocomplete doesn't select first item, closes #243 2018-11-27 13:13:06 +01:00
azivner
4e198ca2f0 storing trilium version into export metadata 2018-11-27 10:31:55 +01:00
azivner
2fbd16a0e3 updated demo export according to latest format changes 2018-11-26 23:50:43 +01:00
azivner
76fc49f037 allow import of single HTML file too 2018-11-26 23:47:02 +01:00
azivner
139c99440f export stores note position and some other fixes 2018-11-26 23:39:43 +01:00
azivner
137b9dfa0b fix storing attributes and relinking noteIds 2018-11-26 23:19:19 +01:00
azivner
5f0fdd15eb fix adding sync entities during import 2018-11-26 22:37:59 +01:00
azivner
61e1427b83 fix matching of "b" in the note autcomplete highlighter 2018-11-26 22:35:19 +01:00
azivner
b3aa0ba47c entity events are not triggered on imported entities 2018-11-26 22:27:57 +01:00
azivner
56e2b44c25 fix import 2018-11-26 22:22:16 +01:00
azivner
4d5a17583f happy path tar import now works 2018-11-26 20:30:43 +01:00
azivner
71eda5aa3d import tar archive WIP 2018-11-26 14:47:46 +01:00
azivner
0711ea8dc8 filter out links and relations which are outside of the export 2018-11-25 22:38:09 +01:00
azivner
be206872d1 changed export model to single metadata file per exported .tar 2018-11-25 22:09:52 +01:00
azivner
fcf3fe8dcd tar export can now solve naming conflict 2018-11-25 15:17:28 +01:00
azivner
62dbd4062a on/off button for entering/leaving protected session has been changed to literal buttons 2018-11-25 14:12:33 +01:00
azivner
196e8b4380 extra icons 2018-11-25 14:05:54 +01:00
azivner
551e1255ff export WIP + some unrelated changes 2018-11-25 10:26:45 +01:00
azivner
e09b61d1ac single file export working, tar WIP 2018-11-24 20:58:38 +01:00
azivner
ee23bcc783 unified export dialog, WIP 2018-11-24 14:44:56 +01:00
azivner
3e351bd8d3 better positioning of nonmodal protected session dialog 2018-11-22 21:24:47 +01:00
azivner
0d3bc22d73 fix z-index of notification 2018-11-22 21:19:12 +01:00
azivner
d82898421e less obtrusive saved indicator, fixes #122 2018-11-22 20:25:49 +01:00
azivner
1db2f0c2c5 improved notifications, now with animations, in center and show up properly in the dialogs 2018-11-22 16:08:02 +01:00
azivner
6cd8a2203e create app icon only for electron build 2018-11-22 00:24:33 +01:00
97 changed files with 1968 additions and 1158 deletions

View File

@@ -1,4 +1,7 @@
.git
.idea
/bin
/dist
/docs
/npm-debug.log
node_modules
npm-debug.log
dist
.idea

View File

@@ -1,21 +1,27 @@
FROM node:10.13.0
RUN apt-get update && apt-get install -y nasm
FROM node:10.14.0-alpine
# Create app directory
WORKDIR /usr/src/app
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# Copy both package.json and package-lock.json
# where available (npm@5+)
COPY package*.json ./
COPY package.json package-lock.json ./
RUN npm install --production
# If you are building your code for production
# RUN npm install --only=production
# Install app dependencies
RUN set -x \
&& apk add --no-cache --virtual .build-dependencies \
autoconf \
automake \
g++ \
gcc \
libtool \
make \
nasm \
&& npm install --production \
&& apk del .build-dependencies
# Bundle app source
COPY . .
EXPOSE 8080
CMD [ "node", "src/www" ]
CMD [ "node", "./src/www" ]

View File

@@ -1,23 +1,20 @@
# Trilium Notes
[![Join the chat at https://gitter.im/trilium-notes/Lobby](https://badges.gitter.im/trilium-notes/Lobby.svg)](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Trilium Notes is a hierarchical note taking application. Picture tells a thousand words:
Trilium Notes is a hierarchical note taking application with focus on building large personal knowledge bases. See [screenshots](https://github.com/zadam/trilium/wiki/Screenshot-tour) for quick overview:
![](https://raw.githubusercontent.com/wiki/zadam/trilium/images/screenshot.png)
See other pictures in [screenshot tour](https://github.com/zadam/trilium/wiki/Screenshot-tour).
## Features
* Notes can be arranged into arbitrarily deep hierarchy (tree)
* Notes can have more than 1 parents - see [cloning](https://github.com/zadam/trilium/wiki/Cloning-notes)
* 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
* Strong [note encryption](https://github.com/zadam/trilium/wiki/Protected-notes)
* Strong [note encryption](https://github.com/zadam/trilium/wiki/Protected-notes) with per-note granularity
* [Relation maps](https://github.com/zadam/trilium/wiki/Relation-map) for visualizing notes and their relations
* [Scripting](https://github.com/zadam/trilium/wiki/Scripts) - see [Advanced showcases](https://github.com/zadam/trilium/wiki/Advanced-showcases)
* Scales well in both usability and performance upwards of 100 000 notes
@@ -26,12 +23,14 @@ See other pictures in [screenshot tour](https://github.com/zadam/trilium/wiki/Sc
## Builds
Trilium is provided as either desktop application ([Electron](https://electronjs.org)-based) or web application hosted on your server.
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.
## Documentation
[See wiki for complete list of documentation pages.](https://github.com/zadam/trilium/wiki/)
[See wiki for complete list of documentation pages.](https://github.com/zadam/trilium/wiki/)
You can also read [Patterns of personal knowledge base](https://github.com/zadam/trilium/wiki/Patterns-of-personal-knowledge-base) to get some inspiration on how you might use Trilium.

View File

@@ -1,8 +1,5 @@
#!/usr/bin/env bash
if [[ $# -eq 0 ]] ; then
echo "Missing argument of new version"
exit 1
fi
VERSION=`jq -r ".version" package.json`
sudo docker build -t zadam/trilium:latest -t zadam/trilium:$1 .
sudo docker build -t zadam/trilium:latest -t zadam/trilium:$VERSION .

24
bin/build-linux-x64.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
BUILD_DIR=./dist/trilium-linux-x64
rm -rf $BUILD_DIR
# 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 --executable-name=trilium --platform=linux --arch=x64 --overwrite
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
echo "Packaging linux x64 electron distribution..."
VERSION=`jq -r ".version" package.json`
7z a $BUILD_DIR-${VERSION}.7z $BUILD_DIR

27
bin/build-mac-x64.sh Executable file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/env bash
BUILD_DIR=./dist/trilium-mac-x64
rm -rf $BUILD_DIR
./node_modules/.bin/electron-packager . --out=dist --executable-name=trilium --platform=darwin --arch=x64 --overwrite --icon=src/public/images/app-icons/mac/icon.icns
# Mac build has by default useless directory level
mv "./dist/Trilium Notes-darwin-x64" $BUILD_DIR
echo "Copying required mac binaries"
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-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"
echo "Packaging mac x64 electron distribution..."
VERSION=`jq -r ".version" package.json`
7z a $BUILD_DIR-${VERSION}.7z $BUILD_DIR

View File

@@ -1,13 +1,7 @@
#!/usr/bin/env bash
if [[ $# -eq 0 ]] ; then
echo "Missing argument of new version"
exit 1
fi
VERSION=$1
PKG_DIR=dist/trilium-linux-x64-server
NODE_VERSION=10.13.0
NODE_VERSION=10.14.1
rm -r $PKG_DIR
mkdir $PKG_DIR
@@ -30,9 +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`
7z a trilium-linux-x64-server-${VERSION}.7z trilium-linux-x64-server

25
bin/build-win-x64.sh Executable file
View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
BUILD_DIR=./dist/trilium-windows-x64
rm -rf $BUILD_DIR
./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" $BUILD_DIR
echo "Copying required windows binaries"
WIN_RES_DIR=$BUILD_DIR/resources/app
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
rm -r $BUILD_DIR/swiftshader
echo "Packaging windows x64 electron distribution..."
VERSION=`jq -r ".version" package.json`
7z a $BUILD_DIR-${VERSION}.7z $BUILD_DIR

View File

@@ -8,36 +8,11 @@ echo "Deleting existing builds"
rm -r dist/*
echo "Rebuilding binaries for linux-ia32"
./node_modules/.bin/electron-rebuild --arch=ia32
bin/build-win-x64.sh
./node_modules/.bin/electron-packager . --out=dist --executable-name=trilium --platform=linux --arch=ia32 --overwrite
bin/build-mac-x64.sh
mv "./dist/Trilium Notes-linux-ia32" ./dist/trilium-linux-ia32
# building X64 linux as the last so electron-rebuild will prepare X64 binaries for local development
bin/build-linux-x64.sh
./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 --executable-name=trilium --platform=linux --arch=x64 --overwrite
mv "./dist/Trilium Notes-linux-x64" ./dist/trilium-linux-x64
echo "Copying required windows binaries"
WIN_RES_DIR=./dist/trilium-win32-x64/resources/app
cp -r bin/deps/sqlite/* $WIN_RES_DIR/node_modules/sqlite3/lib/binding/
cp bin/deps/image/cjpeg.exe $WIN_RES_DIR/node_modules/mozjpeg/vendor/
cp bin/deps/image/pngquant.exe $WIN_RES_DIR/node_modules/pngquant-bin/vendor/
cp bin/deps/image/gifsicle.exe $WIN_RES_DIR/node_modules/giflossy/vendor/
echo "Cleaning up unnecessary binaries from all builds"
rm -r ./dist/trilium-linux-ia32/resources/app/bin/deps
rm -r ./dist/trilium-linux-x64/resources/app/bin/deps
rm -r ./dist/trilium-win32-x64/resources/app/bin/deps
bin/build-server.sh

Binary file not shown.

Binary file not shown.

Binary file not shown.

0
bin/generate-cert.sh Normal file → Executable file
View File

View File

@@ -1,14 +0,0 @@
#!/usr/bin/env bash
VERSION=`jq -r ".version" package.json`
cd dist
echo "Packaging linux x64 electron distribution..."
7z a trilium-linux-x64-${VERSION}.7z trilium-linux-x64
echo "Packaging linux ia32 electron distribution..."
7z a trilium-linux-ia32-${VERSION}.7z trilium-linux-ia32
echo "Packaging windows x64 electron distribution..."
7z a trilium-windows-x64-${VERSION}.7z trilium-win32-x64

View File

@@ -42,18 +42,22 @@ git push origin $TAG
bin/build.sh
bin/package.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
echo "Creating release in GitHub"
EXTRA=
if [[ $TAG == *"beta"* ]]; then
EXTRA=--pre-release
fi
github-release release \
--tag $TAG \
--name "$TAG release"
--name "$TAG release" $EXTRA
echo "Uploading linux x64 build"
@@ -62,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 \
@@ -76,9 +73,14 @@ github-release upload \
--name "$WINDOWS_X64_BUILD" \
--file "dist/$WINDOWS_X64_BUILD"
echo "Packaging server version"
echo "Uploading mac x64 build"
bin/build-server.sh $VERSION
github-release upload \
--tag $TAG \
--name "$MAC_X64_BUILD" \
--file "dist/$MAC_X64_BUILD"
echo "Uploading linux x64 server build"
github-release upload \
--tag $TAG \

Binary file not shown.

View File

@@ -0,0 +1 @@
UPDATE attributes SET name = 'archived' where name = 'hideInAutocomplete';

View File

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

479
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "trilium",
"version": "0.24.3-beta",
"version": "0.25.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -65,21 +65,21 @@
}
},
"@jimp/bmp": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.5.4.tgz",
"integrity": "sha512-P/ezH1FuoM3FwS0Dm2ZGkph4x5/rPBzFLEZor7KQkmGUnYEIEG4o0BUcAWFmJOp2HgzbT6O2SfrpJNBOcVACzQ==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.6.0.tgz",
"integrity": "sha512-zZOcVT1zK/1QL5a7qirkzPPgDKB1ianER7pBdpR2J71vx/g8MnrPbL3h/jEVPxjdci2Hph/VWhc/oLBtTbqO8w==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"bmp-js": "^0.1.0",
"core-js": "^2.5.7"
}
},
"@jimp/core": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.5.4.tgz",
"integrity": "sha512-n3uvHy2ndUKItmbhnRO8xmU8J6KR+v6CQxO9sbeUDpSc3VXc1PkqrA8ZsCVFCjnDFcGBXL+MJeCTyQzq5W9Crw==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.6.0.tgz",
"integrity": "sha512-ngAkyCLtX7buc2QyFy0ql/j4R2wGYQVsVhW2G3Y0GVAAklRIFIUYpyNKrqs228xA8f2O6XStbDStFlYkt7uNeg==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"any-base": "^1.1.0",
"buffer": "^5.2.0",
"core-js": "^2.5.7",
@@ -114,252 +114,252 @@
}
},
"@jimp/custom": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.5.4.tgz",
"integrity": "sha512-tLfyJoyouDl2J3RPFGfDzTtE+4S8ljqJUmLzy/cmx1n7+xS5TpLPdPskp7UaeAfNTqdF4CNAm94KYoxTZdj2mg==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.6.0.tgz",
"integrity": "sha512-+YZIWhf03Rfbi+VPbHomKInu3tcntF/aij/JrIJd1QZq13f8m3mRNxakXupiL18KH0C8BPNDk8RiwFX+HaOw3A==",
"requires": {
"@jimp/core": "^0.5.4",
"@jimp/core": "^0.6.0",
"core-js": "^2.5.7"
}
},
"@jimp/gif": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.5.0.tgz",
"integrity": "sha512-HVB4c7b8r/yCpjhCjVNPRFLuujTav5UPmcQcFJjU6aIxmne6e29rAjRJEv3UMamHDGSu/96PzOsPZBO5U+ZGww==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.6.0.tgz",
"integrity": "sha512-aWQ02P0ymTN1eh0BVsY+84wMdb/QeiVpCNQZl9y50cRnpuMM8TTmF/ZdCEBDiTRFcwXzHsqBXcLwEcYp3X2lTw==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"core-js": "^2.5.7",
"omggif": "^1.0.9"
}
},
"@jimp/jpeg": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.5.4.tgz",
"integrity": "sha512-YaPWm+YSGCThNE/jLMckM3Qs6uaMxd/VsHOnEaqu5tGA4GFbfVaWHjKqkNGAFuiNV+HdgKlNcCOF3of+elvzqQ==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.6.0.tgz",
"integrity": "sha512-quYb+lM4h57jQvr2q9dEIkc0laTljws4dunIdFhJRfa5UlNL5mHInk8h5MxyALo0mZdT07TAcxiDHw5QXZ28JQ==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"core-js": "^2.5.7",
"jpeg-js": "^0.3.4"
}
},
"@jimp/plugin-blit": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.5.4.tgz",
"integrity": "sha512-WqDYOugv76hF1wnKy7+xPGf9PUbcm9vPW28/jHWn1hjbb2GnusJ2fVEFad76J/1SPfhrQ2Uebf2QCWJuLmOqZg==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.6.0.tgz",
"integrity": "sha512-LjiCa+8OT2fgmvBpZt0ogurg/eu5kB8ZFWDRwHPcf8i+058sZC20dar/qrjVd5Knssq4ynjb5oAHsGuJq16Rqw==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"core-js": "^2.5.7"
}
},
"@jimp/plugin-blur": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.5.0.tgz",
"integrity": "sha512-5k0PXCA1RTJdITL7yMAyZ5tGQjKLHqFvwdXj/PCoBo5PuMyr0x6qfxmQEySixGk/ZHdDxMi80vYxHdKHjNNgjg==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.6.0.tgz",
"integrity": "sha512-/vjGcEiHda6OLTCYqXPFkfSTbL+RatZoGcp1vewcWqChUccn9QVINTlxB7nEI/3Nb/i7KdhOPNEQh1k6q6QXsw==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"core-js": "^2.5.7"
}
},
"@jimp/plugin-color": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.5.5.tgz",
"integrity": "sha512-hWeOqNCmLguGYLhSvBrpfCvlijsMEVaLZAOod62s1rzWnujozyKOzm2eZe+W3To6mHbp5RGJNVrIwHBWMab4ug==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.6.0.tgz",
"integrity": "sha512-mvDeAwN8ZpDkOaABMJ0w9zUzo9OOtu1qvvPkSirXDTMiXt1nsbfz8BoeoD7nU2MFhQj5MiGjH65UDnsH5ZzYuw==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"core-js": "^2.5.7",
"tinycolor2": "^1.4.1"
}
},
"@jimp/plugin-contain": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.5.4.tgz",
"integrity": "sha512-8YJh4FI3S69unri0nJsWeqVLeVGA77N2R0Ws16iSuCCD/5UnWd9FeWRrSbKuidBG6TdMBaG2KUqSYZeHeH9GOQ==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.6.0.tgz",
"integrity": "sha512-gPHnoQkDztMbvnTVo01BaMoM/hhDJdeJ7FRToD4p4Qvdor4V0I6NXtjOeUPXfD94miTgh/UTyJDqeG4GZzi4sA==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"core-js": "^2.5.7"
}
},
"@jimp/plugin-cover": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.5.4.tgz",
"integrity": "sha512-2Rur7b44WiDDgizUI2M2uYWc1RmfhU5KjKS1xXruobjQ0tXkf5xlrPXSushq0hB6Ne0Ss6wv0+/6eQ8WeGHU2w==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.6.0.tgz",
"integrity": "sha512-iv9lA2v3qv+x3eaTThtyzFg+hO8/pSnM8NBymC5OlpSJnR54aWi7BVFXLJAF27T4EZyXko432PVul2IdY3BEPw==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"core-js": "^2.5.7"
}
},
"@jimp/plugin-crop": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.5.4.tgz",
"integrity": "sha512-6t0rqn4VazquGk48tO6hFBrQ+nkvC+A1RnR6UM/m8ZtG2/yjpwF0MXcpgJI1Fb+a4Ug7BY1fu2GPcZOhnAVK/g==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.6.0.tgz",
"integrity": "sha512-YftdmFZ2YnZDYyBulkStCt2MZbKKfbjytkE+6i3Djk2b/Rfryg5xjgzVnAumCRQJhVPukexrnc2V7KKbEgx7mQ==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"core-js": "^2.5.7"
}
},
"@jimp/plugin-displace": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.5.0.tgz",
"integrity": "sha512-Bec7SQvnmKia4hOXEDjeNVx7vo/1bWqjuV6NO8xbNQcAO3gaCl91c9FjMDhsfAVb0Ou6imhbIuFPrLxorXsecQ==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.6.0.tgz",
"integrity": "sha512-kkva5Fy3r7J7QmiqYQ5c9NeUKKkN7+KSfCGsZ6tkRHK4REMIXhQO/OnJN8XG6RReV29O6QykdyeTXDiHUDiROw==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"core-js": "^2.5.7"
}
},
"@jimp/plugin-dither": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.5.0.tgz",
"integrity": "sha512-We2WJQsD/Lm8oqBFp/vUv9/5r2avyenL+wNNu/s2b1HqA5O4sPGrjHy9K6vIov0NroQGCQ3bNznLkTmjiHKBcg==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.6.0.tgz",
"integrity": "sha512-ILSG7bl3SOqmcIa9C4nBvs0h0E0ObnMbeKWUZiNuz6i0OAlbxryiIfU4j0UVQD5XqT9ksC5mviVNrvOMw4SZLw==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"core-js": "^2.5.7"
}
},
"@jimp/plugin-flip": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.5.0.tgz",
"integrity": "sha512-D/ehBQxLMNR7oNd80KXo4tnSET5zEm5mR70khYOTtTlfti/DlLp3qOdjPOzfLyAdqO7Ly4qCaXrIsnia+pfPrA==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.6.0.tgz",
"integrity": "sha512-MXGGwABjERvfqVadEzJuVAmbsEQfjxXD0O/mMBegU1Qh7/JmnKAVplQCnojsMPxUdao/FKZjQqOnB/j4LLJtOQ==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"core-js": "^2.5.7"
}
},
"@jimp/plugin-gaussian": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.5.0.tgz",
"integrity": "sha512-Ln4kgxblv0/YzLBDb/J8DYPLhDzKH87Y8yHh5UKv3H+LPKnLaEG3L4iKTE9ivvdocnjmrtTFMYcWv2ERSPeHcg==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.6.0.tgz",
"integrity": "sha512-RUsBCyj6Ukxgn/TU8v6c6WRbSFqKM0iknLVqDkKIuiOyJB7ougv66fqomh/i/h3ihIkEnf50BuO0c3ovrczfvw==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"core-js": "^2.5.7"
}
},
"@jimp/plugin-invert": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.5.0.tgz",
"integrity": "sha512-/vyKeIi3T7puf+8ruWovTjzDC585EnTwJ+lGOOUYiNPsdn4JDFe1B3xd+Ayv9aCQbXDIlPElZaM9vd/+wqDiIQ==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.6.0.tgz",
"integrity": "sha512-zTCqK8el6eqcNKAxw0y57gHBFgxygI5iM8dQDPyqsvVWO71i8XII7ubnJhEvPPN7vhIKlOSnS9XXglezvJoX4Q==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"core-js": "^2.5.7"
}
},
"@jimp/plugin-mask": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.5.4.tgz",
"integrity": "sha512-mUJ04pCrUWaJGXPjgoVbzhIQB8cVobj2ZEFlGO3BEAjyylYMrdJlNlsER8dd7UuJ2L/a4ocWtFDdsnuicnBghQ==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.6.0.tgz",
"integrity": "sha512-zkZVqAA7lxWhkn5EbPjBQ6tPluYIGfLMSX4kD1gksj+MVJJnVAd459AVuEXCvkUvv4wG5AlH8m6ve5NZj9vvxw==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"core-js": "^2.5.7"
}
},
"@jimp/plugin-normalize": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.5.4.tgz",
"integrity": "sha512-Q5W0oEz9wxsjuhvHAJynI/OqXZcmqEAuRONQId7Aw5ulCXSOg9C4y2a67EO7aZAt55T+zMVxI9UpVUpzVvO6hw==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.6.0.tgz",
"integrity": "sha512-7bNGT+S0rw9gvmxpkNsA19JSqBZYFrAn9QhEmoN4HIimdKtJaoLJh/GnxrPuOBLuv1IPJntoTOOWvOmfrQ6/ww==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"core-js": "^2.5.7"
}
},
"@jimp/plugin-print": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.5.4.tgz",
"integrity": "sha512-DOZr5TY9WyMWFBD37oz7KpTEBVioFIHQF/gH5b3O5jjFyj4JPMkw7k3kVBve9lIrzIYrvLqe0wH59vyAwpeEFg==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.6.0.tgz",
"integrity": "sha512-kXNHYo7bGQiMZkUqhCvm6OomjJtZnLGs7cgXp9qsCfPcDBLLW+X3oxnoLaePQMlpQt6hX/lzFnNaWKv/KB1jlA==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"core-js": "^2.5.7",
"load-bmfont": "^1.4.0"
}
},
"@jimp/plugin-resize": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.5.4.tgz",
"integrity": "sha512-lXNprNAT0QY1D1vG/1x6urUTlWuZe2dfL29P81ApW2Yfcio471+oqo45moX5FLS0q24xU600g7cHGf2/TzqSfA==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.6.0.tgz",
"integrity": "sha512-m0AA/mPkJG++RuftBFDUMRenqgIN/uSh88Kqs33VURYaabApni4ML3QslE1TCJtl2Lnu1eosxYlbzODjHx49eg==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"core-js": "^2.5.7"
}
},
"@jimp/plugin-rotate": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.5.4.tgz",
"integrity": "sha512-SIdUpMc8clObMchy8TnjgHgcXEQM992z5KavgiuOnCuBlsmSHtE3MrXTOyMW0Dn3gqapV9Y5vygrLm/BVtCCsg==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.6.0.tgz",
"integrity": "sha512-1QGlIisyxs2HNLuynq/ETc4h7E6At3yR+IYAhG9U4KONG4RqlIy0giyDhnfEZaiqOE+O7f+0Z7zN6GoSHmQjzg==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"core-js": "^2.5.7"
}
},
"@jimp/plugin-scale": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.5.0.tgz",
"integrity": "sha512-5InIOr3cNtrS5aQ/uaosNf28qLLc0InpNGKFmGFTv8oqZqLch6PtDTjDBZ1GGWsPdA/ljy4Qyy7mJO1QBmgQeQ==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.6.0.tgz",
"integrity": "sha512-le/ttYwYioNPRoMlMaoJMCTv+m8d1v0peo/3J8E6Rf9ok7Bw3agkvjL9ILnsmr8jXj1YLrBSPKRs5nJ6ziM/qA==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"core-js": "^2.5.7"
}
},
"@jimp/plugins": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.5.5.tgz",
"integrity": "sha512-9oF6LbSM/K7YkFCcxaPaD8NUkL/ZY8vT8NIGfQ/NpX+tKQtcsLHcRavHpUC+M1xXShv/QGx9OdBV/jgiu82QYg==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.6.0.tgz",
"integrity": "sha512-9+znfBJM1B31kvw+IcQFnAuDntQhwca/SONFnKOSZ8BNiQdiuTNbXHFxOo3tvdv1ngtB+LkkiTgK+QoF358b8g==",
"requires": {
"@jimp/plugin-blit": "^0.5.4",
"@jimp/plugin-blur": "^0.5.0",
"@jimp/plugin-color": "^0.5.5",
"@jimp/plugin-contain": "^0.5.4",
"@jimp/plugin-cover": "^0.5.4",
"@jimp/plugin-crop": "^0.5.4",
"@jimp/plugin-displace": "^0.5.0",
"@jimp/plugin-dither": "^0.5.0",
"@jimp/plugin-flip": "^0.5.0",
"@jimp/plugin-gaussian": "^0.5.0",
"@jimp/plugin-invert": "^0.5.0",
"@jimp/plugin-mask": "^0.5.4",
"@jimp/plugin-normalize": "^0.5.4",
"@jimp/plugin-print": "^0.5.4",
"@jimp/plugin-resize": "^0.5.4",
"@jimp/plugin-rotate": "^0.5.4",
"@jimp/plugin-scale": "^0.5.0",
"@jimp/plugin-blit": "^0.6.0",
"@jimp/plugin-blur": "^0.6.0",
"@jimp/plugin-color": "^0.6.0",
"@jimp/plugin-contain": "^0.6.0",
"@jimp/plugin-cover": "^0.6.0",
"@jimp/plugin-crop": "^0.6.0",
"@jimp/plugin-displace": "^0.6.0",
"@jimp/plugin-dither": "^0.6.0",
"@jimp/plugin-flip": "^0.6.0",
"@jimp/plugin-gaussian": "^0.6.0",
"@jimp/plugin-invert": "^0.6.0",
"@jimp/plugin-mask": "^0.6.0",
"@jimp/plugin-normalize": "^0.6.0",
"@jimp/plugin-print": "^0.6.0",
"@jimp/plugin-resize": "^0.6.0",
"@jimp/plugin-rotate": "^0.6.0",
"@jimp/plugin-scale": "^0.6.0",
"core-js": "^2.5.7",
"timm": "^1.6.1"
}
},
"@jimp/png": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.5.4.tgz",
"integrity": "sha512-J2NU7368zihF1HUZdmpXsL/Hhyf+I3ubmK+6Uz3Uoyvtk1VS7dO3L0io6fJQutfWmPZ4bvu6Ry022oHjbi6QCA==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.6.0.tgz",
"integrity": "sha512-DBtMyQyrJxuKI7/1dVqLek+rCMM8U6BSOTHgo05wU7lhJKTB6fn2tbYfsnHQKzd9ld1M2qKuC+O1GTVdB2yl6w==",
"requires": {
"@jimp/utils": "^0.5.0",
"@jimp/utils": "^0.6.0",
"core-js": "^2.5.7",
"pngjs": "^3.3.3"
}
},
"@jimp/tiff": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.5.4.tgz",
"integrity": "sha512-hr7Zq3eWjAZ+itSwuAObIWMRNv7oHVM3xuEDC2ouP7HfE7woBtyhCyfA7u12KlgtM57gKWeogXqTlewRGVzx6g==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.6.0.tgz",
"integrity": "sha512-PV95CquEsolFziq0zZrAEJIzZSKwMK89TvkOXTPDi/xesgdXGC2rtG1IZFpC9L4UX5hi/M5GaeJa49xULX6Nqw==",
"requires": {
"core-js": "^2.5.7",
"utif": "^2.0.1"
}
},
"@jimp/types": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.5.4.tgz",
"integrity": "sha512-nbZXM6TsdpnYHIBd8ZuoxGpvmxc2SqiggY30/bhOP/VJQoDBzm2v/20Ywz5M0snpIK2SdYG52eZPNjfjqUP39w==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.6.0.tgz",
"integrity": "sha512-j4tm82huEWpLrwave/2NYnMTY6us/6K9Js6Vd/CHoM/ki8M71tMXEVzc8tly92wtnEzQ9+FEk0Ue6pYo68m/5A==",
"requires": {
"@jimp/bmp": "^0.5.4",
"@jimp/gif": "^0.5.0",
"@jimp/jpeg": "^0.5.4",
"@jimp/png": "^0.5.4",
"@jimp/tiff": "^0.5.4",
"@jimp/bmp": "^0.6.0",
"@jimp/gif": "^0.6.0",
"@jimp/jpeg": "^0.6.0",
"@jimp/png": "^0.6.0",
"@jimp/tiff": "^0.6.0",
"core-js": "^2.5.7",
"timm": "^1.6.1"
}
},
"@jimp/utils": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.5.0.tgz",
"integrity": "sha512-7H9RFVU+Li2XmEko0GGyzy7m7JjSc7qa+m8l3fUzYg2GtwASApjKF/LSG2AUQCUmDKFLdfIEVjxvKvZUJFEmpw==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.6.0.tgz",
"integrity": "sha512-z5iYEfqc45vlYweROneNkjv32en6jS7lPL/eMLIvaEcQAHaoza20Dw8fUoJ0Ht9S92kR74xeTunAZq+gK2w67Q==",
"requires": {
"core-js": "^2.5.7"
}
@@ -399,9 +399,9 @@
"integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow=="
},
"@types/node": {
"version": "8.10.37",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.37.tgz",
"integrity": "sha512-Jp39foY8Euv/PG4OGPyzxis82mnjcUtXLEMA8oFMCE4ilmuJgZPdV2nZNV1moz+99EJTtcpOSgDCgATUwABKig==",
"version": "8.10.38",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.38.tgz",
"integrity": "sha512-EibsnbJerd0hBFaDjJStFrVbVBAtOy4dgL8zZFw0uOvPqzBAX59Ci8cgjg3+RgJIWhsB5A4c+pi+D4P9tQQh/A==",
"dev": true
},
"abab": {
@@ -426,7 +426,8 @@
"accessibility-developer-tools": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/accessibility-developer-tools/-/accessibility-developer-tools-2.12.0.tgz",
"integrity": "sha1-PaDM6dbsY3OWS4TzXbfPw996tRQ="
"integrity": "sha1-PaDM6dbsY3OWS4TzXbfPw996tRQ=",
"dev": true
},
"acorn": {
"version": "5.7.3",
@@ -670,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",
@@ -681,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": {
@@ -968,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",
@@ -2033,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
}
@@ -2202,6 +2204,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/devtron/-/devtron-1.4.0.tgz",
"integrity": "sha1-tedIvW6Vu+cL/MaKrm/mlhGUQeE=",
"dev": true,
"requires": {
"accessibility-developer-tools": "^2.11.0",
"highlight.js": "^9.3.0",
@@ -2372,9 +2375,9 @@
"integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ=="
},
"electron": {
"version": "4.0.0-beta.7",
"resolved": "https://registry.npmjs.org/electron/-/electron-4.0.0-beta.7.tgz",
"integrity": "sha512-770Hzxq10nQrzq39uVmvHLNKpPY3TCNrk+IYGDQTNWqmkreXZX6+9iflMMo+xdg1ZHysrTj1QQZvsjjBY176pg==",
"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",
@@ -2524,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",
@@ -2535,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": {
@@ -2550,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"
}
}
}
@@ -4303,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
},
@@ -4903,7 +4897,8 @@
"highlight.js": {
"version": "9.12.0",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.12.0.tgz",
"integrity": "sha1-5tnb5Xy+/mB1HwKvM2GVhwyQwB4="
"integrity": "sha1-5tnb5Xy+/mB1HwKvM2GVhwyQwB4=",
"dev": true
},
"hosted-git-info": {
"version": "2.7.1",
@@ -4965,7 +4960,8 @@
"humanize-plus": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/humanize-plus/-/humanize-plus-1.8.2.tgz",
"integrity": "sha1-pls0RZrWNnrbs3B6gqPJ+RYWcDA="
"integrity": "sha1-pls0RZrWNnrbs3B6gqPJ+RYWcDA=",
"dev": true
},
"iconv-lite": {
"version": "0.4.23",
@@ -5604,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",
@@ -5750,14 +5740,14 @@
"dev": true
},
"jimp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/jimp/-/jimp-0.5.6.tgz",
"integrity": "sha512-H0nHTu6KgAgQzDxa38ew2dXbnRzKm1w5uEyhMIxqwCQVjwgarOjjkV/avbNLxfxRHAFaNp4rGIc/qm8P+uhX9A==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/jimp/-/jimp-0.6.0.tgz",
"integrity": "sha512-RYpN+AAlTEMf8Bnkhq2eeTNyr70rDK/2UUfUqzBJmwmZwdR6fxRJvgbCGWT1BDVRxaAqo+4CWm8ePBxOIsr4jg==",
"requires": {
"@babel/polyfill": "^7.0.0",
"@jimp/custom": "^0.5.4",
"@jimp/plugins": "^0.5.5",
"@jimp/types": "^0.5.4",
"@jimp/custom": "^0.6.0",
"@jimp/plugins": "^0.6.0",
"@jimp/types": "^0.6.0",
"core-js": "^2.5.7"
}
},
@@ -6417,11 +6407,18 @@
"integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw=="
},
"mime-types": {
"version": "2.1.20",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz",
"integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==",
"version": "2.1.21",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz",
"integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==",
"requires": {
"mime-db": "~1.36.0"
"mime-db": "~1.37.0"
},
"dependencies": {
"mime-db": {
"version": "1.37.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz",
"integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg=="
}
}
},
"mimic-fn": {
@@ -6531,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": {
@@ -6571,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",
@@ -7275,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",
@@ -7770,9 +7757,9 @@
}
},
"pako": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz",
"integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg=="
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.7.tgz",
"integrity": "sha512-3HNK5tW4x8o5mO8RuHZp3Ydw9icZXx0RANAOMzlMzx7LVXhMJ4mo3MOBpzyd7r/+RUu8BmndP47LXT+vzjtWcQ=="
},
"parse-author": {
"version": "2.0.0",
@@ -8030,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
}
}
@@ -8479,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",
@@ -8815,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",

View File

@@ -2,7 +2,7 @@
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.24.4-beta",
"version": "0.26.0-beta",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -27,7 +27,6 @@
"commonmark": "0.28.1",
"cookie-parser": "1.4.3",
"debug": "4.1.0",
"devtron": "1.4.0",
"ejs": "2.6.1",
"electron-debug": "2.0.0",
"electron-dl": "1.12.0",
@@ -44,14 +43,13 @@
"imagemin-mozjpeg": "8.0.0",
"imagemin-pngquant": "6.0.0",
"ini": "1.3.5",
"jimp": "0.5.6",
"moment": "2.22.2",
"jimp": "0.6.0",
"mime-types": "^2.1.21",
"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",
@@ -66,9 +64,10 @@
"xml2js": "0.4.19"
},
"devDependencies": {
"electron": "4.0.0-beta.7",
"devtron": "1.4.0",
"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",

View File

@@ -105,6 +105,11 @@ class Attribute extends Entity {
this.dateModified = dateUtils.nowDate();
}
}
// cannot be static!
updatePojo(pojo) {
delete pojo.isOwned;
}
}
module.exports = Attribute;

View File

@@ -59,6 +59,11 @@ class Branch extends Entity {
this.dateModified = dateUtils.nowDate();
}
}
// cannot be static!
updatePojo(pojo) {
delete pojo.origParentNoteId;
}
}
module.exports = Branch;

View File

@@ -4,6 +4,7 @@ const Entity = require('./entity');
const Attribute = require('./attribute');
const protectedSessionService = require('../services/protected_session');
const repository = require('../services/repository');
const sql = require('../services/sql');
const dateUtils = require('../services/date_utils');
const LABEL = 'label';
@@ -74,7 +75,9 @@ class Note extends Entity {
/** @returns {boolean} true if this note is JavaScript (code or attachment) */
isJavaScript() {
return (this.type === "code" || this.type === "file")
&& (this.mime.startsWith("application/javascript") || this.mime === "application/x-javascript");
&& (this.mime.startsWith("application/javascript")
|| this.mime === "application/x-javascript"
|| this.mime === "text/javascript");
}
/** @returns {boolean} true if this note is HTML */
@@ -433,14 +436,32 @@ class Note extends Entity {
}
/**
* Finds child notes with given attribute name and value. Only own attributes are considered, not inherited ones
* @return {Promise<string[]>} return list of all descendant noteIds of this note. Returning just noteIds because number of notes can be huge. Includes also this note's noteId
*/
async getDescendantNoteIds() {
return await sql.getColumn(`
WITH RECURSIVE
tree(noteId) AS (
SELECT ?
UNION
SELECT branches.noteId FROM branches
JOIN tree ON branches.parentNoteId = tree.noteId
JOIN notes ON notes.noteId = branches.noteId
WHERE notes.isDeleted = 0
AND branches.isDeleted = 0
)
SELECT noteId FROM tree`, [this.noteId]);
}
/**
* Finds descendant notes with given attribute name and value. Only own attributes are considered, not inherited ones
*
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @param {string} [value] - attribute value
* @returns {Promise<Note[]>}
*/
async findChildNotesWithAttribute(type, name, value) {
async getDescendantNotesWithAttribute(type, name, value) {
const params = [this.noteId, name];
let valueCondition = "";
@@ -472,22 +493,22 @@ class Note extends Entity {
}
/**
* Finds notes with given label name and value. Only own labels are considered, not inherited ones
* Finds descendant notes with given label name and value. Only own labels are considered, not inherited ones
*
* @param {string} name - label name
* @param {string} [value] - label value
* @returns {Promise<Note[]>}
*/
async findChildNotesWithLabel(name, value) { return await this.findChildNotesWithAttribute(LABEL, name, value); }
async getDescendantNotesWithLabel(name, value) { return await this.getDescendantNotesWithAttribute(LABEL, name, value); }
/**
* Finds notes with given relation name and value. Only own relations are considered, not inherited ones
* Finds descendant notes with given relation name and value. Only own relations are considered, not inherited ones
*
* @param {string} name - relation name
* @param {string} [value] - relation value
* @returns {Promise<Note[]>}
*/
async findChildNotesWithRelation(name, value) { return await this.findChildNotesWithAttribute(RELATION, name, value); }
async getDescendantNotesWithRelation(name, value) { return await this.getDescendantNotesWithAttribute(RELATION, name, value); }
/**
* Returns note revisions of this note.
@@ -587,10 +608,6 @@ class Note extends Entity {
// we do this here because encryption needs the note ID for the IV
this.generateIdIfNecessary();
if (this.isProtected) {
protectedSessionService.encryptNote(this);
}
if (!this.isDeleted) {
this.isDeleted = false;
}
@@ -605,6 +622,17 @@ class Note extends Entity {
this.dateModified = dateUtils.nowDate();
}
}
// cannot be static!
updatePojo(pojo) {
if (pojo.isProtected) {
protectedSessionService.encryptNote(pojo);
}
delete pojo.jsonContent;
delete pojo.isContentAvailable;
delete pojo.__attributeCache;
}
}
module.exports = Note;

Binary file not shown.

View File

@@ -67,7 +67,8 @@ function AttributesModel() {
attr.labelDefinition = (attr.type === 'label-definition' && attr.value) ? attr.value : {
labelType: "text",
multiplicityType: "singlevalue",
isPromoted: true
isPromoted: true,
numberPrecision: 0
};
attr.relationDefinition = (attr.type === 'relation-definition' && attr.value) ? attr.value : {
@@ -187,7 +188,8 @@ function AttributesModel() {
labelDefinition: {
labelType: "text",
multiplicityType: "singlevalue",
isPromoted: true
isPromoted: true,
numberPrecision: 0
},
relationDefinition: {
multiplicityType: "singlevalue",

View File

@@ -0,0 +1,77 @@
import treeService from '../services/tree.js';
import treeUtils from "../services/tree_utils.js";
import exportService from "../services/export.js";
const $dialog = $("#export-dialog");
const $form = $("#export-form");
const $noteTitle = $dialog.find(".note-title");
const $subtreeFormats = $("#export-subtree-formats");
const $singleFormats = $("#export-single-formats");
const $subtreeType = $("#export-type-subtree");
const $singleType = $("#export-type-single");
async function showDialog(defaultType) {
if (defaultType === 'subtree') {
$subtreeType.prop("checked", true).change();
}
else if (defaultType === 'single') {
$singleType.prop("checked", true).change();
}
else {
throw new Error("Unrecognized type " + defaultType);
}
glob.activeDialog = $dialog;
$dialog.modal();
const currentNode = treeService.getCurrentNode();
const noteTitle = await treeUtils.getNoteTitle(currentNode.data.noteId);
$noteTitle.html(noteTitle);
}
$form.submit(() => {
const exportType = $dialog.find("input[name='export-type']:checked").val();
if (!exportType) {
// this shouldn't happen as we always choose default export type
alert("Choose export type first please");
return;
}
const exportFormat = exportType === 'subtree'
? $("input[name=export-subtree-format]:checked").val()
: $("input[name=export-single-format]:checked").val();
const currentNode = treeService.getCurrentNode();
exportService.exportBranch(currentNode.data.branchId, exportType, exportFormat);
$dialog.modal('hide');
return false;
});
$('input[name=export-type]').change(function () {
if (this.value === 'subtree') {
if ($("input[name=export-subtree-format]:checked").length === 0) {
$("input[name=export-subtree-format]:first").prop("checked", true);
}
$subtreeFormats.slideDown();
$singleFormats.slideUp();
}
else {
if ($("input[name=export-single-format]:checked").length === 0) {
$("input[name=export-single-format]:first").prop("checked", true);
}
$subtreeFormats.slideUp();
$singleFormats.slideDown();
}
});
export default {
showDialog
};

View File

@@ -1,35 +0,0 @@
import treeService from '../services/tree.js';
import server from '../services/server.js';
import treeUtils from "../services/tree_utils.js";
import exportService from "../services/export.js";
const $dialog = $("#export-subtree-dialog");
const $form = $("#export-subtree-form");
const $noteTitle = $dialog.find(".note-title");
async function showDialog() {
glob.activeDialog = $dialog;
$dialog.modal();
const currentNode = treeService.getCurrentNode();
const noteTitle = await treeUtils.getNoteTitle(currentNode.data.noteId);
$noteTitle.html(noteTitle);
}
$form.submit(() => {
const exportFormat = $dialog.find("input[name='export-format']:checked").val();
const currentNode = treeService.getCurrentNode();
exportService.exportSubtree(currentNode.data.branchId, exportFormat);
$dialog.modal('hide');
return false;
});
export default {
showDialog
};

View File

@@ -14,7 +14,7 @@ class Branch {
/** @param {string} */
this.prefix = row.prefix;
/** @param {boolean} */
this.isExpanded = row.isExpanded;
this.isExpanded = !!row.isExpanded;
}
/** @returns {NoteShort} */

View File

@@ -10,9 +10,9 @@ function initAttributeNameAutocomplete({ $el, attributeType, open }) {
$el.autocomplete({
appendTo: document.querySelector('body'),
hint: false,
autoselect: true,
openOnFocus: true,
minLength: 0
minLength: 0,
tabAutocomplete: false
}, [{
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
@@ -25,10 +25,6 @@ function initAttributeNameAutocomplete({ $el, attributeType, open }) {
return {name};
});
if (result.length === 0) {
result.push({name: "No results"});
}
cb(result);
}
}]);
@@ -57,9 +53,9 @@ async function initLabelValueAutocomplete({ $el, open }) {
$el.autocomplete({
appendTo: document.querySelector('body'),
hint: false,
autoselect: true,
openOnFocus: true,
minLength: 0
minLength: 0,
tabAutocomplete: false
}, [{
displayKey: 'value',
source: function (term, cb) {

View File

@@ -3,14 +3,13 @@ import utils from "./utils.js";
import messagingService from "./messaging.js";
import treeUtils from "./tree_utils.js";
import noteAutocompleteService from "./note_autocomplete.js";
import treeService from "./tree.js";
import linkService from "./link.js";
import infoService from "./info.js";
import noteDetailService from "./note_detail.js";
const $attributeList = $("#attribute-list");
const $attributeListInner = $("#attribute-list-inner");
const $promotedAttributesContainer = $("#note-detail-promoted-attributes");
const $savedIndicator = $("#saved-indicator");
let attributePromise;
@@ -146,7 +145,8 @@ async function createPromotedAttributeRow(definitionAttr, valueAttr) {
hint: false,
autoselect: true,
openOnFocus: true,
minLength: 0
minLength: 0,
tabAutocomplete: false
}, [{
displayKey: 'value',
source: function (term, cb) {
@@ -161,6 +161,14 @@ async function createPromotedAttributeRow(definitionAttr, valueAttr) {
}
else if (definition.labelType === 'number') {
$input.prop("type", "number");
let step = 1;
for (let i = 0; i < (definition.numberPrecision || 0) && i < 10; i++) {
step /= 10;
}
$input.prop("step", step);
}
else if (definition.labelType === 'boolean') {
$input.prop("type", "checkbox");
@@ -267,7 +275,12 @@ async function promotedAttributeChanged(event) {
$attr.prop("attribute-id", result.attributeId);
infoService.showMessage("Attribute has been saved.");
// animate only if it's not being animated already, this is important especially for e.g. number inputs
// which can be changed many times in a second by clicking on higher/lower buttons.
if ($savedIndicator.queue().length === 0) {
$savedIndicator.fadeOut();
$savedIndicator.fadeIn();
}
}
export default {

View File

@@ -7,6 +7,7 @@ import recentChangesDialog from '../dialogs/recent_changes.js';
import optionsDialog from '../dialogs/options.js';
import sqlConsoleDialog from '../dialogs/sql_console.js';
import markdownImportDialog from '../dialogs/markdown_import.js';
import exportDialog from '../dialogs/export.js';
import cloning from './cloning.js';
import contextMenu from './tree_context_menu.js';
@@ -31,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;
@@ -83,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
@@ -103,12 +107,34 @@ if (utils.isElectron()) {
});
}
$("#export-note-to-markdown-button").click(function () {
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;
}
exportService.exportSubtree(noteDetailService.getCurrentNoteId(), 'markdown-single')
exportDialog.showDialog('single');
});
treeService.showTree();
@@ -117,4 +143,4 @@ entrypoints.registerEntrypoints();
tooltip.setupTooltip();
bundle.executeStartupBundles();
bundle.executeStartupBundles();

View File

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

View File

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

View File

@@ -43,8 +43,8 @@ function registerEntrypoints() {
$("#recent-changes-button").click(recentChangesDialog.showDialog);
$("#protected-session-on").click(protectedSessionService.enterProtectedSession);
$("#protected-session-off").click(protectedSessionService.leaveProtectedSession);
$("#enter-protected-session-button").click(protectedSessionService.enterProtectedSession);
$("#leave-protected-session-button").click(protectedSessionService.leaveProtectedSession);
$("#toggle-search-button").click(searchNotesService.toggleSearch);
utils.bindShortcut('ctrl+s', searchNotesService.toggleSearch);
@@ -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", () => {

View File

@@ -1,16 +1,14 @@
import treeService from './tree.js';
import infoService from './info.js';
import protectedSessionHolder from './protected_session_holder.js';
import utils from './utils.js';
import server from './server.js';
function exportSubtree(noteId, format) {
const url = utils.getHost() + "/api/notes/" + noteId + "/export/" + format +
"?protectedSessionId=" + encodeURIComponent(protectedSessionHolder.getProtectedSessionId());
function exportBranch(branchId, type, format) {
const url = utils.getHost() + `/api/notes/${branchId}/export/${type}/${format}?protectedSessionId=` + encodeURIComponent(protectedSessionHolder.getProtectedSessionId());
console.log(url);
utils.download(url);
infoService.showMessage("Export to file has been finished.");
}
let importNoteId;
@@ -47,6 +45,6 @@ $("#import-upload").change(async function() {
});
export default {
exportSubtree,
exportBranch,
importIntoNote
};

View File

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

View File

@@ -5,13 +5,9 @@ function showMessage(message) {
console.debug(utils.now(), "message: ", message);
$.notify({
// options
icon: 'jam jam-check',
message: message
}, {
// options
type: 'success',
delay: 3000
});
}, getNotifySettings('success', 3000));
}
function showAndLogError(message, delay = 10000) {
@@ -25,12 +21,26 @@ function showError(message, delay = 10000) {
$.notify({
// options
icon: 'jam jam-alert',
message: message
}, {
// options
type: 'danger',
}, getNotifySettings('danger', delay));
}
function getNotifySettings(type, delay) {
return {
element: 'body',
type: type,
z_index: 90000,
placement: {
from: "top",
align: "center"
},
animate: {
enter: 'animated fadeInDown',
exit: 'animated fadeOutUp'
},
delay: delay
});
};
}
function throwError(message) {

View File

@@ -76,7 +76,8 @@ function initNoteAutocomplete($el, options) {
hint: false,
autoselect: true,
openOnFocus: true,
minLength: 0
minLength: 0,
tabAutocomplete: false
}, [
{
source: autocompleteSource,

View File

@@ -28,6 +28,7 @@ const $noteDetailWrapper = $("#note-detail-wrapper");
const $noteIdDisplay = $("#note-id-display");
const $childrenOverview = $("#children-overview");
const $scriptArea = $("#note-detail-script-area");
const $savedIndicator = $("#saved-indicator");
let currentNote = null;
@@ -78,6 +79,8 @@ function noteChanged() {
}
isNoteChanged = true;
$savedIndicator.fadeOut();
}
async function reload() {
@@ -120,15 +123,16 @@ async function saveNote() {
protectedSessionHolder.touchProtectedSession();
}
infoService.showMessage("Saved!");
$savedIndicator.fadeIn();
}
async function saveNoteIfChanged() {
if (!isNoteChanged) {
return;
if (isNoteChanged) {
await saveNote();
}
await saveNote();
// make sure indicator is visible in a case there was some race condition.
$savedIndicator.fadeIn();
}
function setNoteBackgroundIfProtected(note) {
@@ -294,7 +298,7 @@ $(document).ready(() => {
// this sends the request asynchronously and doesn't wait for result
$(window).on('beforeunload', () => { saveNoteIfChanged(); }); // don't convert to short form, handler doesn't like returned promise
setInterval(saveNoteIfChanged, 5000);
setInterval(saveNoteIfChanged, 3000);
export default {
reload,

View File

@@ -11,8 +11,8 @@ const $password = $("#protected-session-password");
const $noteDetailWrapper = $("#note-detail-wrapper");
const $protectButton = $("#protect-button");
const $unprotectButton = $("#unprotect-button");
const $protectedSessionOnButton = $("#protected-session-on");
const $protectedSessionOffButton = $("#protected-session-off");
const $enterProtectedSessionButton = $("#enter-protected-session-button");
const $leaveProtectedSessionButton = $("#leave-protected-session-button");
let protectedSessionDeferred = null;
@@ -57,7 +57,7 @@ async function setupProtectedSession() {
const response = await enterProtectedSessionOnServer(password);
if (!response.success) {
infoService.showError("Wrong password.");
infoService.showError("Wrong password.", 3000);
return;
}
@@ -65,9 +65,11 @@ async function setupProtectedSession() {
$dialog.modal("hide");
await noteDetailService.reload();
await treeService.reload();
treeService.reload();
// it's important that tree has been already reloaded at this point
// since detail also uses tree cache (for children overview)
await noteDetailService.reload();
if (protectedSessionDeferred !== null) {
ensureDialogIsClosed($dialog, $password);
@@ -77,8 +79,8 @@ async function setupProtectedSession() {
protectedSessionDeferred.resolve(true);
protectedSessionDeferred = null;
$protectedSessionOnButton.addClass('active');
$protectedSessionOffButton.removeClass('active');
$enterProtectedSessionButton.hide();
$leaveProtectedSessionButton.show();
}
infoService.showMessage("Protected session has been started.");

View File

@@ -12,9 +12,10 @@ function getHeaders() {
// headers need to be lowercase because node.js automatically converts them to lower case
// so hypothetical protectedSessionId becomes protectedsessionid on the backend
// also avoiding using underscores instead of dashes since nginx filters them out by default
return {
protected_session_id: protectedSessionId,
source_id: glob.sourceId
'trilium-protected-session-id': protectedSessionId,
'trilium-source-id': glob.sourceId
};
}

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);
}
}
});
@@ -500,6 +524,12 @@ async function createNote(node, parentNoteId, target, isProtected, saveSelection
if (noteDetailService.getCurrentNoteType() !== 'text') {
saveSelection = false;
}
else {
// just disable this feature altogether - there's a problem that note containing image or table at the beginning
// of the content will be auto-selected by CKEditor and then CTRL-P with no user interaction will automatically save
// the selection - see https://github.com/ckeditor/ckeditor5/issues/1384
saveSelection = false;
}
let title, content;
@@ -539,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') {
@@ -564,8 +594,6 @@ async function createNote(node, parentNoteId, target, isProtected, saveSelection
clearSelectedNodes(); // to unmark previously active node
infoService.showMessage("Created!");
return {note, branch};
}

View File

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

View File

@@ -6,11 +6,11 @@ import protectedSessionService from './protected_session.js';
import treeChangesService from './branches.js';
import treeUtils from './tree_utils.js';
import branchPrefixDialog from '../dialogs/branch_prefix.js';
import exportSubtreeDialog from '../dialogs/export_subtree.js';
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"},
@@ -93,7 +95,7 @@ const contextMenuItems = [
{title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "clipboard"},
{title: "Paste after", cmd: "pasteAfter", uiIcon: "clipboard"},
{title: "----"},
{title: "Export subtree", cmd: "exportSubtree", uiIcon: "arrow-up-right"},
{title: "Export", cmd: "export", uiIcon: "arrow-up-right"},
{title: "Import into note (tar, opml, md, enex)", cmd: "importIntoNote", uiIcon: "arrow-down-left"},
{title: "----"},
{title: "Collapse subtree <kbd>Alt+-</kbd>", cmd: "collapseSubtree", uiIcon: "align-justify"},
@@ -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);
@@ -127,9 +139,14 @@ async function getContextMenuItems(event) {
enableItem("pasteAfter", clipboardIds.length > 0 && isNotRoot && parentNote.type !== 'search');
enableItem("pasteInto", clipboardIds.length > 0 && note.type !== 'search');
enableItem("importIntoNote", note.type !== 'search');
enableItem("exportSubtree", note.type !== 'search');
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();
@@ -179,8 +196,8 @@ function selectContextMenuItem(event, cmd) {
else if (cmd === "delete") {
treeChangesService.deleteNodes(treeService.getSelectedNodes(true));
}
else if (cmd === "exportSubtree") {
exportSubtreeDialog.showDialog();
else if (cmd === "export") {
exportDialog.showDialog("subtree");
}
else if (cmd === "importIntoNote") {
exportService.importIntoNote(node.data.noteId);
@@ -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);
}

View File

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

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

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 {
@@ -620,11 +620,11 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
}
.modalless {
top:10%;
left:50%;
bottom:auto;
right:auto;
margin-left:-300px;
top: 15%;
left: 40%;
bottom: auto;
right: auto;
margin-left: -300px;
}
.multiplicity {
@@ -634,4 +634,74 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
/* this is because bootstrap (?) sets code color to red for some reason */
code {
color: inherit !important;
}
.animated {
animation-duration: 1s;
animation-fill-mode: both;
}
@keyframes fadeInDown {
from {
opacity: 0;
transform: translate3d(0, -100%, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
.fadeInDown {
animation-name: fadeInDown;
}
@keyframes fadeOutUp {
from {
opacity: 1;
}
to {
opacity: 0;
-webkit-transform: translate3d(0, -100%, 0);
transform: translate3d(0, -100%, 0);
}
}
.fadeOutUp {
animation-name: fadeOutUp;
}
div[data-notify="container"] {
text-align: center;
}
#saved-indicator {
position: absolute;
right: 10px;
top: 11px;
font-size: x-large;
color: #777;
z-index: 100;
}
#export-form .form-check {
padding-top: 10px;
padding-bottom: 10px;
}
#export-form .format-choice {
padding-left: 40px;
display: none;
}
#export-form .form-check-label {
padding: 2px;
}
.unhoist-button {
text-decoration: underline !important;
color: blue !important;
cursor: pointer !important;
}

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

View File

@@ -1,28 +1,22 @@
"use strict";
const nativeTarExportService = require('../../services/export/native_tar');
const markdownTarExportService = require('../../services/export/markdown_tar');
const markdownSingleExportService = require('../../services/export/markdown_single');
const tarExportService = require('../../services/export/tar');
const singleExportService = require('../../services/export/single');
const opmlExportService = require('../../services/export/opml');
const repository = require("../../services/repository");
async function exportNote(req, res) {
// entityId maybe either noteId or branchId depending on format
const entityId = req.params.entityId;
const format = req.params.format;
async function exportBranch(req, res) {
const {branchId, type, format} = req.params;
const branch = await repository.getBranch(branchId);
if (format === 'native-tar') {
await nativeTarExportService.exportToTar(await repository.getBranch(entityId), res);
if (type === 'subtree' && (format === 'html' || format === 'markdown')) {
await tarExportService.exportToTar(branch, format, res);
}
else if (format === 'markdown-tar') {
await markdownTarExportService.exportToMarkdown(await repository.getBranch(entityId), res);
}
// export single note without subtree
else if (format === 'markdown-single') {
await markdownSingleExportService.exportSingleMarkdown(await repository.getNote(entityId), res);
else if (type === 'single') {
await singleExportService.exportSingleNote(branch, format, res);
}
else if (format === 'opml') {
await opmlExportService.exportToOpml(await repository.getBranch(entityId), res);
await opmlExportService.exportToOpml(branch, res);
}
else {
return [404, "Unrecognized export format " + format];
@@ -30,5 +24,5 @@ async function exportNote(req, res) {
}
module.exports = {
exportNote
exportBranch
};

View File

@@ -4,7 +4,8 @@ const repository = require('../../services/repository');
const enexImportService = require('../../services/import/enex');
const opmlImportService = require('../../services/import/opml');
const tarImportService = require('../../services/import/tar');
const markdownImportService = require('../../services/import/markdown');
const singleImportService = require('../../services/import/single');
const cls = require('../../services/cls');
const path = require('path');
async function importToBranch(req) {
@@ -23,6 +24,10 @@ async function importToBranch(req) {
const extension = path.extname(file.originalname).toLowerCase();
// running all the event handlers on imported notes (and attributes) is slow
// and may produce unintended consequences
cls.disableEntityEvents();
if (extension === '.tar') {
return await tarImportService.importTar(file.buffer, parentNote);
}
@@ -30,7 +35,10 @@ async function importToBranch(req) {
return await opmlImportService.importOpml(file.buffer, parentNote);
}
else if (extension === '.md') {
return await markdownImportService.importMarkdown(file, parentNote);
return await singleImportService.importMarkdown(file, parentNote);
}
else if (extension === '.html' || extension === '.htm') {
return await singleImportService.importHtml(file, parentNote);
}
else if (extension === '.enex') {
return await enexImportService.importEnex(file, parentNote);

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

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

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

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

View File

@@ -66,7 +66,7 @@ function route(method, path, middleware, routeHandler, resultHandler, transactio
router[method](path, ...middleware, async (req, res, next) => {
try {
const result = await cls.init(async () => {
cls.namespace.set('sourceId', req.headers.source_id);
cls.namespace.set('sourceId', req.headers['trilium-source-id']);
protectedSessionService.setProtectedSessionId(req);
if (transactional) {
@@ -128,7 +128,7 @@ function register(app) {
apiRoute(PUT, '/api/notes/:noteId/clone-to/:parentNoteId', cloningApiRoute.cloneNoteToParent);
apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter);
route(GET, '/api/notes/:entityId/export/:format', [auth.checkApiAuthOrElectron], exportRoute.exportNote);
route(GET, '/api/notes/:branchId/export/:type/:format', [auth.checkApiAuthOrElectron], exportRoute.exportBranch);
route(POST, '/api/notes/:parentNoteId/import', [auth.checkApiAuthOrElectron, uploadMiddleware], importRoute.importToBranch, apiResultHandler);
route(POST, '/api/notes/:parentNoteId/upload', [auth.checkApiAuthOrElectron, uploadMiddleware],
@@ -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);

View File

@@ -6,6 +6,7 @@ const log = require("./log");
const os = require('os');
const fs = require('fs');
const config = require('./config');
const utils = require('./utils');
const template = `[Desktop Entry]
Type=Application
@@ -21,7 +22,9 @@ Terminal=false
* We overwrite this file during every run as it might have been updated.
*/
function installLocalAppIcon() {
if (["win32", "darwin"].includes(os.platform()) || (config.General && config.General.noDesktopIcon)) {
if (!utils.isElectron()
|| ["win32", "darwin"].includes(os.platform())
|| (config.General && config.General.noDesktopIcon)) {
return;
}

View File

@@ -3,7 +3,7 @@
const build = require('./build');
const packageJson = require('../../package');
const APP_DB_VERSION = 119;
const APP_DB_VERSION = 121;
const SYNC_VERSION = 2;
module.exports = {

View File

@@ -225,6 +225,8 @@ function BackendScriptApi(startNote, currentNote, originEntity) {
*/
this.transactional = sql.transactional;
this.sql = sql;
/**
* Trigger tree refresh in all connected clients. This is required when some tree change happens in
* the backend.

View File

@@ -1 +1 @@
module.exports = { buildDate:"2018-11-21T23:47:09+01:00", buildRevision: "3a064934598b70878f6da4c11c0ceb84ef18db57" };
module.exports = { buildDate:"2018-12-18T22:49:48+01:00", buildRevision: "f693dc31e82f1822b7393d12b53afdc0d06f5297" };

View File

@@ -13,6 +13,14 @@ function getSourceId() {
return namespace.get('sourceId');
}
function disableEntityEvents() {
namespace.set('disableEntityEvents', true);
}
function isEntityEventsDisabled() {
return !!namespace.get('disableEntityEvents');
}
function reset() {
clsHooked.reset();
}
@@ -22,5 +30,7 @@ module.exports = {
wrap,
namespace,
getSourceId,
disableEntityEvents,
isEntityEventsDisabled,
reset
};

View File

@@ -1,14 +1,64 @@
"use strict";
/*
* This file resolves trilium data path in this order of priority:
* - if TRILIUM_DATA_DIR environment variable exists, then its value is used as the path
* - if "trilium-data" dir exists directly in the home dir, then it is used
* - based on OS convention, if the "app data directory" exists, we'll use or create "trilium-data" directory there
* - as a fallback if previous step fails, we'll use home dir
*/
const os = require('os');
const fs = require('fs');
const TRILIUM_DATA_DIR = process.env.TRILIUM_DATA_DIR || os.homedir() + "/trilium-data";
function getAppDataDir() {
let appDataDir = os.homedir(); // fallback if OS is not recognized
if (!fs.existsSync(TRILIUM_DATA_DIR)) {
fs.mkdirSync(TRILIUM_DATA_DIR, 0o700);
if (os.platform() === 'win32') {
appDataDir = process.env.APPDATA;
}
else if (os.platform() === 'linux') {
appDataDir = os.homedir() + '/.local/share';
}
else if (os.platform() === 'darwin') {
appDataDir = os.homedir() + '/Library/Application Support';
}
if (!fs.existsSync(appDataDir)) {
// expected app data path doesn't exist, let's use fallback
appDataDir = os.homedir();
}
return appDataDir;
}
const DIR_NAME = 'trilium-data';
function getTriliumDataDir() {
if (process.env.TRILIUM_DATA_DIR) {
return process.env.TRILIUM_DATA_DIR;
}
const homePath = os.homedir() + "/" + DIR_NAME;
if (fs.existsSync(homePath)) {
return homePath;
}
const appDataPath = getAppDataDir() + '/' + DIR_NAME;
if (!fs.existsSync(appDataPath)) {
fs.mkdirSync(appDataPath, 0o700);
}
return appDataPath;
}
const TRILIUM_DATA_DIR = getTriliumDataDir();
// not necessary to log this since if we have logs we already know where data dir is.
console.log("Using data dir:", TRILIUM_DATA_DIR);
const DOCUMENT_PATH = TRILIUM_DATA_DIR + "/document.db";
const BACKUP_DIR = TRILIUM_DATA_DIR + "/backup";
const LOG_DIR = TRILIUM_DATA_DIR + "/log";

View File

@@ -1,31 +0,0 @@
"use strict";
const sanitize = require("sanitize-filename");
const TurndownService = require('turndown');
async function exportSingleMarkdown(note, res) {
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"');
res.setHeader('Content-Type', 'text/markdown; charset=UTF-8');
res.send(markdown);
}
module.exports = {
exportSingleMarkdown
};

View File

@@ -1,91 +0,0 @@
"use strict";
const tar = require('tar-stream');
const TurndownService = require('turndown');
const sanitize = require("sanitize-filename");
const markdownSingleExportService = require('../../services/export/markdown_single');
async function exportToMarkdown(branch, res) {
const note = await branch.getNote();
if (!await note.hasChildren()) {
await markdownSingleExportService.exportSingleMarkdown(note, res);
return;
}
const turndownService = new TurndownService();
const pack = tar.pack();
const name = await exportNoteInner(note, '');
async function exportNoteInner(note, directory) {
const childFileName = directory + sanitize(note.title);
if (await note.hasLabel('excludeFromExport')) {
return;
}
saveNote(childFileName, note);
const childNotes = await note.getChildNotes();
if (childNotes.length > 0) {
saveDirectory(childFileName);
}
for (const childNote of childNotes) {
await exportNoteInner(childNote, childFileName + "/");
}
return childFileName;
}
function saveTextNote(childFileName, note) {
if (note.content.trim().length === 0) {
return;
}
let markdown;
if (note.type === 'code') {
markdown = '```\n' + note.content + "\n```";
}
else if (note.type === 'text') {
markdown = turndownService.turndown(note.content);
}
else {
// other note types are not supported
return;
}
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'});
}
pack.finalize();
res.setHeader('Content-Disposition', 'file; filename="' + name + '.tar"');
res.setHeader('Content-Type', 'application/tar');
pack.pipe(res);
}
module.exports = {
exportToMarkdown
};

View File

@@ -1,103 +0,0 @@
"use strict";
const html = require('html');
const native_tar = require('tar-stream');
const sanitize = require("sanitize-filename");
async function exportToTar(branch, res) {
const pack = native_tar.pack();
const exportedNoteIds = [];
const name = await exportNoteInner(branch, '');
async function exportNoteInner(branch, directory) {
const note = await branch.getNote();
const childFileName = directory + sanitize(note.title);
if (exportedNoteIds.includes(note.noteId)) {
saveMetadataFile(childFileName, {
version: 1,
clone: true,
noteId: note.noteId,
prefix: branch.prefix
});
return;
}
const metadata = {
version: 1,
clone: false,
noteId: note.noteId,
title: note.title,
prefix: branch.prefix,
isExpanded: branch.isExpanded,
type: note.type,
mime: note.mime,
// we don't export dateCreated and dateModified of any entity since that would be a bit misleading
attributes: (await note.getOwnedAttributes()).map(attribute => {
return {
type: attribute.type,
name: attribute.name,
value: attribute.value,
isInheritable: attribute.isInheritable,
position: attribute.position
};
}),
links: (await note.getLinks()).map(link => {
return {
type: link.type,
targetNoteId: link.targetNoteId
}
})
};
if (await note.hasLabel('excludeFromExport')) {
return;
}
saveMetadataFile(childFileName, metadata);
saveDataFile(childFileName, note);
exportedNoteIds.push(note.noteId);
const childBranches = await note.getChildBranches();
if (childBranches.length > 0) {
saveDirectory(childFileName);
}
for (const childBranch of childBranches) {
await exportNoteInner(childBranch, childFileName + "/");
}
return childFileName;
}
function saveDataFile(childFileName, note) {
const content = note.type === 'text' ? html.prettyPrint(note.content, {indent_size: 2}) : note.content;
pack.entry({name: childFileName + ".dat", size: content.length}, content);
}
function saveMetadataFile(childFileName, metadata) {
const metadataJson = JSON.stringify(metadata, null, '\t');
pack.entry({name: childFileName + ".meta", size: metadataJson.length}, metadataJson);
}
function saveDirectory(childFileName) {
pack.entry({name: childFileName, type: 'directory'});
}
pack.finalize();
res.setHeader('Content-Disposition', 'file; filename="' + name + '.tar"');
res.setHeader('Content-Type', 'application/tar');
pack.pipe(res);
}
module.exports = {
exportToTar
};

View File

@@ -0,0 +1,57 @@
"use strict";
const sanitize = require("sanitize-filename");
const TurndownService = require('turndown');
const mimeTypes = require('mime-types');
const html = require('html');
async function exportSingleNote(branch, format, res) {
const note = await branch.getNote();
if (note.type === 'image' || note.type === 'file') {
return [400, `Note type ${note.type} cannot be exported as single file.`];
}
if (format !== 'html' && format !== 'markdown') {
return [400, 'Unrecognized format ' + format];
}
let payload, extension, mime;
if (note.type === 'text') {
if (format === 'html') {
payload = html.prettyPrint(note.content, {indent_size: 2});
extension = 'html';
mime = 'text/html';
}
else if (format === 'markdown') {
const turndownService = new TurndownService();
payload = turndownService.turndown(note.content);
extension = 'md';
mime = 'text/markdown'
}
}
else if (note.type === 'code') {
payload = note.content;
extension = mimeTypes.extension(note.mime) || 'code';
mime = note.mime;
}
else if (note.type === 'relation-map' || note.type === 'search') {
payload = note.content;
extension = 'json';
mime = 'application/json';
}
const name = sanitize(note.title);
console.log(name, extension, mime);
res.setHeader('Content-Disposition', `file; filename="${name}.${extension}"`);
res.setHeader('Content-Type', mime + '; charset=UTF-8');
res.send(payload);
}
module.exports = {
exportSingleNote
};

232
src/services/export/tar.js Normal file
View File

@@ -0,0 +1,232 @@
"use strict";
const html = require('html');
const repository = require('../repository');
const tar = require('tar-stream');
const path = require('path');
const sanitize = require("sanitize-filename");
const mimeTypes = require('mime-types');
const TurndownService = require('turndown');
const packageInfo = require('../../../package.json');
/**
* @param format - 'html' or 'markdown'
*/
async function exportToTar(branch, format, res) {
let turndownService = format === 'markdown' ? new TurndownService() : null;
const pack = tar.pack();
const noteIdToMeta = {};
function getUniqueFilename(existingFileNames, fileName) {
const lcFileName = fileName.toLowerCase();
if (lcFileName in existingFileNames) {
let index;
let newName;
do {
index = existingFileNames[lcFileName]++;
newName = lcFileName + "_" + index;
}
while (newName in existingFileNames);
return fileName + "_" + index;
}
else {
existingFileNames[lcFileName] = 1;
return fileName;
}
}
function getDataFileName(note, baseFileName, existingFileNames) {
let extension;
if (note.type === 'text' && format === 'markdown') {
extension = 'md';
}
else if (note.mime === 'application/x-javascript' || note.mime === 'text/javascript') {
extension = 'js';
}
else {
extension = mimeTypes.extension(note.mime) || "dat";
}
let fileName = baseFileName;
const existingExtension = path.extname(fileName).toLowerCase();
// if the note is already named with extension (e.g. "jquery.js"), then it's silly to append exact same extension again
if (existingExtension !== extension) {
fileName += "." + extension;
}
return getUniqueFilename(existingFileNames, fileName);
}
async function getNote(branch, existingFileNames) {
const note = await branch.getNote();
if (await note.hasLabel('excludeFromExport')) {
return;
}
const baseFileName = branch.prefix ? (branch.prefix + ' - ' + note.title) : note.title;
if (note.noteId in noteIdToMeta) {
const sanitizedFileName = sanitize(baseFileName + ".clone");
const fileName = getUniqueFilename(existingFileNames, sanitizedFileName);
return {
isClone: true,
noteId: note.noteId,
prefix: branch.prefix,
dataFileName: fileName
};
}
const meta = {
isClone: false,
noteId: note.noteId,
title: note.title,
notePosition: branch.notePosition,
prefix: branch.prefix,
isExpanded: branch.isExpanded,
type: note.type,
mime: note.mime,
// we don't export dateCreated and dateModified of any entity since that would be a bit misleading
attributes: (await note.getOwnedAttributes()).map(attribute => {
return {
type: attribute.type,
name: attribute.name,
value: attribute.value,
isInheritable: attribute.isInheritable,
position: attribute.position
};
}),
links: (await note.getLinks()).map(link => {
return {
type: link.type,
targetNoteId: link.targetNoteId
}
})
};
if (note.type === 'text') {
meta.format = format;
}
noteIdToMeta[note.noteId] = meta;
const childBranches = await note.getChildBranches();
// if it's a leaf then we'll export it even if it's empty
if (note.content.length > 0 || childBranches.length === 0) {
meta.dataFileName = getDataFileName(note, baseFileName, existingFileNames);
}
if (childBranches.length > 0) {
meta.dirFileName = getUniqueFilename(existingFileNames, baseFileName);
meta.children = [];
// namespace is shared by children in the same note
const childExistingNames = {};
for (const childBranch of childBranches) {
const note = await getNote(childBranch, childExistingNames);
// can be undefined if export is disabled for this note
if (note) {
meta.children.push(note);
}
}
}
return meta;
}
function prepareContent(note, format) {
if (format === 'html') {
return html.prettyPrint(note.content, {indent_size: 2});
}
else if (format === 'markdown') {
return turndownService.turndown(note.content);
}
else {
return note.content;
}
}
// noteId => file path
const notePaths = {};
async function saveNote(noteMeta, path) {
if (noteMeta.isClone) {
const content = "Note is present at " + notePaths[noteMeta.noteId];
pack.entry({name: path + noteMeta.dataFileName, size: content.length}, content);
return;
}
const note = await repository.getNote(noteMeta.noteId);
notePaths[note.noteId] = path + (noteMeta.dataFileName || noteMeta.dirFileName);
if (noteMeta.dataFileName) {
const content = prepareContent(note, noteMeta.format);
pack.entry({name: path + noteMeta.dataFileName, size: content.length}, content);
}
if (noteMeta.children && noteMeta.children.length > 0) {
const directoryPath = path + noteMeta.dirFileName;
pack.entry({name: directoryPath, type: 'directory'});
for (const childMeta of noteMeta.children) {
await saveNote(childMeta, directoryPath + '/');
}
}
}
const metaFile = {
formatVersion: 1,
appVersion: packageInfo.version,
files: [
await getNote(branch, [])
]
};
for (const noteMeta of Object.values(noteIdToMeta)) {
// filter out relations and links which are not inside this export
noteMeta.attributes = noteMeta.attributes.filter(attr => attr.type !== 'relation' || attr.value in noteIdToMeta);
noteMeta.links = noteMeta.links.filter(link => link.targetNoteId in noteIdToMeta);
}
if (!metaFile.files[0]) { // corner case of disabled export for exported note
res.sendStatus(400);
return;
}
const metaFileJson = JSON.stringify(metaFile, null, '\t');
pack.entry({name: "!!!meta.json", size: metaFileJson.length}, metaFileJson);
await saveNote(metaFile.files[0], '');
pack.finalize();
const note = await branch.getNote();
const tarFileName = sanitize((branch.prefix ? (branch.prefix + " - ") : "") + note.title);
res.setHeader('Content-Disposition', `file; filename="${tarFileName}.tar"`);
res.setHeader('Content-Type', 'application/tar');
pack.pipe(res);
}
module.exports = {
exportToTar
};

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

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

View File

@@ -1,30 +0,0 @@
"use strict";
// note that this is for import of single markdown file only - for archive/structure of markdown files
// see tar export/import
const noteService = require('../../services/notes');
const commonmark = require('commonmark');
async function importMarkdown(file, parentNote) {
const markdownContent = file.buffer.toString("UTF-8");
const reader = new commonmark.Parser();
const writer = new commonmark.HtmlRenderer();
const parsed = reader.parse(markdownContent);
const htmlContent = writer.render(parsed);
const title = file.originalname.substr(0, file.originalname.length - 3); // strip .md extension
const {note} = await noteService.createNote(parentNote.noteId, title, htmlContent, {
type: 'text',
mime: 'text/html'
});
return note;
}
module.exports = {
importMarkdown
};

View File

@@ -0,0 +1,47 @@
"use strict";
const noteService = require('../../services/notes');
const commonmark = require('commonmark');
const path = require('path');
async function importMarkdown(file, parentNote) {
const markdownContent = file.buffer.toString("UTF-8");
const reader = new commonmark.Parser();
const writer = new commonmark.HtmlRenderer();
const parsed = reader.parse(markdownContent);
const htmlContent = writer.render(parsed);
const title = getFileNameWithoutExtension(file.originalname);
const {note} = await noteService.createNote(parentNote.noteId, title, htmlContent, {
type: 'text',
mime: 'text/html'
});
return note;
}
async function importHtml(file, parentNote) {
const title = getFileNameWithoutExtension(file.originalname);
const content = file.buffer.toString("UTF-8");
const {note} = await noteService.createNote(parentNote.noteId, title, content, {
type: 'text',
mime: 'text/html'
});
return note;
}
function getFileNameWithoutExtension(filePath) {
const extension = path.extname(filePath);
return filePath.substr(0, filePath.length - extension.length);
}
module.exports = {
importMarkdown,
importHtml
};

View File

@@ -2,30 +2,32 @@
const Attribute = require('../../entities/attribute');
const Link = require('../../entities/link');
const log = require('../../services/log');
const utils = require('../../services/utils');
const log = require('../../services/log');
const repository = require('../../services/repository');
const noteService = require('../../services/notes');
const Branch = require('../../entities/branch');
const tar = require('tar-stream');
const stream = require('stream');
const path = require('path');
const commonmark = require('commonmark');
const mimeTypes = require('mime-types');
async function importTar(fileBuffer, parentNote) {
const files = await parseImportFile(fileBuffer);
async function importTar(fileBuffer, importRootNote) {
// maps from original noteId (in tar file) to newly generated noteId
const noteIdMap = {};
const attributes = [];
const links = [];
// path => noteId
const createdPaths = { '/': importRootNote.noteId, '\\': importRootNote.noteId };
const mdReader = new commonmark.Parser();
const mdWriter = new commonmark.HtmlRenderer();
let metaFile = null;
let firstNote = null;
const ctx = {
// maps from original noteId (in tar file) to newly generated noteId
noteIdMap: {},
// new noteIds of notes which were actually created (not just referenced)
createdNoteIds: [],
attributes: [],
links: [],
reader: new commonmark.Parser(),
writer: new commonmark.HtmlRenderer()
};
const extract = tar.extract();
ctx.getNewNoteId = function(origNoteId) {
function getNewNoteId(origNoteId) {
// in case the original noteId is empty. This probably shouldn't happen, but still good to have this precaution
if (!origNoteId.trim()) {
return "";
@@ -36,107 +38,274 @@ async function importTar(fileBuffer, parentNote) {
return origNoteId;
}
if (!ctx.noteIdMap[origNoteId]) {
ctx.noteIdMap[origNoteId] = utils.newEntityId();
if (!noteIdMap[origNoteId]) {
noteIdMap[origNoteId] = utils.newEntityId();
}
return ctx.noteIdMap[origNoteId];
};
return noteIdMap[origNoteId];
}
function getMeta(filePath) {
if (!metaFile) {
return {};
}
const note = await importNotes(ctx, files, parentNote.noteId);
const pathSegments = filePath.split(/[\/\\]/g);
// we save attributes and links after importing notes because we need to check that target noteIds
// have been really created (relation/links with targets outside of the export are not created)
let cursor = {
isImportRoot: true,
children: metaFile.files
};
for (const attr of ctx.attributes) {
if (attr.type === 'relation') {
attr.value = ctx.getNewNoteId(attr.value);
let parent;
if (!ctx.createdNoteIds.includes(attr.value)) {
// relation targets note outside of the export
continue;
for (const segment of pathSegments) {
if (!cursor || !cursor.children || cursor.children.length === 0) {
return {};
}
parent = cursor;
cursor = cursor.children.find(file => file.dataFileName === segment || file.dirFileName === segment);
}
return {
parentNoteMeta: parent,
noteMeta: cursor
};
}
function getParentNoteId(filePath, parentNoteMeta) {
let parentNoteId;
if (parentNoteMeta) {
parentNoteId = parentNoteMeta.isImportRoot ? importRootNote.noteId : getNewNoteId(parentNoteMeta.noteId);
}
else {
const parentPath = path.dirname(filePath);
if (parentPath === '.') {
parentNoteId = importRootNote.noteId;
}
else if (parentPath in createdPaths) {
parentNoteId = createdPaths[parentPath];
}
else {
throw new Error(`Could not find existing path ${parentPath} for ${filePath}.`);
}
}
await new Attribute(attr).save();
return parentNoteId;
}
for (const link of ctx.links) {
link.targetNoteId = ctx.getNewNoteId(link.targetNoteId);
if (!ctx.createdNoteIds.includes(link.targetNoteId)) {
// link targets note outside of the export
continue;
}
await new Link(link).save();
}
return note;
}
function getFileName(name) {
let key;
if (name.endsWith(".dat")) {
key = "data";
name = name.substr(0, name.length - 4);
}
else if (name.endsWith(".md")) {
key = "markdown";
name = name.substr(0, name.length - 3);
}
else if (name.endsWith((".meta"))) {
key = "meta";
name = name.substr(0, name.length - 5);
}
else {
log.error("Unknown file type in import: " + name);
}
return {name, key};
}
async function parseImportFile(fileBuffer) {
const fileMap = {};
const files = [];
const extract = tar.extract();
extract.on('entry', function(header, stream, next) {
let name, key;
if (header.type === 'file') {
({name, key} = getFileName(header.name));
}
else if (header.type === 'directory') {
// directory entries in tar often end with directory separator
name = (header.name.endsWith("/") || header.name.endsWith("\\")) ? header.name.substr(0, header.name.length - 1) : header.name;
key = 'directory';
function getNoteTitle(filePath, noteMeta) {
if (noteMeta) {
return noteMeta.title;
}
else {
log.error("Unrecognized tar entry: " + JSON.stringify(header));
const basename = path.basename(filePath);
return getTextFileWithoutExtension(basename);
}
}
function getNoteId(noteMeta, filePath) {
if (noteMeta) {
return getNewNoteId(noteMeta.noteId);
}
else {
const filePathNoExt = getTextFileWithoutExtension(filePath);
if (filePathNoExt in createdPaths) {
return createdPaths[filePathNoExt];
}
else {
return utils.newEntityId();
}
}
}
function detectFileTypeAndMime(filePath) {
const mime = mimeTypes.lookup(filePath);
let type = 'file';
if (mime) {
if (mime === 'text/html' || mime === 'text/markdown') {
type = 'text';
}
else if (mime.startsWith('image/')) {
type = 'image';
}
}
return { type, mime };
}
async function saveAttributesAndLinks(note, noteMeta) {
if (!noteMeta) {
return;
}
let file = fileMap[name];
for (const attr of noteMeta.attributes) {
attr.noteId = note.noteId;
if (!file) {
file = fileMap[name] = {
name: path.basename(name),
children: []
};
let parentFileName = path.dirname(header.name);
if (parentFileName && parentFileName !== '.') {
fileMap[parentFileName].children.push(file);
if (attr.type === 'relation') {
attr.value = getNewNoteId(attr.value);
}
else {
files.push(file);
attributes.push(attr);
}
for (const link of noteMeta.links) {
link.noteId = note.noteId;
link.targetNoteId = getNewNoteId(link.targetNoteId);
links.push(link);
}
}
async function saveDirectory(filePath) {
const { parentNoteMeta, noteMeta } = getMeta(filePath);
const noteId = getNoteId(noteMeta, filePath);
const noteTitle = getNoteTitle(filePath, noteMeta);
const parentNoteId = getParentNoteId(filePath, parentNoteMeta);
let note = await repository.getNote(noteId);
if (note) {
return;
}
({note} = await noteService.createNote(parentNoteId, noteTitle, '', {
noteId,
type: noteMeta ? noteMeta.type : 'text',
mime: noteMeta ? noteMeta.mime : 'text/html',
prefix: noteMeta ? noteMeta.prefix : '',
isExpanded: noteMeta ? noteMeta.isExpanded : false
}));
await saveAttributesAndLinks(note, noteMeta);
if (!firstNote) {
firstNote = note;
}
createdPaths[filePath] = noteId;
}
function getTextFileWithoutExtension(filePath) {
const extension = path.extname(filePath).toLowerCase();
if (extension === '.md' || extension === '.html') {
return filePath.substr(0, filePath.length - extension.length);
}
else {
return filePath;
}
}
async function saveNote(filePath, content) {
const {parentNoteMeta, noteMeta} = getMeta(filePath);
const noteId = getNoteId(noteMeta, filePath);
const parentNoteId = getParentNoteId(filePath, parentNoteMeta);
if (noteMeta && noteMeta.isClone) {
await new Branch({
noteId,
parentNoteId,
isExpanded: noteMeta.isExpanded,
prefix: noteMeta.prefix,
notePosition: noteMeta.notePosition
}).save();
return;
}
const {type, mime} = noteMeta ? noteMeta : detectFileTypeAndMime(filePath);
if (type !== 'file' && type !== 'image') {
content = content.toString("UTF-8");
if (noteMeta) {
// this will replace all internal links (<a> and <img>) inside the body
// links pointing outside the export will be broken and changed (ctx.getNewNoteId() will still assign new noteId)
for (const link of noteMeta.links || []) {
// no need to escape the regexp find string since it's a noteId which doesn't contain any special characters
content = content.replace(new RegExp(link.targetNoteId, "g"), getNewNoteId(link.targetNoteId));
}
}
}
if ((noteMeta && noteMeta.format === 'markdown') || (!noteMeta && mime === 'text/markdown')) {
const parsed = mdReader.parse(content);
content = mdWriter.render(parsed);
}
let note = await repository.getNote(noteId);
if (note) {
note.content = content;
await note.save();
}
else {
const noteTitle = getNoteTitle(filePath, noteMeta);
({note} = await noteService.createNote(parentNoteId, noteTitle, content, {
noteId,
type,
mime,
prefix: noteMeta ? noteMeta.prefix : '',
isExpanded: noteMeta ? noteMeta.isExpanded : false,
notePosition: noteMeta ? noteMeta.notePosition : false
}));
await saveAttributesAndLinks(note, noteMeta);
if (!noteMeta && (type === 'file' || type === 'image')) {
attributes.push({
noteId,
type: 'label',
name: 'originalFileName',
value: path.basename(filePath)
});
attributes.push({
noteId,
type: 'label',
name: 'fileSize',
value: content.byteLength
});
}
if (!firstNote) {
firstNote = note;
}
if (type === 'text') {
filePath = getTextFileWithoutExtension(filePath);
}
createdPaths[filePath] = noteId;
}
}
/** @return path without leading or trailing slash and backslashes converted to forward ones*/
function normalizeFilePath(filePath) {
filePath = filePath.replace(/\\/g, "/");
if (filePath.startsWith("/")) {
filePath = filePath.substr(1);
}
if (filePath.endsWith("/")) {
filePath = filePath.substr(0, filePath.length - 1);
}
return filePath;
}
extract.on('entry', function(header, stream, next) {
const chunks = [];
stream.on("data", function (chunk) {
@@ -147,11 +316,22 @@ async function parseImportFile(fileBuffer) {
// stream is the content body (might be an empty stream)
// call next when you are done with this entry
stream.on('end', function() {
file[key] = Buffer.concat(chunks);
stream.on('end', async function() {
let filePath = normalizeFilePath(header.name);
if (key === "meta") {
file[key] = JSON.parse(file[key].toString("UTF-8"));
const content = Buffer.concat(chunks);
if (filePath === '!!!meta.json') {
metaFile = JSON.parse(content.toString("UTF-8"));
}
else if (header.type === 'directory') {
await saveDirectory(filePath);
}
else if (header.type === 'file') {
await saveNote(filePath, content);
}
else {
log.info("Ignoring tar import entry with type " + header.type);
}
next(); // ready for next entry
@@ -161,8 +341,34 @@ async function parseImportFile(fileBuffer) {
});
return new Promise(resolve => {
extract.on('finish', function() {
resolve(files);
extract.on('finish', async function() {
const createdNoteIds = {};
for (const path in createdPaths) {
createdNoteIds[createdPaths[path]] = true;
}
// we're saving attributes and links only now so that all relation and link target notes
// are already in the database (we don't want to have "broken" relations, not even transitionally)
for (const attr of attributes) {
if (attr.type !== 'relation' || attr.value in createdNoteIds) {
await new Attribute(attr).save();
}
else {
log.info("Relation not imported since target note doesn't exist: " + JSON.stringify(attr));
}
}
for (const link of links) {
if (link.targetNoteId in createdNoteIds) {
await new Link(link).save();
}
else {
log.info("Link not imported since target note doesn't exist: " + JSON.stringify(link));
}
}
resolve(firstNote);
});
const bufferStream = new stream.PassThrough();
@@ -172,96 +378,6 @@ async function parseImportFile(fileBuffer) {
});
}
async function importNotes(ctx, files, parentNoteId) {
let returnNote = null;
for (const file of files) {
let note;
if (!file.meta) {
let content = '';
if (file.data) {
content = file.data.toString("UTF-8");
}
else if (file.markdown) {
const parsed = ctx.reader.parse(file.markdown.toString("UTF-8"));
content = ctx.writer.render(parsed);
}
note = (await noteService.createNote(parentNoteId, file.name, content, {
type: 'text',
mime: 'text/html'
})).note;
}
else {
if (file.meta.version !== 1) {
throw new Error("Can't read meta data version " + file.meta.version);
}
if (file.meta.clone) {
await new Branch({
parentNoteId: parentNoteId,
noteId: ctx.getNewNoteId(file.meta.noteId),
prefix: file.meta.prefix,
isExpanded: !!file.meta.isExpanded
}).save();
continue;
}
if (file.meta.type !== 'file' && file.meta.type !== 'image') {
file.data = file.data.toString("UTF-8");
// this will replace all internal links (<a> and <img>) inside the body
// links pointing outside the export will be broken and changed (ctx.getNewNoteId() will still assign new noteId)
for (const link of file.meta.links || []) {
// no need to escape the regexp find string since it's a noteId which doesn't contain any special characters
file.data = file.data.replace(new RegExp(link.targetNoteId, "g"), ctx.getNewNoteId(link.targetNoteId));
}
}
note = (await noteService.createNote(parentNoteId, file.meta.title, file.data, {
noteId: ctx.getNewNoteId(file.meta.noteId),
type: file.meta.type,
mime: file.meta.mime,
prefix: file.meta.prefix,
isExpanded: !!file.meta.isExpanded
})).note;
ctx.createdNoteIds.push(note.noteId);
for (const attribute of file.meta.attributes || []) {
ctx.attributes.push({
noteId: note.noteId,
type: attribute.type,
name: attribute.name,
value: attribute.value,
isInheritable: attribute.isInheritable,
position: attribute.position
});
}
for (const link of file.meta.links || []) {
ctx.links.push({
noteId: note.noteId,
type: link.type,
targetNoteId: link.targetNoteId
});
}
}
// first created note will be activated after import
returnNote = returnNote || note;
if (file.children.length > 0) {
await importNotes(ctx, file.children, note.noteId);
}
}
return returnNote;
}
module.exports = {
importTar
};

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 = {};
@@ -38,7 +39,8 @@ async function load() {
function highlightResults(results, allTokens) {
// we remove < signs because they can cause trouble in matching and overwriting existing highlighted chunks
// which would make the resulting HTML string invalid.
allTokens = allTokens.map(token => token.replace('/</g', ''));
// { and } are used for marking <b> and </b> tag (to avoid matches on single 'b' character)
allTokens = allTokens.map(token => token.replace('/[<\{\}]/g', ''));
// sort by the longest so we first highlight longest matches
allTokens.sort((a, b) => a.length > b.length ? -1 : 1);
@@ -51,12 +53,18 @@ function highlightResults(results, allTokens) {
const tokenRegex = new RegExp("(" + utils.escapeRegExp(token) + ")", "gi");
for (const result of results) {
result.highlighted = result.highlighted.replace(tokenRegex, "<b>$1</b>");
result.highlighted = result.highlighted.replace(tokenRegex, "{$1}");
}
}
for (const result of results) {
result.highlighted = result.highlighted
.replace(/{/g, "<b>")
.replace(/}/g, "</b>");
}
}
function findNotes(query) {
async function findNotes(query) {
if (!noteTitles || !query.length) {
return [];
}
@@ -65,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);
@@ -113,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) => {
@@ -207,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;
}
@@ -239,6 +255,10 @@ function getSomePath(noteId, path) {
path.push(noteId);
path.reverse();
if (!path.includes(hoistedNoteService.getHoistedNoteId())) {
return false;
}
return path;
}
@@ -292,14 +312,7 @@ eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entity})
delete childToParent[note.noteId];
}
else {
if (note.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
protectedNoteTitles[note.noteId] = protectedSessionService.decryptNoteTitle(note.noteId, note.title);
}
}
else {
noteTitles[note.noteId] = note.title;
}
noteTitles[note.noteId] = note.title;
}
}
else if (entityName === 'branches') {
@@ -327,11 +340,11 @@ eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entity})
if (attribute.type === 'label' && attribute.name === 'archived') {
// we're not using label object directly, since there might be other non-deleted archived label
const hideLabel = await repository.getEntity(`SELECT * FROM attributes WHERE isDeleted = 0 AND type = 'label'
const archivedLabel = await repository.getEntity(`SELECT * FROM attributes WHERE isDeleted = 0 AND type = 'label'
AND name = 'archived' AND noteId = ?`, [attribute.noteId]);
if (hideLabel) {
archived[attribute.noteId] = hideLabel.isInheritable ? 1 : 0;
if (archivedLabel) {
archived[attribute.noteId] = archivedLabel.isInheritable ? 1 : 0;
}
else {
delete archived[attribute.noteId];

View File

@@ -49,10 +49,21 @@ async function triggerNoteTitleChanged(note) {
* FIXME: noteData has mandatory property "target", it might be better to add it as parameter to reflect this
*/
async function createNewNote(parentNoteId, noteData) {
const newNotePos = await getNewNotePosition(parentNoteId, noteData);
let newNotePos;
if (noteData.notePosition !== undefined) {
newNotePos = noteData.notePosition;
}
else {
newNotePos = await getNewNotePosition(parentNoteId, noteData);
}
const parentNote = await repository.getNote(parentNoteId);
if (!parentNote) {
throw new Error(`Parent note ${parentNoteId} not found.`);
}
if (!noteData.type) {
if (parentNote.type === 'text' || parentNote.type === 'code') {
noteData.type = parentNote.type;
@@ -126,7 +137,8 @@ async function createNote(parentNoteId, title, content = "", extraOptions = {})
type: extraOptions.type,
mime: extraOptions.mime,
dateCreated: extraOptions.dateCreated,
isExpanded: extraOptions.isExpanded
isExpanded: extraOptions.isExpanded,
notePosition: extraOptions.notePosition
};
if (extraOptions.json && !noteData.type) {

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

View File

@@ -15,7 +15,7 @@ function setDataKey(decryptedDataKey) {
}
function setProtectedSessionId(req) {
cls.namespace.set('protectedSessionId', req.headers.protected_session_id);
cls.namespace.set('protectedSessionId', req.headers['trilium-protected-session-id']);
}
function getProtectedSessionId() {

View File

@@ -3,6 +3,7 @@
const sql = require('./sql');
const syncTableService = require('../services/sync_table');
const eventService = require('./events');
const cls = require('./cls');
let entityConstructor;
@@ -73,13 +74,13 @@ async function updateEntity(entity) {
const clone = Object.assign({}, entity);
// transient properties not supposed to be persisted
delete clone.jsonContent;
delete clone.isOwned;
// this check requires that updatePojo is not static
if (entity.updatePojo) {
await entity.updatePojo(clone);
}
// indicates whether entity actually changed
delete clone.isChanged;
delete clone.origParentNoteId;
delete clone.isContentAvailable;
delete clone.__attributeCache;
for (const key in clone) {
// !isBuffer is for images and attachments
@@ -93,20 +94,24 @@ async function updateEntity(entity) {
const primaryKey = entity[primaryKeyName];
if (entity.isChanged && (entityName !== 'options' || entity.isSynced)) {
await syncTableService.addEntitySync(entityName, primaryKey);
const eventPayload = {
entityName,
entity
};
if (isNewEntity && !entity.isDeleted) {
await eventService.emit(eventService.ENTITY_CREATED, eventPayload);
if (entity.isChanged) {
if (entityName !== 'options' || entity.isSynced) {
await syncTableService.addEntitySync(entityName, primaryKey);
}
// it seems to be better to handle deletion and update separately
await eventService.emit(entity.isDeleted ? eventService.ENTITY_DELETED : eventService.ENTITY_CHANGED, eventPayload);
if (!cls.isEntityEventsDisabled()) {
const eventPayload = {
entityName,
entity
};
if (isNewEntity && !entity.isDeleted) {
await eventService.emit(eventService.ENTITY_CREATED, eventPayload);
}
// it seems to be better to handle deletion and update separately
await eventService.emit(entity.isDeleted ? eventService.ENTITY_DELETED : eventService.ENTITY_CHANGED, eventPayload);
}
}
});
}

118
src/services/request.js Normal file
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
};

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

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

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

View File

@@ -1,4 +1,6 @@
<div id="note-detail-wrapper">
<span id="saved-indicator" title="All changes have been saved" class="jam jam-check"></span>
<div id="note-detail-script-area"></div>
<table id="note-detail-promoted-attributes"></table>

View File

@@ -11,7 +11,7 @@
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;">
<div class="btn-group floating-button" style="right: 40px;">
<button type="button"
class="btn icon-button jam jam-search-plus"
title="Zoom In"

View File

@@ -64,6 +64,11 @@
data-bind="checked: labelDefinition.isPromoted"/>
Promoted
</label>
<div data-bind="visible: labelDefinition.labelType === 'number'"
title="Precision of floating point numbers - 0 means effectively integer, 2 allows entering e.g. 1.23">
Number precision: <input type="number" min="0" max="9" data-bind="value: labelDefinition.numberPrecision" style="width: 50px;"/>
</div>
</div>
<div data-bind="visible: type == 'relation-definition'">

View File

@@ -0,0 +1,67 @@
<div id="export-dialog" class="modal fade mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Export note</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form id="export-form">
<div class="modal-body">
<div class="form-check">
<input class="form-check-input" type="radio" name="export-type" id="export-type-subtree" value="subtree">
<label class="form-check-label" for="export-type-subtree">this note and all of its descendants</label>
</div>
<div id="export-subtree-formats" class="format-choice">
<div class="form-check">
<input class="form-check-input" type="radio" name="export-subtree-format" id="export-subtree-format-html"
value="html">
<label class="form-check-label" for="export-subtree-format-html">HTML in TAR archiv - this is recommended since this preserves all the formatting.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="export-subtree-format" id="export-subtree-format-markdown"
value="markdown">
<label class="form-check-label" for="export-subtree-format-markdown">
Markdown - this preserves most of the formatting.
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="export-subtree-format" id="export-subtree-format-opml"
value="opml">
<label class="form-check-label" for="export-subtree-format-opml">
OPML - outliner interchange format for text only. Formatting, images and files are not included.
</label>
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="export-type" id="export-type-single" value="single">
<label class="form-check-label" for="export-type-single">only this note without its descendants</label>
</div>
<div id="export-single-formats" class="format-choice">
<div class="form-check">
<input class="form-check-input" type="radio" name="export-single-format" id="export-single-format-html" value="html">
<label class="form-check-label" for="export-single-format-html">HTML - this is recommended since this preserves all the formatting.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="export-single-format" id="export-single-format-markdown"
value="markdown">
<label class="form-check-label" for="export-single-format-markdown">
Markdown - this preserves most of the formatting.
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary btn-sm">Export</button>
</div>
</form>
</div>
</div>
</div>

View File

@@ -1,46 +0,0 @@
<div id="export-subtree-dialog" class="modal fade mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Export subtree</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form id="export-subtree-form">
<div class="modal-body">
<div>Export note "<span class="note-title"></span>" and its subtree in the following format:</div>
<br/>
<div class="form-check">
<input class="form-check-input" type="radio" name="export-format" id="export-format-tar" value="native-tar" checked>
<label class="form-check-label" for="export-format-tar">Native TAR - this is Trilium's native format which preserves all notes' data & metadata.</label>
</div>
<br/>
<div class="form-check">
<input class="form-check-input" type="radio" name="export-format" id="export-format-opml" value="opml">
<label class="form-check-label" for="export-format-opml">
OPML - standard outliner interchange format for text only. Formatting, images, files are not included.
</label>
</div>
<br/>
<div class="form-check disabled">
<input class="form-check-input" type="radio" name="export-format" id="export-format-markdown"
value="markdown-tar">
<label class="form-check-label" for="export-format-markdown">
Markdown - TAR archive of Markdown formatted notes
</label>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary btn-sm">Export</button>
</div>
</form>
</div>
</div>
</div>

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

View File

@@ -16,33 +16,47 @@
</div>
<div style="flex-grow: 100; display: flex;">
<button class="btn btn-sm" id="jump-to-note-dialog-button" title="CTRL+J">Jump to note</button>
<button class="btn btn-sm" id="recent-changes-button">Recent changes</button>
<div>
<span style="font-size: smaller">Protected session:</span>
<button class="btn btn-sm" id="jump-to-note-dialog-button" title="CTRL+J">
<span class="jam jam-direction"></span>
Jump to note
</button>
<div class="btn-group btn-group-xs">
<button type="button" class="btn" id="protected-session-on">On</button>
<button type="button" class="btn active" id="protected-session-off">Off</button>
</div>
</div>
<button class="btn btn-sm" id="recent-changes-button">
<span class="jam jam-history"></span>
Recent changes
</button>
<button class="btn btn-sm" id="enter-protected-session-button" title="Enter protected session to be able to find and view protected notes">
<span class="jam jam-door"></span>
Enter protected session
</button>
<button class="btn btn-sm" id="leave-protected-session-button" title="Leave protected session so that protected notes are not accessible any more." style="display: none;">
<span class="jam jam-log-out"></span>
Leave protected session
</button>
</div>
<div id="plugin-buttons">
</div>
<div>
<button class="btn btn-sm" id="sync-now-button" title="Number of outstanding changes to be pushed to server">
<button class="btn btn-sm" id="sync-now-button" title="Trigger sync">
<span class="jam jam-refresh"></span>
Sync now (<span id="outstanding-syncs-count">0</span>)
Sync (<span id="outstanding-syncs-count">0</span>)
</button>
<button class="btn btn-sm" id="options-button">
<span class="jam jam-settings-alt"></span> Options</button>
<form action="logout" id="logout-button" method="POST" style="display: inline;">
<button type="submit" class="btn btn-sm">Logout</button>
<button type="submit" class="btn btn-sm">
<span class="jam jam-log-out"></span>
Logout
</button>
</form>
</div>
</div>
@@ -53,7 +67,7 @@
<a id="collapse-tree-button" title="Collapse note tree. Shortcut ALT+C" class="icon-action jam jam-align-justify"></a>
<a id="scroll-to-current-note-button" title="Scroll to current note. Shortcut CTRL+." class="icon-action jam jam-target"></a>
<a id="scroll-to-current-note-button" title="Scroll to current note. Shortcut CTRL+." class="icon-action jam jam-download"></a>
<a id="toggle-search-button" title="Search in notes. Shortcut CTRL+S" class="icon-action jam jam-search"></a>
</div>
@@ -157,9 +171,9 @@
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" id="show-note-revisions-button" data-bind="css: { disabled: type() == 'file' || type() == 'image' }">Revisions</a>
<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="show-source-button" data-bind="css: { disabled: type() != 'text' && type() != 'code' && type() != 'relation-map' && type() != 'search' }">Note 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' && type() != 'code' }">Export as markdown</a>
<a class="dropdown-item" id="export-note-button" data-bind="css: { disabled: type() != 'text' }">Export note</a>
</div>
</div>
</div>
@@ -173,7 +187,7 @@
<% include dialogs/attributes.ejs %>
<% include dialogs/branch_prefix.ejs %>
<% include dialogs/event_log.ejs %>
<% include dialogs/export_subtree.ejs %>
<% include dialogs/export.ejs %>
<% include dialogs/jump_to_note.ejs %>
<% include dialogs/markdown_import.ejs %>
<% include dialogs/note_revisions.ejs %>
@@ -187,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/';
@@ -202,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" 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
@@ -236,4 +250,4 @@
<%= appCss %>
</style>
</body>
</html>
</html>

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>
@@ -129,6 +131,6 @@
<script src="/libraries/knockout.min.js"></script>
<script src="/javascripts/setup.js" type="module"></script>
<script src="/javascripts/setup.js" crossorigin type="module"></script>
</body>
</html>
</html>