mirror of
https://github.com/zadam/trilium.git
synced 2025-10-29 09:16:45 +01:00
Compare commits
85 Commits
v0.24.4-be
...
v0.26.0-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b036852b2d | ||
|
|
f693dc31e8 | ||
|
|
af8f5b11b8 | ||
|
|
8ca943f87b | ||
|
|
35cc9da626 | ||
|
|
44cc86a1a5 | ||
|
|
7c3bbfd45e | ||
|
|
a1f939e3a0 | ||
|
|
4ca7886090 | ||
|
|
b942163748 | ||
|
|
5b6d15acb3 | ||
|
|
841420360e | ||
|
|
a680bb4612 | ||
|
|
f763e13996 | ||
|
|
e73e1262ae | ||
|
|
b774d56cf7 | ||
|
|
bae55f2e8b | ||
|
|
6c16cdb011 | ||
|
|
86fcbb0354 | ||
|
|
17d030e800 | ||
|
|
6fbf28b30d | ||
|
|
8171b68b18 | ||
|
|
d30e3a4052 | ||
|
|
87aab7ac5b | ||
|
|
c46f5805b1 | ||
|
|
dd6a29dfc0 | ||
|
|
a0933c7f10 | ||
|
|
492adc2909 | ||
|
|
90a52f57b5 | ||
|
|
a1469854e8 | ||
|
|
e2c2993255 | ||
|
|
270d63b265 | ||
|
|
1d004be91d | ||
|
|
b8a74c727e | ||
|
|
c87b592277 | ||
|
|
cfa926602a | ||
|
|
350cb52c07 | ||
|
|
af195beb7e | ||
|
|
cb00d42546 | ||
|
|
ee0b0c3dfe | ||
|
|
ddcb4a0e10 | ||
|
|
8cbb29ee25 | ||
|
|
9da11ac144 | ||
|
|
ba7c8e77e5 | ||
|
|
4577b03fc9 | ||
|
|
72d83aa85e | ||
|
|
8ee80cb5f1 | ||
|
|
4cc08bff8b | ||
|
|
295cfb2d75 | ||
|
|
7775376b33 | ||
|
|
9f64e994dc | ||
|
|
ee1e4fc710 | ||
|
|
5ea62c710d | ||
|
|
aeca31d06a | ||
|
|
9cb9ea6ab5 | ||
|
|
e37dd69827 | ||
|
|
6c51696d1a | ||
|
|
27787c8f37 | ||
|
|
e910595545 | ||
|
|
a616739805 | ||
|
|
bea28de6a0 | ||
|
|
4e198ca2f0 | ||
|
|
2fbd16a0e3 | ||
|
|
76fc49f037 | ||
|
|
139c99440f | ||
|
|
137b9dfa0b | ||
|
|
5f0fdd15eb | ||
|
|
61e1427b83 | ||
|
|
b3aa0ba47c | ||
|
|
56e2b44c25 | ||
|
|
4d5a17583f | ||
|
|
71eda5aa3d | ||
|
|
0711ea8dc8 | ||
|
|
be206872d1 | ||
|
|
fcf3fe8dcd | ||
|
|
62dbd4062a | ||
|
|
196e8b4380 | ||
|
|
551e1255ff | ||
|
|
e09b61d1ac | ||
|
|
ee23bcc783 | ||
|
|
3e351bd8d3 | ||
|
|
0d3bc22d73 | ||
|
|
d82898421e | ||
|
|
1db2f0c2c5 | ||
|
|
6cd8a2203e |
@@ -1,4 +1,7 @@
|
||||
.git
|
||||
.idea
|
||||
/bin
|
||||
/dist
|
||||
/docs
|
||||
/npm-debug.log
|
||||
node_modules
|
||||
npm-debug.log
|
||||
dist
|
||||
.idea
|
||||
26
Dockerfile
26
Dockerfile
@@ -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" ]
|
||||
|
||||
19
README.md
19
README.md
@@ -1,23 +1,20 @@
|
||||
# Trilium Notes
|
||||
|
||||
[](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:
|
||||
|
||||

|
||||
|
||||
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.
|
||||
@@ -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
24
bin/build-linux-x64.sh
Executable 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
27
bin/build-mac-x64.sh
Executable 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
|
||||
@@ -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
25
bin/build-win-x64.sh
Executable 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
|
||||
35
bin/build.sh
35
bin/build.sh
@@ -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
|
||||
BIN
bin/deps/linux-x64/sqlite/electron-v4.0-linux-x64/node_sqlite3.node
Executable file
BIN
bin/deps/linux-x64/sqlite/electron-v4.0-linux-x64/node_sqlite3.node
Executable file
Binary file not shown.
BIN
bin/deps/linux-x64/sqlite/node-v64-linux-x64/node_sqlite3.node
Executable file
BIN
bin/deps/linux-x64/sqlite/node-v64-linux-x64/node_sqlite3.node
Executable file
Binary file not shown.
BIN
bin/deps/mac-x64/image/cjpeg
Normal file
BIN
bin/deps/mac-x64/image/cjpeg
Normal file
Binary file not shown.
BIN
bin/deps/mac-x64/image/gifsicle
Normal file
BIN
bin/deps/mac-x64/image/gifsicle
Normal file
Binary file not shown.
BIN
bin/deps/mac-x64/image/pngquant
Normal file
BIN
bin/deps/mac-x64/image/pngquant
Normal file
Binary file not shown.
Binary file not shown.
0
bin/generate-cert.sh
Normal file → Executable file
0
bin/generate-cert.sh
Normal file → Executable 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
|
||||
@@ -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 \
|
||||
|
||||
BIN
db/demo.tar
BIN
db/demo.tar
Binary file not shown.
@@ -0,0 +1 @@
|
||||
UPDATE attributes SET name = 'archived' where name = 'hideInAutocomplete';
|
||||
2
db/migrations/0121__add_hoisting_option.sql
Normal file
2
db/migrations/0121__add_hoisting_option.sql
Normal 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
479
package-lock.json
generated
@@ -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",
|
||||
|
||||
15
package.json
15
package.json
@@ -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",
|
||||
|
||||
@@ -105,6 +105,11 @@ class Attribute extends Entity {
|
||||
this.dateModified = dateUtils.nowDate();
|
||||
}
|
||||
}
|
||||
|
||||
// cannot be static!
|
||||
updatePojo(pojo) {
|
||||
delete pojo.isOwned;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Attribute;
|
||||
@@ -59,6 +59,11 @@ class Branch extends Entity {
|
||||
this.dateModified = dateUtils.nowDate();
|
||||
}
|
||||
}
|
||||
|
||||
// cannot be static!
|
||||
updatePojo(pojo) {
|
||||
delete pojo.origParentNoteId;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Branch;
|
||||
@@ -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;
|
||||
BIN
src/public/images/app-icons/mac/icon.icns
Normal file
BIN
src/public/images/app-icons/mac/icon.icns
Normal file
Binary file not shown.
@@ -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",
|
||||
|
||||
77
src/public/javascripts/dialogs/export.js
Normal file
77
src/public/javascripts/dialogs/export.js
Normal 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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -14,7 +14,7 @@ class Branch {
|
||||
/** @param {string} */
|
||||
this.prefix = row.prefix;
|
||||
/** @param {boolean} */
|
||||
this.isExpanded = row.isExpanded;
|
||||
this.isExpanded = !!row.isExpanded;
|
||||
}
|
||||
|
||||
/** @returns {NoteShort} */
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
32
src/public/javascripts/services/bootstrap.js
vendored
32
src/public/javascripts/services/bootstrap.js
vendored
@@ -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();
|
||||
|
||||
@@ -4,6 +4,10 @@ function initContextMenu(event, contextMenuItems, selectContextMenuItem) {
|
||||
$contextMenuContainer.empty();
|
||||
|
||||
for (const item of contextMenuItems) {
|
||||
if (item.hidden) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.title === '----') {
|
||||
$contextMenuContainer.append($("<div>").addClass("dropdown-divider"));
|
||||
} else {
|
||||
|
||||
@@ -4,6 +4,7 @@ import treeChangesService from './branches.js';
|
||||
const dragAndDropSetup = {
|
||||
autoExpandMS: 600,
|
||||
dragStart: (node, data) => {
|
||||
// don't allow dragging root node
|
||||
if (node.data.noteId === 'root') {
|
||||
return false;
|
||||
}
|
||||
@@ -22,11 +23,7 @@ const dragAndDropSetup = {
|
||||
// Return false to cancel dragging of node.
|
||||
return true;
|
||||
},
|
||||
dragEnter: (node, data) => {
|
||||
// we don't allow moving root to any other location in the tree
|
||||
// we allow it to be placed on the relation map though, that's handled in a different drop handler
|
||||
return node.data.noteId === 'root';
|
||||
}, // allow drop on any node
|
||||
dragEnter: (node, data) => true, // allow drop on any node
|
||||
dragDrop: (node, data) => {
|
||||
// This function MUST be defined to enable dropping of items on the tree.
|
||||
// data.hitMode is 'before', 'after', or 'over'.
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
33
src/public/javascripts/services/hoisted_note.js
Normal file
33
src/public/javascripts/services/hoisted_note.js
Normal 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
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -76,7 +76,8 @@ function initNoteAutocomplete($el, options) {
|
||||
hint: false,
|
||||
autoselect: true,
|
||||
openOnFocus: true,
|
||||
minLength: 0
|
||||
minLength: 0,
|
||||
tabAutocomplete: false
|
||||
}, [
|
||||
{
|
||||
source: autocompleteSource,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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> (<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};
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
2
src/public/libraries/ckeditor/ckeditor.js
vendored
2
src/public/libraries/ckeditor/ckeditor.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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;
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
const noteCacheService = require('../../services/note_cache');
|
||||
const repository = require('../../services/repository');
|
||||
const log = require('../../services/log');
|
||||
const utils = require('../../services/utils');
|
||||
const optionService = require('../../services/options');
|
||||
|
||||
async function getAutocomplete(req) {
|
||||
const query = req.query.query;
|
||||
@@ -16,7 +18,7 @@ async function getAutocomplete(req) {
|
||||
results = await getRecentNotes(currentNoteId);
|
||||
}
|
||||
else {
|
||||
results = noteCacheService.findNotes(query);
|
||||
results = await noteCacheService.findNotes(query);
|
||||
}
|
||||
|
||||
const msTaken = Date.now() - timestampStarted;
|
||||
@@ -29,6 +31,13 @@ async function getAutocomplete(req) {
|
||||
}
|
||||
|
||||
async function getRecentNotes(currentNoteId) {
|
||||
let extraCondition = '';
|
||||
|
||||
const hoistedNoteId = await optionService.getOption('hoistedNoteId');
|
||||
if (hoistedNoteId !== 'root') {
|
||||
extraCondition = `AND recent_notes.notePath LIKE '%${utils.sanitizeSql(hoistedNoteId)}%'`;
|
||||
}
|
||||
|
||||
const recentNotes = await repository.getEntities(`
|
||||
SELECT
|
||||
recent_notes.*
|
||||
@@ -39,6 +48,7 @@ async function getRecentNotes(currentNoteId) {
|
||||
recent_notes.isDeleted = 0
|
||||
AND branches.isDeleted = 0
|
||||
AND branches.noteId != ?
|
||||
${extraCondition}
|
||||
ORDER BY
|
||||
dateCreated DESC
|
||||
LIMIT 200`, [currentNoteId]);
|
||||
|
||||
@@ -1,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
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)));
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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" };
|
||||
|
||||
@@ -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
|
||||
};
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
57
src/services/export/single.js
Normal file
57
src/services/export/single.js
Normal 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
232
src/services/export/tar.js
Normal 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
|
||||
};
|
||||
19
src/services/hoisted_note.js
Normal file
19
src/services/hoisted_note.js
Normal 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
|
||||
};
|
||||
@@ -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>");
|
||||
|
||||
@@ -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
|
||||
};
|
||||
47
src/services/import/single.js
Normal file
47
src/services/import/single.js
Normal 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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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];
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
118
src/services/request.js
Normal 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
|
||||
};
|
||||
@@ -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();
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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')
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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'">
|
||||
|
||||
67
src/views/dialogs/export.ejs
Normal file
67
src/views/dialogs/export.ejs
Normal 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">×</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>
|
||||
@@ -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">×</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>
|
||||
@@ -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;">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user