mirror of
https://github.com/zadam/trilium.git
synced 2025-10-29 01:06:36 +01:00
Compare commits
88 Commits
v0.27.2-be
...
v0.28.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a9a49b739 | ||
|
|
2ac560c56e | ||
|
|
c23387c0fb | ||
|
|
0ff250fe15 | ||
|
|
52b1e58b26 | ||
|
|
484715e440 | ||
|
|
9d42c3d802 | ||
|
|
c654172d33 | ||
|
|
e17b26c883 | ||
|
|
24d02d9cf5 | ||
|
|
7208a311ac | ||
|
|
ad7355372b | ||
|
|
f18b5babad | ||
|
|
9831ec0ca9 | ||
|
|
596544eca3 | ||
|
|
1f853024ee | ||
|
|
0308b13460 | ||
|
|
06b8a82f70 | ||
|
|
91ca07929d | ||
|
|
afabaa5fdb | ||
|
|
a6fd3fa77c | ||
|
|
58a2c08dcd | ||
|
|
299bbff2f4 | ||
|
|
19d8947123 | ||
|
|
35edce7523 | ||
|
|
bc4cec69a5 | ||
|
|
cce8c1b674 | ||
|
|
aa58788769 | ||
|
|
6c62ab7a52 | ||
|
|
d6ab638b30 | ||
|
|
bd4db406de | ||
|
|
e50f9cd0a3 | ||
|
|
2b64cbce2c | ||
|
|
2797c942ab | ||
|
|
f1c3278874 | ||
|
|
424c22dcde | ||
|
|
5c223dfd12 | ||
|
|
6a3e7a5a8e | ||
|
|
f88cdac000 | ||
|
|
eeead90f32 | ||
|
|
b607857409 | ||
|
|
9268f88bc3 | ||
|
|
f7f0560a9f | ||
|
|
3d8905207e | ||
|
|
dbc312010b | ||
|
|
b115a7cf19 | ||
|
|
348562352c | ||
|
|
70fd917e7c | ||
|
|
d2b60764cd | ||
|
|
581b1fdaa5 | ||
|
|
3c19a712c0 | ||
|
|
cc27f16088 | ||
|
|
dffdb82288 | ||
|
|
2b32addade | ||
|
|
0b251530fa | ||
|
|
f5b933149a | ||
|
|
48bbfb8bdb | ||
|
|
889971c4d6 | ||
|
|
0722494d41 | ||
|
|
4b977a3306 | ||
|
|
3ff3021acd | ||
|
|
99e56a9c42 | ||
|
|
77279dfe16 | ||
|
|
93f8050454 | ||
|
|
31cfede7a7 | ||
|
|
c8ec86e537 | ||
|
|
05aee884b6 | ||
|
|
012ba9e060 | ||
|
|
8e8fd88857 | ||
|
|
523ccdad6b | ||
|
|
ded3f605be | ||
|
|
62b44e3549 | ||
|
|
030d12a465 | ||
|
|
4d15628840 | ||
|
|
81b849898c | ||
|
|
3824486b85 | ||
|
|
081ab00a0a | ||
|
|
04f6af5c9a | ||
|
|
4dc1f1f6eb | ||
|
|
3930a02123 | ||
|
|
3112de105e | ||
|
|
3b8d7b8fba | ||
|
|
9fca7f09a5 | ||
|
|
fd39d6b3a9 | ||
|
|
a103886ea5 | ||
|
|
373408e401 | ||
|
|
db44c1d8e6 | ||
|
|
95a34c9e2d |
7
.gitpod.yml
Normal file
7
.gitpod.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
tasks:
|
||||||
|
- before: nvm install 10 && nvm use 10
|
||||||
|
init: npm install
|
||||||
|
command: npm run start
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
onOpen: open-preview
|
||||||
@@ -17,6 +17,7 @@ RUN set -x \
|
|||||||
libtool \
|
libtool \
|
||||||
make \
|
make \
|
||||||
nasm \
|
nasm \
|
||||||
|
libpng-dev \
|
||||||
&& npm install --production \
|
&& npm install --production \
|
||||||
&& apk del .build-dependencies
|
&& apk del .build-dependencies
|
||||||
|
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -1,7 +1,7 @@
|
|||||||
# Trilium Notes
|
# Trilium Notes
|
||||||
|
|
||||||
[](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](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 with focus on building large personal knowledge bases. See [screenshots](https://github.com/zadam/trilium/wiki/Screenshot-tour) for quick overview:
|
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:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -18,6 +18,7 @@ Trilium Notes is a hierarchical note taking application with focus on building l
|
|||||||
* [Relation maps](https://github.com/zadam/trilium/wiki/Relation-map) for visualizing notes and their relations
|
* [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)
|
* [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
|
* Scales well in both usability and performance upwards of 100 000 notes
|
||||||
|
* Touch optimized [mobile frontend](https://github.com/zadam/trilium/wiki/Mobile-frontend) for smartphones and tablets
|
||||||
* [Night theme](https://github.com/zadam/trilium/wiki/Themes)
|
* [Night theme](https://github.com/zadam/trilium/wiki/Themes)
|
||||||
* [Evernote](https://github.com/zadam/trilium/wiki/Evernote-import) and [Markdown import & export](https://github.com/zadam/trilium/wiki/Markdown)
|
* [Evernote](https://github.com/zadam/trilium/wiki/Evernote-import) and [Markdown import & export](https://github.com/zadam/trilium/wiki/Markdown)
|
||||||
|
|
||||||
@@ -33,4 +34,16 @@ Trilium is provided as either desktop application (Linux, Windows, Mac) or web a
|
|||||||
|
|
||||||
[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.
|
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.
|
||||||
|
|
||||||
|
## Contribute
|
||||||
|
|
||||||
|
Use a browser based dev environment
|
||||||
|
|
||||||
|
[](https://gitpod.io/#https://github.com/zadam/trilium)
|
||||||
|
|
||||||
|
Or clone locally and run
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
npm run start
|
||||||
|
```
|
||||||
@@ -3,22 +3,22 @@
|
|||||||
BUILD_DIR=./dist/trilium-linux-x64
|
BUILD_DIR=./dist/trilium-linux-x64
|
||||||
rm -rf $BUILD_DIR
|
rm -rf $BUILD_DIR
|
||||||
|
|
||||||
# we build x64 as second so that we keep X64 binaries in node_modules for local development and server build
|
rm -r node_modules/sqlite3/lib/binding/*
|
||||||
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
|
cp -r bin/deps/linux-x64/sqlite/* node_modules/sqlite3/lib/binding/
|
||||||
|
|
||||||
|
./node_modules/.bin/electron-packager . --asar --out=dist --executable-name=trilium --platform=linux --arch=x64 --overwrite
|
||||||
|
|
||||||
mv "./dist/Trilium Notes-linux-x64" $BUILD_DIR
|
mv "./dist/Trilium Notes-linux-x64" $BUILD_DIR
|
||||||
|
|
||||||
rm -r "$BUILD_DIR/resources/app/node_modules/sqlite3/lib/binding/*"
|
cp src/public/images/app-icons/png/128x128.png $BUILD_DIR/icon.png
|
||||||
|
|
||||||
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
|
# removing software WebGL binaries because they are pretty huge and not necessary
|
||||||
rm -r $BUILD_DIR/swiftshader
|
rm -r $BUILD_DIR/swiftshader
|
||||||
|
|
||||||
echo "Packaging linux x64 electron distribution..."
|
echo "Packaging linux x64 electron distribution..."
|
||||||
VERSION=`jq -r ".version" package.json`
|
VERSION=`jq -r ".version" package.json`
|
||||||
7z a $BUILD_DIR-${VERSION}.7z $BUILD_DIR
|
|
||||||
|
cd dist
|
||||||
|
|
||||||
|
tar cJf trilium-linux-x64-${VERSION}.tar.xz trilium-linux-x64
|
||||||
|
|||||||
@@ -3,25 +3,30 @@
|
|||||||
BUILD_DIR=./dist/trilium-mac-x64
|
BUILD_DIR=./dist/trilium-mac-x64
|
||||||
rm -rf $BUILD_DIR
|
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
|
echo "Copying required mac binaries"
|
||||||
|
|
||||||
|
rm -r node_modules/sqlite3/lib/binding/*
|
||||||
|
rm -r node_modules/mozjpeg/vendor/*
|
||||||
|
rm -r node_modules/pngquant-bin/vendor/*
|
||||||
|
rm -r node_modules/giflossy/vendor/*
|
||||||
|
|
||||||
|
cp -r bin/deps/mac-x64/sqlite/* node_modules/sqlite3/lib/binding/
|
||||||
|
cp bin/deps/mac-x64/image/cjpeg node_modules/mozjpeg/vendor/
|
||||||
|
cp bin/deps/mac-x64/image/pngquant node_modules/pngquant-bin/vendor/
|
||||||
|
cp bin/deps/mac-x64/image/gifsicle node_modules/giflossy/vendor/
|
||||||
|
|
||||||
|
./node_modules/.bin/electron-packager . --asar --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
|
# Mac build has by default useless directory level
|
||||||
mv "./dist/Trilium Notes-darwin-x64" $BUILD_DIR
|
mv "./dist/Trilium Notes-darwin-x64" $BUILD_DIR
|
||||||
|
|
||||||
echo "Copying required mac binaries"
|
./reset-local.sh
|
||||||
|
|
||||||
MAC_RES_DIR=$BUILD_DIR/Trilium\ Notes.app/Contents/Resources/app
|
echo "Zipping mac x64 electron distribution..."
|
||||||
|
|
||||||
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`
|
VERSION=`jq -r ".version" package.json`
|
||||||
7z a $BUILD_DIR-${VERSION}.7z $BUILD_DIR
|
|
||||||
|
cd dist
|
||||||
|
|
||||||
|
rm trilium-mac-x64-${VERSION}.zip
|
||||||
|
zip -r9 --symlinks trilium-mac-x64-${VERSION}.zip trilium-mac-x64
|
||||||
|
|||||||
@@ -34,4 +34,5 @@ chmod 755 trilium.sh
|
|||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
VERSION=`jq -r ".version" ../package.json`
|
VERSION=`jq -r ".version" ../package.json`
|
||||||
7z a trilium-linux-x64-server-${VERSION}.7z trilium-linux-x64-server
|
|
||||||
|
tar cJf trilium-linux-x64-server-${VERSION}.tar.xz trilium-linux-x64-server
|
||||||
@@ -3,23 +3,30 @@
|
|||||||
BUILD_DIR=./dist/trilium-windows-x64
|
BUILD_DIR=./dist/trilium-windows-x64
|
||||||
rm -rf $BUILD_DIR
|
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
|
echo "Copying required windows binaries"
|
||||||
|
|
||||||
|
rm -r node_modules/sqlite3/lib/binding/*
|
||||||
|
rm -r node_modules/mozjpeg/vendor/*
|
||||||
|
rm -r node_modules/pngquant-bin/vendor/*
|
||||||
|
rm -r node_modules/giflossy/vendor/*
|
||||||
|
|
||||||
|
cp -r bin/deps/win-x64/sqlite/* node_modules/sqlite3/lib/binding/
|
||||||
|
cp bin/deps/win-x64/image/cjpeg.exe node_modules/mozjpeg/vendor/
|
||||||
|
cp bin/deps/win-x64/image/pngquant.exe node_modules/pngquant-bin/vendor/
|
||||||
|
cp bin/deps/win-x64/image/gifsicle.exe node_modules/giflossy/vendor/
|
||||||
|
|
||||||
|
./node_modules/.bin/electron-packager . --asar --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
|
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
|
# removing software WebGL binaries because they are pretty huge and not necessary
|
||||||
rm -r $BUILD_DIR/swiftshader
|
rm -r $BUILD_DIR/swiftshader
|
||||||
|
|
||||||
echo "Packaging windows x64 electron distribution..."
|
./reset-local.sh
|
||||||
|
|
||||||
|
echo "Zipping windows x64 electron distribution..."
|
||||||
VERSION=`jq -r ".version" package.json`
|
VERSION=`jq -r ".version" package.json`
|
||||||
7z a $BUILD_DIR-${VERSION}.7z $BUILD_DIR
|
|
||||||
|
cd dist
|
||||||
|
|
||||||
|
zip -r9 trilium-windows-x64-${VERSION}.zip trilium-windows-x64
|
||||||
|
|||||||
@@ -42,10 +42,10 @@ git push origin $TAG
|
|||||||
|
|
||||||
bin/build.sh
|
bin/build.sh
|
||||||
|
|
||||||
LINUX_X64_BUILD=trilium-linux-x64-$VERSION.7z
|
LINUX_X64_BUILD=trilium-linux-x64-$VERSION.tar.xz
|
||||||
WINDOWS_X64_BUILD=trilium-windows-x64-$VERSION.7z
|
WINDOWS_X64_BUILD=trilium-windows-x64-$VERSION.zip
|
||||||
MAC_X64_BUILD=trilium-mac-x64-$VERSION.7z
|
MAC_X64_BUILD=trilium-mac-x64-$VERSION.zip
|
||||||
SERVER_BUILD=trilium-linux-x64-server-$VERSION.7z
|
SERVER_BUILD=trilium-linux-x64-server-$VERSION.tar.xz
|
||||||
|
|
||||||
echo "Creating release in GitHub"
|
echo "Creating release in GitHub"
|
||||||
|
|
||||||
|
|||||||
3
bin/reset-local.sh
Executable file
3
bin/reset-local.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
./node_modules/.bin/electron-rebuild --arch=x64
|
||||||
BIN
db/demo.tar
BIN
db/demo.tar
Binary file not shown.
62
db/migrations/0122__add_iv_to_columns.js
Normal file
62
db/migrations/0122__add_iv_to_columns.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
const sql = require('../../src/services/sql');
|
||||||
|
|
||||||
|
function prependIv(cipherText, ivText) {
|
||||||
|
const arr = ivText.split("").map(c => parseInt(c) || 0);
|
||||||
|
const iv = Buffer.from(arr);
|
||||||
|
const payload = Buffer.from(cipherText, 'base64');
|
||||||
|
const complete = Buffer.concat([iv, payload]);
|
||||||
|
|
||||||
|
return complete.toString('base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateEncryptedDataKey() {
|
||||||
|
const encryptedDataKey = await sql.getValue("SELECT value FROM options WHERE name = 'encryptedDataKey'");
|
||||||
|
const encryptedDataKeyIv = await sql.getValue("SELECT value FROM options WHERE name = 'encryptedDataKeyIv'");
|
||||||
|
|
||||||
|
const newEncryptedDataKey = prependIv(encryptedDataKey, encryptedDataKeyIv);
|
||||||
|
|
||||||
|
await sql.execute("UPDATE options SET value = ? WHERE name = 'encryptedDataKey'", [newEncryptedDataKey]);
|
||||||
|
|
||||||
|
await sql.execute("DELETE FROM options WHERE name = 'encryptedDataKeyIv'");
|
||||||
|
await sql.execute("DELETE FROM sync WHERE entityName = 'options' AND entityId = 'encryptedDataKeyIv'");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateNotes() {
|
||||||
|
const protectedNotes = await sql.getRows("SELECT noteId, title, content FROM notes WHERE isProtected = 1");
|
||||||
|
|
||||||
|
for (const note of protectedNotes) {
|
||||||
|
if (note.title !== null) {
|
||||||
|
note.title = prependIv(note.title, "0" + note.noteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (note.content !== null) {
|
||||||
|
note.content = prependIv(note.content, "1" + note.noteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
await sql.execute("UPDATE notes SET title = ?, content = ? WHERE noteId = ?", [note.title, note.content, note.noteId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateNoteRevisions() {
|
||||||
|
const protectedNoteRevisions = await sql.getRows("SELECT noteRevisionId, title, content FROM note_revisions WHERE isProtected = 1");
|
||||||
|
|
||||||
|
for (const noteRevision of protectedNoteRevisions) {
|
||||||
|
if (noteRevision.title !== null) {
|
||||||
|
noteRevision.title = prependIv(noteRevision.title, "0" + noteRevision.noteRevisionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noteRevision.content !== null) {
|
||||||
|
noteRevision.content = prependIv(noteRevision.content, "1" + noteRevision.noteRevisionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
await sql.execute("UPDATE note_revisions SET title = ?, content = ? WHERE noteRevisionId = ?", [noteRevision.title, noteRevision.content, noteRevision.noteRevisionId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = async () => {
|
||||||
|
await updateEncryptedDataKey();
|
||||||
|
|
||||||
|
await updateNotes();
|
||||||
|
|
||||||
|
await updateNoteRevisions();
|
||||||
|
};
|
||||||
8
db/migrations/0123__add_options_for_font_sizes.sql
Normal file
8
db/migrations/0123__add_options_for_font_sizes.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
INSERT INTO options (name, value, dateCreated, dateModified, isSynced)
|
||||||
|
VALUES ('mainFontSize', '100', '2019-01-13T18:31:00.874Z', '2019-01-13T18:31:00.874Z', 0);
|
||||||
|
|
||||||
|
INSERT INTO options (name, value, dateCreated, dateModified, isSynced)
|
||||||
|
VALUES ('treeFontSize', '100', '2019-01-13T18:31:00.874Z', '2019-01-13T18:31:00.874Z', 0);
|
||||||
|
|
||||||
|
INSERT INTO options (name, value, dateCreated, dateModified, isSynced)
|
||||||
|
VALUES ('detailFontSize', '110', '2019-01-13T18:31:00.874Z', '2019-01-13T18:31:00.874Z', 0);
|
||||||
11
db/migrations/0124__readd_font_sizes.sql
Normal file
11
db/migrations/0124__readd_font_sizes.sql
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
INSERT INTO options (name, value, dateCreated, dateModified, isSynced)
|
||||||
|
SELECT 'mainFontSize', '100', '2019-01-13T18:31:00.874Z', '2019-01-13T18:31:00.874Z', 0
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM options WHERE name = 'mainFontSize');
|
||||||
|
|
||||||
|
INSERT INTO options (name, value, dateCreated, dateModified, isSynced)
|
||||||
|
SELECT 'treeFontSize', '100', '2019-01-13T18:31:00.874Z', '2019-01-13T18:31:00.874Z', 0
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM options WHERE name = 'treeFontSize');
|
||||||
|
|
||||||
|
INSERT INTO options (name, value, dateCreated, dateModified, isSynced)
|
||||||
|
SELECT 'detailFontSize', '110', '2019-01-13T18:31:00.874Z', '2019-01-13T18:31:00.874Z', 0
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM options WHERE name = 'detailFontSize');
|
||||||
@@ -81,7 +81,7 @@ app.on('ready', async () => {
|
|||||||
const dateNoteService = require('./src/services/date_notes');
|
const dateNoteService = require('./src/services/date_notes');
|
||||||
const dateUtils = require('./src/services/date_utils');
|
const dateUtils = require('./src/services/date_utils');
|
||||||
|
|
||||||
const parentNote = await dateNoteService.getDateNote(dateUtils.nowDate());
|
const parentNote = await dateNoteService.getDateNote(dateUtils.nowLocalDate());
|
||||||
|
|
||||||
// window may be hidden / not in focus
|
// window may be hidden / not in focus
|
||||||
mainWindow.focus();
|
mainWindow.focus();
|
||||||
|
|||||||
5
issue_template.md
Normal file
5
issue_template.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
For bug reports, please mention **version of the application** and include **log files** from following location:
|
||||||
|
|
||||||
|
* `/home/[user]/.local/share/trilium-data/log` for Linux
|
||||||
|
* `C:\Users\[user]\AppData\Roaming\trilium-data\log` for Windows Vista and up
|
||||||
|
* `/Users/[user]/Library/Application Support/trilium-data/log` for Mac OS
|
||||||
15
package-lock.json
generated
15
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"version": "0.27.1-beta",
|
"version": "0.27.4",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -3870,9 +3870,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"file-type": {
|
"file-type": {
|
||||||
"version": "4.4.0",
|
"version": "10.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/file-type/-/file-type-10.7.0.tgz",
|
||||||
"integrity": "sha1-G2AOX8ofvcboDApwxxyNul95BsU="
|
"integrity": "sha512-AbaGtdWYYRaVrv2MwL/65myuRJ9j3e79e7etJ79US18QHuVlzJBcQHUH+HxDUoLtbyWRTUfLzLkGXX3pP9kfZg=="
|
||||||
},
|
},
|
||||||
"filename-regex": {
|
"filename-regex": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
@@ -5000,6 +5000,13 @@
|
|||||||
"integrity": "sha1-FQKvMTX5BuEiyHfDHpSve3qRRsU=",
|
"integrity": "sha1-FQKvMTX5BuEiyHfDHpSve3qRRsU=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"file-type": "^4.1.0"
|
"file-type": "^4.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"file-type": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz",
|
||||||
|
"integrity": "sha1-G2AOX8ofvcboDApwxxyNul95BsU="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"imagemin": {
|
"imagemin": {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"productName": "Trilium Notes",
|
"productName": "Trilium Notes",
|
||||||
"description": "Trilium Notes",
|
"description": "Trilium Notes",
|
||||||
"version": "0.27.2-beta",
|
"version": "0.28.3",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"main": "electron.js",
|
"main": "electron.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -17,7 +17,8 @@
|
|||||||
"start-electron": "electron . --disable-gpu",
|
"start-electron": "electron . --disable-gpu",
|
||||||
"build-backend-docs": "jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/entities/*.js src/services/backend_script_api.js",
|
"build-backend-docs": "jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/entities/*.js src/services/backend_script_api.js",
|
||||||
"build-frontend-docs": "jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/javascripts/entities/*.js src/public/javascripts/services/frontend_script_api.js",
|
"build-frontend-docs": "jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/javascripts/entities/*.js src/public/javascripts/services/frontend_script_api.js",
|
||||||
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs"
|
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs",
|
||||||
|
"postinstall": "electron-builder install-app-deps"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async-mutex": "0.1.3",
|
"async-mutex": "0.1.3",
|
||||||
@@ -33,6 +34,7 @@
|
|||||||
"electron-in-page-search": "1.3.2",
|
"electron-in-page-search": "1.3.2",
|
||||||
"express": "4.16.4",
|
"express": "4.16.4",
|
||||||
"express-session": "1.15.6",
|
"express-session": "1.15.6",
|
||||||
|
"file-type": "10.7.0",
|
||||||
"fs-extra": "7.0.1",
|
"fs-extra": "7.0.1",
|
||||||
"get-port": "4.1.0",
|
"get-port": "4.1.0",
|
||||||
"helmet": "3.15.0",
|
"helmet": "3.15.0",
|
||||||
@@ -67,6 +69,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"devtron": "1.4.0",
|
"devtron": "1.4.0",
|
||||||
"electron": "4.0.1",
|
"electron": "4.0.1",
|
||||||
|
"electron-builder": "20.38.4",
|
||||||
"electron-compile": "6.4.3",
|
"electron-compile": "6.4.3",
|
||||||
"electron-packager": "13.0.1",
|
"electron-packager": "13.0.1",
|
||||||
"electron-rebuild": "1.8.2",
|
"electron-rebuild": "1.8.2",
|
||||||
|
|||||||
@@ -47,7 +47,18 @@ class Note extends Entity {
|
|||||||
if (this.isProtected && this.noteId) {
|
if (this.isProtected && this.noteId) {
|
||||||
this.isContentAvailable = protectedSessionService.isProtectedSessionAvailable();
|
this.isContentAvailable = protectedSessionService.isProtectedSessionAvailable();
|
||||||
|
|
||||||
protectedSessionService.decryptNote(this);
|
if (this.isContentAvailable) {
|
||||||
|
protectedSessionService.decryptNote(this);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// saving ciphertexts in case we do want to update protected note outside of protected session
|
||||||
|
// (which is allowed)
|
||||||
|
this.titleCipherText = this.title;
|
||||||
|
this.contentCipherText = this.content;
|
||||||
|
|
||||||
|
this.title = "[protected]";
|
||||||
|
this.content = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setContent(this.content);
|
this.setContent(this.content);
|
||||||
@@ -56,6 +67,9 @@ class Note extends Entity {
|
|||||||
setContent(content) {
|
setContent(content) {
|
||||||
this.content = content;
|
this.content = content;
|
||||||
|
|
||||||
|
// if parsing below is not successful then there's no jsonContent as opposed to still having the old unupdated ones
|
||||||
|
delete this.jsonContent;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.jsonContent = JSON.parse(this.content);
|
this.jsonContent = JSON.parse(this.content);
|
||||||
}
|
}
|
||||||
@@ -626,12 +640,21 @@ class Note extends Entity {
|
|||||||
// cannot be static!
|
// cannot be static!
|
||||||
updatePojo(pojo) {
|
updatePojo(pojo) {
|
||||||
if (pojo.isProtected) {
|
if (pojo.isProtected) {
|
||||||
protectedSessionService.encryptNote(pojo);
|
if (this.isContentAvailable) {
|
||||||
|
protectedSessionService.encryptNote(pojo);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// updating protected note outside of protected session means we will keep original ciphertexts
|
||||||
|
pojo.title = pojo.titleCipherText;
|
||||||
|
pojo.content = pojo.contentCipherText;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete pojo.jsonContent;
|
delete pojo.jsonContent;
|
||||||
delete pojo.isContentAvailable;
|
delete pojo.isContentAvailable;
|
||||||
delete pojo.__attributeCache;
|
delete pojo.__attributeCache;
|
||||||
|
delete pojo.titleCipherText;
|
||||||
|
delete pojo.contentCipherText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import hoistedNoteService from './services/hoisted_note.js';
|
|||||||
import noteTypeService from './services/note_type.js';
|
import noteTypeService from './services/note_type.js';
|
||||||
import linkService from './services/link.js';
|
import linkService from './services/link.js';
|
||||||
import noteAutocompleteService from './services/note_autocomplete.js';
|
import noteAutocompleteService from './services/note_autocomplete.js';
|
||||||
|
import macInit from './services/mac_init.js';
|
||||||
|
|
||||||
// required for CKEditor image upload plugin
|
// required for CKEditor image upload plugin
|
||||||
window.glob.getCurrentNode = treeService.getCurrentNode;
|
window.glob.getCurrentNode = treeService.getCurrentNode;
|
||||||
@@ -110,28 +111,6 @@ if (utils.isElectron()) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function exec(cmd) {
|
|
||||||
document.execCommand(cmd);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (utils.isElectron() && utils.isMac()) {
|
|
||||||
utils.bindShortcut('ctrl+c', () => exec("copy"));
|
|
||||||
utils.bindShortcut('ctrl+v', () => exec('paste'));
|
|
||||||
utils.bindShortcut('ctrl+x', () => exec('cut'));
|
|
||||||
utils.bindShortcut('ctrl+a', () => exec('selectAll'));
|
|
||||||
utils.bindShortcut('ctrl+z', () => exec('undo'));
|
|
||||||
utils.bindShortcut('ctrl+y', () => exec('redo'));
|
|
||||||
|
|
||||||
utils.bindShortcut('meta+c', () => exec("copy"));
|
|
||||||
utils.bindShortcut('meta+v', () => exec('paste'));
|
|
||||||
utils.bindShortcut('meta+x', () => exec('cut'));
|
|
||||||
utils.bindShortcut('meta+a', () => exec('selectAll'));
|
|
||||||
utils.bindShortcut('meta+z', () => exec('undo'));
|
|
||||||
utils.bindShortcut('meta+y', () => exec('redo'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#export-note-button").click(function () {
|
$("#export-note-button").click(function () {
|
||||||
if ($(this).hasClass("disabled")) {
|
if ($(this).hasClass("disabled")) {
|
||||||
return;
|
return;
|
||||||
@@ -140,6 +119,8 @@ $("#export-note-button").click(function () {
|
|||||||
exportDialog.showDialog('single');
|
exportDialog.showDialog('single');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
macInit.init();
|
||||||
|
|
||||||
treeService.showTree();
|
treeService.showTree();
|
||||||
|
|
||||||
entrypoints.registerEntrypoints();
|
entrypoints.registerEntrypoints();
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ const $dialog = $("#jump-to-note-dialog");
|
|||||||
const $autoComplete = $("#jump-to-note-autocomplete");
|
const $autoComplete = $("#jump-to-note-autocomplete");
|
||||||
const $showInFullTextButton = $("#show-in-full-text-button");
|
const $showInFullTextButton = $("#show-in-full-text-button");
|
||||||
|
|
||||||
$dialog.on("shown.bs.modal", e => $autoComplete.focus());
|
|
||||||
|
|
||||||
async function showDialog() {
|
async function showDialog() {
|
||||||
glob.activeDialog = $dialog;
|
glob.activeDialog = $dialog;
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,10 @@ addTabHandler((function() {
|
|||||||
const $zoomFactorSelect = $("#zoom-factor-select");
|
const $zoomFactorSelect = $("#zoom-factor-select");
|
||||||
const $leftPaneMinWidth = $("#left-pane-min-width");
|
const $leftPaneMinWidth = $("#left-pane-min-width");
|
||||||
const $leftPaneWidthPercent = $("#left-pane-width-percent");
|
const $leftPaneWidthPercent = $("#left-pane-width-percent");
|
||||||
const $html = $("html");
|
const $mainFontSize = $("#main-font-size");
|
||||||
|
const $treeFontSize = $("#tree-font-size");
|
||||||
|
const $detailFontSize = $("#detail-font-size");
|
||||||
|
const $body = $("body");
|
||||||
const $container = $("#container");
|
const $container = $("#container");
|
||||||
|
|
||||||
function optionsLoaded(options) {
|
function optionsLoaded(options) {
|
||||||
@@ -59,21 +62,27 @@ addTabHandler((function() {
|
|||||||
|
|
||||||
$leftPaneMinWidth.val(options.leftPaneMinWidth);
|
$leftPaneMinWidth.val(options.leftPaneMinWidth);
|
||||||
$leftPaneWidthPercent.val(options.leftPaneWidthPercent);
|
$leftPaneWidthPercent.val(options.leftPaneWidthPercent);
|
||||||
|
|
||||||
|
$mainFontSize.val(options.mainFontSize);
|
||||||
|
$treeFontSize.val(options.treeFontSize);
|
||||||
|
$detailFontSize.val(options.detailFontSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
$themeSelect.change(function() {
|
$themeSelect.change(function() {
|
||||||
const newTheme = $(this).val();
|
const newTheme = $(this).val();
|
||||||
|
|
||||||
$html.attr("class", "theme-" + newTheme);
|
for (const clazz of $body[0].classList) {
|
||||||
|
if (clazz.startsWith("theme-")) {
|
||||||
|
$body.removeClass(clazz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$body.addClass("theme-" + newTheme);
|
||||||
|
|
||||||
server.put('options/theme/' + newTheme);
|
server.put('options/theme/' + newTheme);
|
||||||
});
|
});
|
||||||
|
|
||||||
$zoomFactorSelect.change(function() {
|
$zoomFactorSelect.change(function() { zoomService.setZoomFactorAndSave($(this).val()); });
|
||||||
const newZoomFactor = $(this).val();
|
|
||||||
|
|
||||||
zoomService.setZoomFactorAndSave(newZoomFactor);
|
|
||||||
});
|
|
||||||
|
|
||||||
function resizeLeftPanel() {
|
function resizeLeftPanel() {
|
||||||
const leftPanePercent = parseInt($leftPaneWidthPercent.val());
|
const leftPanePercent = parseInt($leftPaneWidthPercent.val());
|
||||||
@@ -83,20 +92,42 @@ addTabHandler((function() {
|
|||||||
$container.css("grid-template-columns", `minmax(${leftPaneMinWidth}px, ${leftPanePercent}fr) ${rightPanePercent}fr`);
|
$container.css("grid-template-columns", `minmax(${leftPaneMinWidth}px, ${leftPanePercent}fr) ${rightPanePercent}fr`);
|
||||||
}
|
}
|
||||||
|
|
||||||
$leftPaneMinWidth.change(function() {
|
$leftPaneMinWidth.change(async function() {
|
||||||
const newMinWidth = $(this).val();
|
await server.put('options/leftPaneMinWidth/' + $(this).val());
|
||||||
|
|
||||||
resizeLeftPanel();
|
resizeLeftPanel();
|
||||||
|
|
||||||
server.put('options/leftPaneMinWidth/' + newMinWidth);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$leftPaneWidthPercent.change(function() {
|
$leftPaneWidthPercent.change(async function() {
|
||||||
const newWidthPercent = $(this).val();
|
await server.put('options/leftPaneWidthPercent/' + $(this).val());
|
||||||
|
|
||||||
resizeLeftPanel();
|
resizeLeftPanel();
|
||||||
|
});
|
||||||
|
|
||||||
server.put('options/leftPaneWidthPercent/' + newWidthPercent);
|
function applyFontSizes() {
|
||||||
|
console.log($mainFontSize.val() + "% !important");
|
||||||
|
|
||||||
|
$body.get(0).style.setProperty("--main-font-size", $mainFontSize.val() + "%");
|
||||||
|
$body.get(0).style.setProperty("--tree-font-size", $treeFontSize.val() + "%");
|
||||||
|
$body.get(0).style.setProperty("--detail-font-size", $detailFontSize.val() + "%");
|
||||||
|
}
|
||||||
|
|
||||||
|
$mainFontSize.change(async function() {
|
||||||
|
await server.put('options/mainFontSize/' + $(this).val());
|
||||||
|
|
||||||
|
applyFontSizes();
|
||||||
|
});
|
||||||
|
|
||||||
|
$treeFontSize.change(async function() {
|
||||||
|
await server.put('options/treeFontSize/' + $(this).val());
|
||||||
|
|
||||||
|
applyFontSizes();
|
||||||
|
});
|
||||||
|
|
||||||
|
$detailFontSize.change(async function() {
|
||||||
|
await server.put('options/detailFontSize/' + $(this).val());
|
||||||
|
|
||||||
|
applyFontSizes();
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ async function createPromotedAttributeRow(definitionAttr, valueAttr) {
|
|||||||
$input.autocomplete({
|
$input.autocomplete({
|
||||||
appendTo: document.querySelector('body'),
|
appendTo: document.querySelector('body'),
|
||||||
hint: false,
|
hint: false,
|
||||||
autoselect: true,
|
autoselect: false,
|
||||||
openOnFocus: true,
|
openOnFocus: true,
|
||||||
minLength: 0,
|
minLength: 0,
|
||||||
tabAutocomplete: false
|
tabAutocomplete: false
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ async function getAndExecuteBundle(noteId, originEntity = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function executeBundle(bundle, originEntity) {
|
async function executeBundle(bundle, originEntity) {
|
||||||
const apiContext = ScriptContext(bundle.note, bundle.allNotes, originEntity);
|
const apiContext = await ScriptContext(bundle.noteId, bundle.allNoteIds, originEntity);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await (function () {
|
return await (function () {
|
||||||
@@ -30,9 +30,13 @@ async function executeStartupBundles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function executeRelationBundles(note, relationName) {
|
async function executeRelationBundles(note, relationName) {
|
||||||
const bundlesToRun = await server.get("script/relation/" + note.noteId + "/" + relationName);
|
note.bundleCache = note.bundleCache || {};
|
||||||
|
|
||||||
for (const bundle of bundlesToRun) {
|
if (!note.bundleCache[relationName]) {
|
||||||
|
note.bundleCache[relationName] = await server.get("script/relation/" + note.noteId + "/" + relationName);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const bundle of note.bundleCache[relationName]) {
|
||||||
await executeBundle(bundle, note);
|
await executeBundle(bundle, note);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ function registerEntrypoints() {
|
|||||||
|
|
||||||
utils.bindShortcut('ctrl+r', utils.reloadApp);
|
utils.bindShortcut('ctrl+r', utils.reloadApp);
|
||||||
|
|
||||||
$(document).bind('keydown', 'ctrl+shift+i', () => {
|
utils.bindShortcut('ctrl+shift+i', () => {
|
||||||
if (utils.isElectron()) {
|
if (utils.isElectron()) {
|
||||||
require('electron').remote.getCurrentWindow().toggleDevTools();
|
require('electron').remote.getCurrentWindow().toggleDevTools();
|
||||||
|
|
||||||
@@ -117,10 +117,6 @@ function registerEntrypoints() {
|
|||||||
|
|
||||||
utils.bindShortcut('ctrl+f', openInPageSearch);
|
utils.bindShortcut('ctrl+f', openInPageSearch);
|
||||||
|
|
||||||
if (utils.isMac()) {
|
|
||||||
utils.bindShortcut('meta+f', openInPageSearch);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: do we really need these at this point?
|
// FIXME: do we really need these at this point?
|
||||||
utils.bindShortcut("ctrl+shift+up", () => {
|
utils.bindShortcut("ctrl+shift+up", () => {
|
||||||
const node = treeService.getCurrentNode();
|
const node = treeService.getCurrentNode();
|
||||||
@@ -139,8 +135,8 @@ function registerEntrypoints() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (utils.isElectron()) {
|
if (utils.isElectron()) {
|
||||||
$(document).bind('keydown', 'ctrl+-', zoomService.decreaseZoomFactor);
|
utils.bindShortcut('ctrl+-', zoomService.decreaseZoomFactor);
|
||||||
$(document).bind('keydown', 'ctrl+=', zoomService.increaseZoomFactor);
|
utils.bindShortcut('ctrl+=', zoomService.increaseZoomFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#note-title").bind('keydown', 'return', () => $("#note-detail-text").focus());
|
$("#note-title").bind('keydown', 'return', () => $("#note-detail-text").focus());
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null) {
|
|||||||
this.activateNewNote = async notePath => {
|
this.activateNewNote = async notePath => {
|
||||||
await treeService.reload();
|
await treeService.reload();
|
||||||
|
|
||||||
await treeService.activateNote(notePath, noteDetailService.focusOnTitle);
|
await treeService.activateNote(notePath, noteDetailService.focusAndSelectTitle);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
25
src/public/javascripts/services/mac_init.js
Normal file
25
src/public/javascripts/services/mac_init.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Mac specific initialization
|
||||||
|
*/
|
||||||
|
import utils from "./utils.js";
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
if (utils.isElectron() && utils.isMac()) {
|
||||||
|
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'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function exec(cmd) {
|
||||||
|
document.execCommand(cmd);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
init
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ function clearText($el) {
|
|||||||
function showRecentNotes($el) {
|
function showRecentNotes($el) {
|
||||||
$el.setSelectedPath("");
|
$el.setSelectedPath("");
|
||||||
$el.autocomplete("val", "");
|
$el.autocomplete("val", "");
|
||||||
$el.autocomplete("open");
|
$el.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function initNoteAutocomplete($el, options) {
|
function initNoteAutocomplete($el, options) {
|
||||||
@@ -61,7 +61,13 @@ function initNoteAutocomplete($el, options) {
|
|||||||
|
|
||||||
$clearTextButton.click(() => clearText($el));
|
$clearTextButton.click(() => clearText($el));
|
||||||
|
|
||||||
$showRecentNotesButton.click(() => showRecentNotes($el));
|
$showRecentNotesButton.click(e => {
|
||||||
|
showRecentNotes($el);
|
||||||
|
|
||||||
|
// this will cause the click not give focus to the "show recent notes" button
|
||||||
|
// this is important because otherwise input will lose focus immediatelly and not show the results
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
$goToSelectedNoteButton.click(() => {
|
$goToSelectedNoteButton.click(() => {
|
||||||
if ($el.hasClass("disabled")) {
|
if ($el.hasClass("disabled")) {
|
||||||
|
|||||||
@@ -131,6 +131,9 @@ async function saveNote() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$savedIndicator.fadeIn();
|
$savedIndicator.fadeIn();
|
||||||
|
|
||||||
|
// run async
|
||||||
|
bundleService.executeRelationBundles(getCurrentNote(), 'runOnNoteChange');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveNoteIfChanged() {
|
async function saveNoteIfChanged() {
|
||||||
@@ -145,8 +148,9 @@ async function saveNoteIfChanged() {
|
|||||||
function setNoteBackgroundIfProtected(note) {
|
function setNoteBackgroundIfProtected(note) {
|
||||||
$noteDetailWrapper.toggleClass("protected", note.isProtected);
|
$noteDetailWrapper.toggleClass("protected", note.isProtected);
|
||||||
$protectButton.toggleClass("active", note.isProtected);
|
$protectButton.toggleClass("active", note.isProtected);
|
||||||
|
$protectButton.prop("disabled", note.isProtected);
|
||||||
$unprotectButton.toggleClass("active", !note.isProtected);
|
$unprotectButton.toggleClass("active", !note.isProtected);
|
||||||
$unprotectButton.prop("disabled", !protectedSessionHolder.isProtectedSessionAvailable());
|
$unprotectButton.prop("disabled", !note.isProtected || !protectedSessionHolder.isProtectedSessionAvailable());
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleProtectedSession() {
|
async function handleProtectedSession() {
|
||||||
@@ -283,6 +287,10 @@ function focusOnTitle() {
|
|||||||
$noteTitle.focus();
|
$noteTitle.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function focusAndSelectTitle() {
|
||||||
|
$noteTitle.focus().select();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Since detail loading may take some time and user might just browse through the notes using UP-DOWN keys,
|
* Since detail loading may take some time and user might just browse through the notes using UP-DOWN keys,
|
||||||
* we intentionally decouple activation of the note in the tree and full load of the note so just avaiting on
|
* we intentionally decouple activation of the note in the tree and full load of the note so just avaiting on
|
||||||
@@ -342,6 +350,7 @@ export default {
|
|||||||
getCurrentNoteType,
|
getCurrentNoteType,
|
||||||
getCurrentNoteId,
|
getCurrentNoteId,
|
||||||
focusOnTitle,
|
focusOnTitle,
|
||||||
|
focusAndSelectTitle,
|
||||||
saveNote,
|
saveNote,
|
||||||
saveNoteIfChanged,
|
saveNoteIfChanged,
|
||||||
noteChanged,
|
noteChanged,
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import noteDetailService from "./note_detail.js";
|
import noteDetailService from "./note_detail.js";
|
||||||
|
import treeService from "./tree.js";
|
||||||
|
import infoService from './info.js';
|
||||||
|
|
||||||
const $searchString = $("#search-string");
|
const $searchString = $("#search-string");
|
||||||
const $component = $('#note-detail-search');
|
const $component = $('#note-detail-search');
|
||||||
|
const $refreshButton = $('#note-detail-search-refresh-results-button');
|
||||||
|
|
||||||
function getContent() {
|
function getContent() {
|
||||||
JSON.stringify({
|
return JSON.stringify({
|
||||||
searchString: $searchString.val()
|
searchString: $searchString.val()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -25,6 +28,14 @@ function show() {
|
|||||||
$searchString.on('input', noteDetailService.noteChanged);
|
$searchString.on('input', noteDetailService.noteChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$refreshButton.click(async () => {
|
||||||
|
await noteDetailService.saveNoteIfChanged();
|
||||||
|
|
||||||
|
treeService.reload();
|
||||||
|
|
||||||
|
infoService.showMessage('Tree has been refreshed.');
|
||||||
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getContent,
|
getContent,
|
||||||
show,
|
show,
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import FrontendScriptApi from './frontend_script_api.js';
|
import FrontendScriptApi from './frontend_script_api.js';
|
||||||
import utils from './utils.js';
|
import utils from './utils.js';
|
||||||
|
import treeCache from './tree_cache.js';
|
||||||
|
|
||||||
function ScriptContext(startNote, allNotes, originEntity = null) {
|
async function ScriptContext(startNoteId, allNoteIds, originEntity = null) {
|
||||||
const modules = {};
|
const modules = {};
|
||||||
|
|
||||||
|
const startNote = await treeCache.getNote(startNoteId);
|
||||||
|
const allNotes = await treeCache.getNotes(allNoteIds);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
modules: modules,
|
modules: modules,
|
||||||
notes: utils.toObject(allNotes, note => [note.noteId, note]),
|
notes: utils.toObject(allNotes, note => [note.noteId, note]),
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ $searchInput.keyup(e => {
|
|||||||
if (e && e.which === $.ui.keyCode.ENTER) {
|
if (e && e.which === $.ui.keyCode.ENTER) {
|
||||||
doSearch();
|
doSearch();
|
||||||
}
|
}
|
||||||
}).focus();
|
});
|
||||||
|
|
||||||
$doSearchButton.click(() => doSearch()); // keep long form because of argument
|
$doSearchButton.click(() => doSearch()); // keep long form because of argument
|
||||||
$resetSearchButton.click(resetSearch);
|
$resetSearchButton.click(resetSearch);
|
||||||
|
|||||||
@@ -94,32 +94,41 @@ async function expandToNote(notePath, expandOpts) {
|
|||||||
|
|
||||||
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
|
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
|
||||||
|
|
||||||
|
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
|
||||||
|
let hoistedNoteFound = false;
|
||||||
|
|
||||||
let parentNoteId = null;
|
let parentNoteId = null;
|
||||||
|
|
||||||
for (const childNoteId of runPath) {
|
for (const childNoteId of runPath) {
|
||||||
// for first node (!parentNoteId) it doesn't matter which node is found
|
if (childNoteId === hoistedNoteId) {
|
||||||
let node = getNode(childNoteId, parentNoteId);
|
hoistedNoteFound = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!node && parentNoteId) {
|
// we expand only after hoisted note since before then nodes are not actually present in the tree
|
||||||
const parents = getNodesByNoteId(parentNoteId);
|
if (hoistedNoteFound) {
|
||||||
|
// for first node (!parentNoteId) it doesn't matter which node is found
|
||||||
|
let node = getNode(childNoteId, parentNoteId);
|
||||||
|
|
||||||
for (const parent of parents) {
|
if (!node && parentNoteId) {
|
||||||
// force load parents. This is useful when fancytree doesn't contain recently created notes yet.
|
const parents = getNodesByNoteId(parentNoteId);
|
||||||
await parent.load(true);
|
|
||||||
|
for (const parent of parents) {
|
||||||
|
// force load parents. This is useful when fancytree doesn't contain recently created notes yet.
|
||||||
|
await parent.load(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
node = getNode(childNoteId, parentNoteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
node = getNode(childNoteId, parentNoteId);
|
if (!node) {
|
||||||
}
|
console.error(`Can't find node for noteId=${childNoteId} with parentNoteId=${parentNoteId}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (!node) {
|
if (childNoteId === noteId) {
|
||||||
console.error(`Can't find node for noteId=${childNoteId} with parentNoteId=${parentNoteId}`);
|
return node;
|
||||||
}
|
} else {
|
||||||
|
await node.setExpanded(true, expandOpts);
|
||||||
if (childNoteId === noteId) {
|
}
|
||||||
return node;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
await node.setExpanded(true, expandOpts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parentNoteId = childNoteId;
|
parentNoteId = childNoteId;
|
||||||
@@ -129,9 +138,12 @@ async function expandToNote(notePath, expandOpts) {
|
|||||||
async function activateNote(notePath, noteLoadedListener) {
|
async function activateNote(notePath, noteLoadedListener) {
|
||||||
utils.assertArguments(notePath);
|
utils.assertArguments(notePath);
|
||||||
|
|
||||||
|
// notePath argument can contain only noteId which is not good when hoisted since
|
||||||
|
// then we need to check the whole note path
|
||||||
|
const runNotePath = await getRunPath(notePath);
|
||||||
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
|
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
|
||||||
|
|
||||||
if (hoistedNoteId !== 'root' && !notePath.includes(hoistedNoteId)) {
|
if (hoistedNoteId !== 'root' && !runNotePath.includes(hoistedNoteId)) {
|
||||||
if (!await confirmDialog.confirm("Requested note is outside of hoisted note subtree. Do you want to unhoist?")) {
|
if (!await confirmDialog.confirm("Requested note is outside of hoisted note subtree. Do you want to unhoist?")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -352,6 +364,7 @@ function clearSelectedNodes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function treeInitialized() {
|
async function treeInitialized() {
|
||||||
|
// - is used in mobile to indicate that we don't want to activate any note after load
|
||||||
if (startNotePath === '-') {
|
if (startNotePath === '-') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -363,7 +376,6 @@ async function treeInitialized() {
|
|||||||
startNotePath = null;
|
startNotePath = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// - is used in mobile to indicate that we don't want to activate any note after load
|
|
||||||
if (startNotePath) {
|
if (startNotePath) {
|
||||||
const node = await activateNote(startNotePath);
|
const node = await activateNote(startNotePath);
|
||||||
|
|
||||||
@@ -438,6 +450,16 @@ function initFancyTree(tree) {
|
|||||||
|
|
||||||
$span.append(unhoistButton);
|
$span.append(unhoistButton);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// this is done to automatically lazy load all expanded search notes after tree load
|
||||||
|
loadChildren: function(event, data) {
|
||||||
|
data.node.visit(function(subNode){
|
||||||
|
// Load all lazy/unloaded child nodes
|
||||||
|
// (which will trigger `loadChildren` recursively)
|
||||||
|
if( subNode.isUndefined() && subNode.isExpanded() ) {
|
||||||
|
subNode.load();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -476,9 +498,11 @@ async function loadTree() {
|
|||||||
return await treeBuilder.prepareTree(resp.notes, resp.branches, resp.relations);
|
return await treeBuilder.prepareTree(resp.notes, resp.branches, resp.relations);
|
||||||
}
|
}
|
||||||
|
|
||||||
function collapseTree(node = null) {
|
async function collapseTree(node = null) {
|
||||||
if (!node) {
|
if (!node) {
|
||||||
node = $tree.fancytree("getRootNode");
|
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
|
||||||
|
|
||||||
|
node = getNodesByNoteId(hoistedNoteId)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
node.setExpanded(false);
|
node.setExpanded(false);
|
||||||
@@ -519,9 +543,11 @@ async function setNoteTitle(noteId, title) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createNewTopLevelNote() {
|
async function createNewTopLevelNote() {
|
||||||
const rootNode = getNodesByNoteId('root')[0];
|
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
|
||||||
|
|
||||||
await createNote(rootNode, "root", "into", false);
|
const rootNode = getNodesByNoteId(hoistedNoteId)[0];
|
||||||
|
|
||||||
|
await createNote(rootNode, hoistedNoteId, "into", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createNote(node, parentNoteId, target, isProtected, saveSelection = false) {
|
async function createNote(node, parentNoteId, target, isProtected, saveSelection = false) {
|
||||||
@@ -566,7 +592,7 @@ async function createNote(node, parentNoteId, target, isProtected, saveSelection
|
|||||||
|
|
||||||
await noteDetailService.saveNoteIfChanged();
|
await noteDetailService.saveNoteIfChanged();
|
||||||
|
|
||||||
noteDetailService.addDetailLoadedListener(note.noteId, noteDetailService.focusOnTitle);
|
noteDetailService.addDetailLoadedListener(note.noteId, noteDetailService.focusAndSelectTitle);
|
||||||
|
|
||||||
const noteEntity = new NoteShort(treeCache, note);
|
const noteEntity = new NoteShort(treeCache, note);
|
||||||
const branchEntity = new Branch(treeCache, branch);
|
const branchEntity = new Branch(treeCache, branch);
|
||||||
@@ -597,7 +623,10 @@ async function createNote(node, parentNoteId, target, isProtected, saveSelection
|
|||||||
|
|
||||||
await node.getLastChild().setActive(true);
|
await node.getLastChild().setActive(true);
|
||||||
|
|
||||||
|
const parentNoteEntity = await treeCache.getNote(node.data.noteId);
|
||||||
|
|
||||||
node.folder = true;
|
node.folder = true;
|
||||||
|
node.icon = await treeBuilder.getIcon(parentNoteEntity); // icon might change into folder
|
||||||
node.renderTitle();
|
node.renderTitle();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ async function prepareNode(branch) {
|
|||||||
extraClasses: await getExtraClasses(note),
|
extraClasses: await getExtraClasses(note),
|
||||||
icon: await getIcon(note),
|
icon: await getIcon(note),
|
||||||
refKey: note.noteId,
|
refKey: note.noteId,
|
||||||
expanded: (note.type !== 'search' && branch.isExpanded) || hoistedNoteId === note.noteId
|
expanded: branch.isExpanded || hoistedNoteId === note.noteId
|
||||||
};
|
};
|
||||||
|
|
||||||
if (note.hasChildren() || note.type === 'search') {
|
if (note.hasChildren() || note.type === 'search') {
|
||||||
@@ -168,9 +168,24 @@ async function getExtraClasses(note) {
|
|||||||
|
|
||||||
extraClasses.push(note.type);
|
extraClasses.push(note.type);
|
||||||
|
|
||||||
|
if (note.mime) { // some notes should not have mime type (e.g. render)
|
||||||
|
extraClasses.push(getMimeTypeClass(note.mime));
|
||||||
|
}
|
||||||
|
|
||||||
return extraClasses.join(" ");
|
return extraClasses.join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMimeTypeClass(mime) {
|
||||||
|
const semicolonIdx = mime.indexOf(';');
|
||||||
|
|
||||||
|
if (semicolonIdx !== -1) {
|
||||||
|
// stripping everything following the semicolon
|
||||||
|
mime = mime.substr(0, semicolonIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'mime-' + mime.toLowerCase().replace(/[\W_]+/g,"-");
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
prepareTree,
|
prepareTree,
|
||||||
prepareBranch,
|
prepareBranch,
|
||||||
|
|||||||
@@ -158,7 +158,11 @@ class TreeCache {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
treeCache.childParentToBranch[childNoteId + '-' + newParentNoteId] = treeCache.childParentToBranch[childNoteId + '-' + oldParentNoteId];
|
const branchId = treeCache.childParentToBranch[childNoteId + '-' + oldParentNoteId];
|
||||||
|
const branch = await this.getBranch(branchId);
|
||||||
|
branch.parentNoteId = newParentNoteId;
|
||||||
|
|
||||||
|
treeCache.childParentToBranch[childNoteId + '-' + newParentNoteId] = branchId;
|
||||||
delete treeCache.childParentToBranch[childNoteId + '-' + oldParentNoteId]; // this is correct because we know that oldParentId isn't same as newParentId
|
delete treeCache.childParentToBranch[childNoteId + '-' + oldParentNoteId]; // this is correct because we know that oldParentId isn't same as newParentId
|
||||||
|
|
||||||
// remove old associations
|
// remove old associations
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ function formatTimeWithSeconds(date) {
|
|||||||
return padNum(date.getHours()) + ":" + padNum(date.getMinutes()) + ":" + padNum(date.getSeconds());
|
return padNum(date.getHours()) + ":" + padNum(date.getMinutes()) + ":" + padNum(date.getSeconds());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this is producing local time!
|
||||||
function formatDate(date) {
|
function formatDate(date) {
|
||||||
// return padNum(date.getDate()) + ". " + padNum(date.getMonth() + 1) + ". " + date.getFullYear();
|
// return padNum(date.getDate()) + ". " + padNum(date.getMonth() + 1) + ". " + date.getFullYear();
|
||||||
// instead of european format we'll just use ISO as that's pretty unambiguous
|
// instead of european format we'll just use ISO as that's pretty unambiguous
|
||||||
@@ -30,6 +31,7 @@ function formatDate(date) {
|
|||||||
return formatDateISO(date);
|
return formatDateISO(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this is producing local time!
|
||||||
function formatDateISO(date) {
|
function formatDateISO(date) {
|
||||||
return date.getFullYear() + "-" + padNum(date.getMonth() + 1) + "-" + padNum(date.getDate());
|
return date.getFullYear() + "-" + padNum(date.getMonth() + 1) + "-" + padNum(date.getDate());
|
||||||
}
|
}
|
||||||
@@ -137,7 +139,13 @@ function randomString(len) {
|
|||||||
|
|
||||||
function bindShortcut(keyboardShortcut, handler) {
|
function bindShortcut(keyboardShortcut, handler) {
|
||||||
if (isDesktop()) {
|
if (isDesktop()) {
|
||||||
|
if (isMac()) {
|
||||||
|
// use CMD (meta) instead of CTRL for all shortcuts
|
||||||
|
keyboardShortcut = keyboardShortcut.replace("ctrl", "meta");
|
||||||
|
}
|
||||||
|
|
||||||
$(document).bind('keydown', keyboardShortcut, e => {
|
$(document).bind('keydown', keyboardShortcut, e => {
|
||||||
|
console.log(e);
|
||||||
handler();
|
handler();
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -146,11 +154,15 @@ function bindShortcut(keyboardShortcut, handler) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isMobile() {
|
function isMobile() {
|
||||||
return window.device === "mobile";
|
return window.device === "mobile"
|
||||||
|
// window.device is not available in setup
|
||||||
|
|| (!window.device && /Mobi/.test(navigator.userAgent));
|
||||||
}
|
}
|
||||||
|
|
||||||
function isDesktop() {
|
function isDesktop() {
|
||||||
return window.device === "desktop";
|
return window.device === "desktop"
|
||||||
|
// window.device is not available in setup
|
||||||
|
|| (!window.device && !/Mobi/.test(navigator.userAgent));
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCookie(name, value) {
|
function setCookie(name, value) {
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import utils from "./services/utils.js";
|
import utils from "./services/utils.js";
|
||||||
|
import macInit from './services/mac_init.js';
|
||||||
|
|
||||||
|
macInit.init();
|
||||||
|
|
||||||
function SetupModel() {
|
function SetupModel() {
|
||||||
if (syncInProgress) {
|
if (syncInProgress) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -9,12 +9,12 @@
|
|||||||
*
|
*
|
||||||
* This section is automatically generated from the `skin-common.less` template.
|
* This section is automatically generated from the `skin-common.less` template.
|
||||||
*
|
*
|
||||||
* Copyright (c) 2008-2018, Martin Wendt (http://wwWendt.de)
|
* Copyright (c) 2008-2019, Martin Wendt (http://wwWendt.de)
|
||||||
* Released under the MIT license
|
* Released under the MIT license
|
||||||
* https://github.com/mar10/fancytree/wiki/LicenseInfo
|
* https://github.com/mar10/fancytree/wiki/LicenseInfo
|
||||||
*
|
*
|
||||||
* @version 2.30.0
|
* @version 2.30.2
|
||||||
* @date 2018-09-02T15:42:49Z
|
* @date 2019-01-13T08:17:01Z
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
/*------------------------------------------------------------------------------
|
/*------------------------------------------------------------------------------
|
||||||
* Helpers
|
* Helpers
|
||||||
@@ -336,7 +336,8 @@ span.fancytree-icon {
|
|||||||
.fancytree-loading span.fancytree-expander,
|
.fancytree-loading span.fancytree-expander,
|
||||||
.fancytree-loading span.fancytree-expander:hover,
|
.fancytree-loading span.fancytree-expander:hover,
|
||||||
.fancytree-statusnode-loading span.fancytree-icon,
|
.fancytree-statusnode-loading span.fancytree-icon,
|
||||||
.fancytree-statusnode-loading span.fancytree-icon:hover {
|
.fancytree-statusnode-loading span.fancytree-icon:hover,
|
||||||
|
span.fancytree-icon.fancytree-icon-loading {
|
||||||
background-image: url("../skin-win8/loading.gif");
|
background-image: url("../skin-win8/loading.gif");
|
||||||
background-position: 0px 0px;
|
background-position: 0px 0px;
|
||||||
}
|
}
|
||||||
@@ -479,6 +480,8 @@ ul.fancytree-container.fancytree-rtl.fancytree-no-connector > li {
|
|||||||
* 'table' extension
|
* 'table' extension
|
||||||
*----------------------------------------------------------------------------*/
|
*----------------------------------------------------------------------------*/
|
||||||
table.fancytree-ext-table {
|
table.fancytree-ext-table {
|
||||||
|
font-family: tahoma, arial, helvetica;
|
||||||
|
font-size: 10pt;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
/* ext-ariagrid */
|
/* ext-ariagrid */
|
||||||
}
|
}
|
||||||
@@ -536,6 +539,9 @@ table.fancytree-ext-columnview span.fancytree-node {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
table.fancytree-ext-columnview span.fancytree-node.fancytree-expanded {
|
table.fancytree-ext-columnview span.fancytree-node.fancytree-expanded {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
}
|
||||||
|
table.fancytree-ext-columnview span.fancytree-node.fancytree-active {
|
||||||
background-color: #CBE8F6;
|
background-color: #CBE8F6;
|
||||||
}
|
}
|
||||||
table.fancytree-ext-columnview .fancytree-has-children span.fancytree-cv-right {
|
table.fancytree-ext-columnview .fancytree-has-children span.fancytree-cv-right {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,7 @@
|
|||||||
|
body {
|
||||||
|
font-size: var(--main-font-size);
|
||||||
|
}
|
||||||
|
|
||||||
#container {
|
#container {
|
||||||
margin: 0 auto; /* center */
|
margin: 0 auto; /* center */
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
@@ -26,6 +30,7 @@
|
|||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
flex-basis: 60%;
|
flex-basis: 60%;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
font-size: var(--tree-font-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
#left-pane {
|
#left-pane {
|
||||||
@@ -36,7 +41,7 @@
|
|||||||
|
|
||||||
#header {
|
#header {
|
||||||
grid-area: header;
|
grid-area: header;
|
||||||
background-color: #f8f8f8;
|
background-color: var(--header-background-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
@@ -44,16 +49,25 @@
|
|||||||
|
|
||||||
#header button {
|
#header button {
|
||||||
padding: 1px 5px 1px 5px;
|
padding: 1px 5px 1px 5px;
|
||||||
font-size: small;
|
font-size: smaller;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
margin-top: 2px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#history-navigation {
|
||||||
|
margin: 0 15px 0 5px;
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#global-buttons {
|
#global-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
padding: 10px 0 10px 0;
|
padding: 10px 0 10px 0;
|
||||||
margin: 0 10px 0 16px;
|
margin: 0 20px 0 10px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ddd;
|
||||||
border-radius: 5px;
|
border-radius: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#context-menu-container {
|
#context-menu-container {
|
||||||
@@ -76,4 +90,8 @@
|
|||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
#note-detail-wrapper {
|
||||||
|
font-size: var(--detail-font-size);
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,6 @@ html, body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#tree {
|
#tree {
|
||||||
font-size: larger;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
@@ -45,7 +44,8 @@ html, body {
|
|||||||
position: relative;
|
position: relative;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
/* for some reason detail overflows a little bit so we subtract few pixels */
|
||||||
|
height: calc(100% - 25px);
|
||||||
/* large left padding is necessary for ckeditor gutter in detail-only (smartphone) layout */
|
/* large left padding is necessary for ckeditor gutter in detail-only (smartphone) layout */
|
||||||
padding-left: 35px;
|
padding-left: 35px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,13 @@
|
|||||||
.note-box {
|
.note-box {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
position: absolute !important;
|
position: absolute !important;
|
||||||
|
background-color: var(--accented-background-color);
|
||||||
|
color: var(--main-text-color);
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
border: 1px solid #666;
|
border: 1px solid #666;
|
||||||
box-shadow: 2px 2px 19px #999;
|
box-shadow: 2px 2px 19px #999;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
background-color: white;
|
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
@@ -28,7 +29,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.note-box:hover {
|
.note-box:hover {
|
||||||
background-color: #ddd;
|
background-color: var(--more-accented-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-box .title {
|
.note-box .title {
|
||||||
@@ -37,11 +38,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.connection-label.jtk-hover, .jtk-source-hover, .jtk-target-hover {
|
.connection-label.jtk-hover, .jtk-source-hover, .jtk-target-hover {
|
||||||
background-color: #ddd;
|
background-color: var(--more-accented-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.connection-label {
|
.connection-label {
|
||||||
background-color: white;
|
background-color: var(--accented-background-color);
|
||||||
|
color: var(--main-text-color);
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
padding: 0.3em;
|
padding: 0.3em;
|
||||||
border-radius: 0.5em;
|
border-radius: 0.5em;
|
||||||
@@ -64,10 +66,6 @@
|
|||||||
box-shadow: 0 0 6px black;
|
box-shadow: 0 0 6px black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.statemachine-demo .jtk-endpoint {
|
|
||||||
z-index: 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dragHover {
|
.dragHover {
|
||||||
border: 2px solid orange;
|
border: 2px solid orange;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,116 @@
|
|||||||
|
:root {
|
||||||
|
--main-font-size: normal;
|
||||||
|
--tree-font-size: normal;
|
||||||
|
--detail-font-size: normal;
|
||||||
|
|
||||||
|
--main-background-color: white;
|
||||||
|
--main-text-color: black;
|
||||||
|
--accented-background-color: #eee;
|
||||||
|
--more-accented-background-color: #ccc;
|
||||||
|
--header-background-color: #f8f8f8;
|
||||||
|
--button-background-color: #eee;
|
||||||
|
--button-border-color: #ddd;
|
||||||
|
--button-text-color: black;
|
||||||
|
--button-border-radius: 5px;
|
||||||
|
--muted-text-color: #444;
|
||||||
|
--input-text-color: black;
|
||||||
|
--input-background-color: white;
|
||||||
|
--modal-background-color: white;
|
||||||
|
--hover-item-text-color: black;
|
||||||
|
--hover-item-background-color: #eee;
|
||||||
|
--active-item-text-color: black;
|
||||||
|
--active-item-background-color: #ccc;
|
||||||
|
--menu-text-color: black;
|
||||||
|
--menu-background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-black {
|
||||||
|
--main-background-color: black;
|
||||||
|
--main-text-color: white;
|
||||||
|
--accented-background-color: #222;
|
||||||
|
--more-accented-background-color: #444;
|
||||||
|
--header-background-color: black;
|
||||||
|
--button-background-color: #333;
|
||||||
|
--button-border-color: #444;
|
||||||
|
--button-text-color: white;
|
||||||
|
--button-border-radius: 5px;
|
||||||
|
--muted-text-color: #ccc;
|
||||||
|
--input-text-color: white;
|
||||||
|
--input-background-color: black;
|
||||||
|
--modal-background-color: #222;
|
||||||
|
--hover-item-text-color: black;
|
||||||
|
--hover-item-background-color: #aaa;
|
||||||
|
--active-item-text-color: black;
|
||||||
|
--active-item-background-color: #ccc;
|
||||||
|
--menu-text-color: white;
|
||||||
|
--menu-background-color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-black .CodeMirror {
|
||||||
|
filter: invert(100%) hue-rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-dark {
|
||||||
|
--main-background-color: #333;
|
||||||
|
--main-text-color: white;
|
||||||
|
--accented-background-color: #555;
|
||||||
|
--more-accented-background-color: #777;
|
||||||
|
--header-background-color: #333;
|
||||||
|
--button-background-color: #555;
|
||||||
|
--button-border-color: #444;
|
||||||
|
--button-text-color: white;
|
||||||
|
--button-border-radius: 5px;
|
||||||
|
--muted-text-color: #ccc;
|
||||||
|
--input-text-color: white;
|
||||||
|
--input-background-color: #333;
|
||||||
|
--modal-background-color: #555;
|
||||||
|
--hover-item-text-color: black;
|
||||||
|
--hover-item-background-color: #aaa;
|
||||||
|
--active-item-text-color: black;
|
||||||
|
--active-item-background-color: #ccc;
|
||||||
|
--menu-text-color: white;
|
||||||
|
--menu-background-color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-dark .CodeMirror {
|
||||||
|
filter: invert(90%) hue-rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
/* this fixes FF filter vs. position fixed bug: https://github.com/zadam/trilium/issues/233 */
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
/* Fix for CKEditor block gutter icon "stretching" body and causing scrollbar to appear after pressing enter
|
/* Fix for CKEditor block gutter icon "stretching" body and causing scrollbar to appear after pressing enter
|
||||||
on the last line of the editor. */
|
on the last line of the editor. */
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--main-background-color);
|
||||||
|
color: var(--main-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
input, select {
|
||||||
|
color: var(--input-text-color) !important;
|
||||||
|
background: var(--input-background-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-text {
|
||||||
|
background-color: var(--accented-background-color) !important;
|
||||||
|
color: var(--muted-text-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.close {
|
||||||
|
color: var(--main-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background-color: var(--modal-background-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link.active {
|
||||||
|
background-color: var(--more-accented-background-color) !important;
|
||||||
|
color: var(--main-text-color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#title-container {
|
#title-container {
|
||||||
@@ -10,12 +119,23 @@ body {
|
|||||||
|
|
||||||
#note-title {
|
#note-title {
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
font-size: x-large;
|
margin-right: 10px;
|
||||||
|
font-size: 150%;
|
||||||
border: 0;
|
border: 0;
|
||||||
width: 5em;
|
width: 5em;
|
||||||
flex-grow: 100;
|
flex-grow: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul.fancytree-container {
|
||||||
|
/* override specific size from fancytree.css */
|
||||||
|
font-family: inherit !important;
|
||||||
|
font-size: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fancytree-title {
|
||||||
|
margin-left: 7px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.fancytree-node:not(.fancytree-loading) .fancytree-expander {
|
.fancytree-node:not(.fancytree-loading) .fancytree-expander {
|
||||||
background: none;
|
background: none;
|
||||||
width: auto;
|
width: auto;
|
||||||
@@ -78,8 +198,6 @@ body {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#note-detail-text { font-size: 1.1em; }
|
|
||||||
|
|
||||||
#note-detail-text h1 { font-size: 2.0em; }
|
#note-detail-text h1 { font-size: 2.0em; }
|
||||||
#note-detail-text h2 { font-size: 1.8em; }
|
#note-detail-text h2 { font-size: 1.8em; }
|
||||||
#note-detail-text h3 { font-size: 1.6em; }
|
#note-detail-text h3 { font-size: 1.6em; }
|
||||||
@@ -106,12 +224,18 @@ body {
|
|||||||
|
|
||||||
ul.fancytree-container {
|
ul.fancytree-container {
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
|
background-color: inherit !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fancytree-custom-icon {
|
.fancytree-custom-icon {
|
||||||
font-size: 1.3em;
|
font-size: 1.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.fancytree-title {
|
||||||
|
color: inherit !important;
|
||||||
|
background: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
span.fancytree-node.protected > span.fancytree-custom-icon {
|
span.fancytree-node.protected > span.fancytree-custom-icon {
|
||||||
filter: drop-shadow(2px 2px 2px black);
|
filter: drop-shadow(2px 2px 2px black);
|
||||||
}
|
}
|
||||||
@@ -129,10 +253,25 @@ span.fancytree-node.fancytree-active-clone:not(.fancytree-active) .fancytree-tit
|
|||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* By default not focused active tree item is not easily visible, this makes it more visible */
|
span.fancytree-active.fancytree-focused .fancytree-title {
|
||||||
span.fancytree-active:not(.fancytree-focused) .fancytree-title {
|
color: var(--active-item-text-color) !important;
|
||||||
background-color: #ddd !important;
|
background-color: var(--active-item-background-color) !important;
|
||||||
border-color: #555 !important;
|
border-color: #ddd !important;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.fancytree-active:not(.fancytree-focused) .fancytree-title, span.fancytree-selected .fancytree-title {
|
||||||
|
color: var(--hover-item-text-color) !important;
|
||||||
|
background-color: var(--hover-item-background-color) !important;
|
||||||
|
border-color: #ddd !important;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.fancytree-node:not(.fancytree-active):hover span.fancytree-title {
|
||||||
|
color: var(--hover-item-text-color) !important;
|
||||||
|
background-color: var(--hover-item-background-color) !important;
|
||||||
|
border-color: #ddd !important;
|
||||||
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-autocomplete {
|
.ui-autocomplete {
|
||||||
@@ -155,17 +294,6 @@ span.fancytree-active:not(.fancytree-focused) .fancytree-title {
|
|||||||
color: #337ab7 !important;
|
color: #337ab7 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#header-title {
|
|
||||||
padding: 5px 20px 5px 10px;
|
|
||||||
font-size: large;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header .btn-sm {
|
|
||||||
margin-bottom: 2px;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ui-tooltip {
|
div.ui-tooltip {
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
max-height: 600px;
|
max-height: 600px;
|
||||||
@@ -231,7 +359,7 @@ div.ui-tooltip {
|
|||||||
|
|
||||||
/* Allow to use <kbd> elements inside the title to define shortcut hints. */
|
/* Allow to use <kbd> elements inside the title to define shortcut hints. */
|
||||||
.ui-menu kbd, button kbd {
|
.ui-menu kbd, button kbd {
|
||||||
color: black;
|
color: var(--muted-text-color);
|
||||||
border: none;
|
border: none;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
@@ -252,8 +380,14 @@ div.ui-tooltip {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
color: var(--menu-text-color) !important;
|
||||||
|
background-color: var(--menu-background-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.dropdown-menu a:hover:not(.disabled) {
|
.dropdown-menu a:hover:not(.disabled) {
|
||||||
background-color: #eee !important;
|
color: var(--hover-item-text-color) !important;
|
||||||
|
background-color: var(--hover-item-background-color) !important;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +397,7 @@ div.ui-tooltip {
|
|||||||
|
|
||||||
.dropdown-menu kbd
|
.dropdown-menu kbd
|
||||||
{
|
{
|
||||||
color: black;
|
color: var(--muted-text-color);
|
||||||
border: none;
|
border: none;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
@@ -331,7 +465,7 @@ div.ui-tooltip {
|
|||||||
|
|
||||||
#file-table th, #file-table td {
|
#file-table th, #file-table td {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
font-size: large;
|
font-size: larger;
|
||||||
}
|
}
|
||||||
|
|
||||||
#children-overview {
|
#children-overview {
|
||||||
@@ -347,9 +481,9 @@ div.ui-tooltip {
|
|||||||
|
|
||||||
.child-overview {
|
.child-overview {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: large;
|
font-size: larger;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background: #f4f4f4;
|
background: var(--accented-background-color);
|
||||||
width: 150px;
|
width: 150px;
|
||||||
height: 90px;
|
height: 90px;
|
||||||
line-height: 2em;
|
line-height: 2em;
|
||||||
@@ -363,7 +497,7 @@ div.ui-tooltip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.child-overview a {
|
.child-overview a {
|
||||||
color: #444;
|
color: var(--muted-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
#sql-console-query {
|
#sql-console-query {
|
||||||
@@ -377,18 +511,18 @@ div.ui-tooltip {
|
|||||||
height: 150px;
|
height: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#history-navigation {
|
.btn {
|
||||||
margin: 0 20px 0 5px;
|
border-radius: var(--button-border-radius);
|
||||||
display: flex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn:not(.btn-primary):not(.btn-danger) {
|
.btn:not(.btn-primary):not(.btn-secondary):not(.btn-danger) {
|
||||||
border-color: #bbb;
|
border-color: var(--button-border-color);
|
||||||
background-color: #eee;
|
background-color: var(--button-background-color);
|
||||||
|
color: var(--button-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn.active:not(.btn-primary) {
|
.btn.active:not(.btn-primary) {
|
||||||
background-color: #ccc;
|
background-color: #ccc !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#note-path-list a.current {
|
#note-path-list a.current {
|
||||||
@@ -409,33 +543,6 @@ button.icon-button {
|
|||||||
width: 15em;
|
width: 15em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Themes */
|
|
||||||
|
|
||||||
html {
|
|
||||||
/* this fixes FF filter vs. position fixed bug: https://github.com/zadam/trilium/issues/233 */
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.theme-black, html.theme-black img, html.theme-black video {
|
|
||||||
filter: invert(100%) hue-rotate(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
html.theme-black body {
|
|
||||||
background: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.theme-dark {
|
|
||||||
filter: invert(90%) hue-rotate(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
html.theme-dark img, html.theme-dark video {
|
|
||||||
filter: invert(100%) hue-rotate(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
html.theme-dark body {
|
|
||||||
background: #191819;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck.ck-block-toolbar-button {
|
.ck.ck-block-toolbar-button {
|
||||||
transform: translateX(10px);
|
transform: translateX(10px);
|
||||||
}
|
}
|
||||||
@@ -538,13 +645,14 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tooltip-inner {
|
.tooltip-inner {
|
||||||
background-color: #fbfbfb !important;
|
background-color: var(--accented-background-color) !important;
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
/* height needs to stay small because tooltip has problem when it can't fit to either top or bottom of the cursor */
|
/* height needs to stay small because tooltip has problem when it can't fit to either top or bottom of the cursor */
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: black;
|
color: var(--main-text-color);
|
||||||
border: 1px solid #aaa;
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 5px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,7 +680,7 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
|
|||||||
|
|
||||||
.algolia-autocomplete .aa-dropdown-menu {
|
.algolia-autocomplete .aa-dropdown-menu {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #fff;
|
background-color: var(--main-background-color);
|
||||||
border: 1px solid #999;
|
border: 1px solid #999;
|
||||||
border-top: none;
|
border-top: none;
|
||||||
z-index: 2000 !important;
|
z-index: 2000 !important;
|
||||||
@@ -594,7 +702,8 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
|
|||||||
}
|
}
|
||||||
|
|
||||||
.algolia-autocomplete .aa-dropdown-menu .aa-suggestion.aa-cursor {
|
.algolia-autocomplete .aa-dropdown-menu .aa-suggestion.aa-cursor {
|
||||||
background-color: #B2D7FF;
|
color: var(--hover-item-text-color);
|
||||||
|
background-color: var(--hover-item-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.help-button {
|
.help-button {
|
||||||
@@ -660,7 +769,7 @@ div[data-notify="container"] {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
top: 11px;
|
top: 11px;
|
||||||
font-size: x-large;
|
font-size: 150%;
|
||||||
color: #777;
|
color: #777;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
@@ -721,4 +830,13 @@ div[data-notify="container"] {
|
|||||||
100% {
|
100% {
|
||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck-content .image > figcaption {
|
||||||
|
color: var(--main-text-color);
|
||||||
|
background-color: var(--accented-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#options-dialog input[type=number] {
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
const noteService = require('../../services/notes');
|
const noteService = require('../../services/notes');
|
||||||
const protectedSessionService = require('../../services/protected_session');
|
const protectedSessionService = require('../../services/protected_session');
|
||||||
const repository = require('../../services/repository');
|
const repository = require('../../services/repository');
|
||||||
|
const utils = require('../../services/utils');
|
||||||
|
|
||||||
async function uploadFile(req) {
|
async function uploadFile(req) {
|
||||||
const parentNoteId = req.params.parentNoteId;
|
const parentNoteId = req.params.parentNoteId;
|
||||||
@@ -49,7 +50,7 @@ async function downloadFile(req, res) {
|
|||||||
const originalFileName = await note.getLabel('originalFileName');
|
const originalFileName = await note.getLabel('originalFileName');
|
||||||
const fileName = originalFileName ? originalFileName.value : note.title;
|
const fileName = originalFileName ? originalFileName.value : note.title;
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'file; filename="' + fileName + '"');
|
res.setHeader('Content-Disposition', utils.getContentDisposition(fileName));
|
||||||
res.setHeader('Content-Type', note.mime);
|
res.setHeader('Content-Type', note.mime);
|
||||||
|
|
||||||
res.send(note.content);
|
res.send(note.content);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const tarImportService = require('../../services/import/tar');
|
|||||||
const singleImportService = require('../../services/import/single');
|
const singleImportService = require('../../services/import/single');
|
||||||
const cls = require('../../services/cls');
|
const cls = require('../../services/cls');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const noteCacheService = require('../../services/note_cache');
|
||||||
|
|
||||||
async function importToBranch(req) {
|
async function importToBranch(req) {
|
||||||
const parentNoteId = req.params.parentNoteId;
|
const parentNoteId = req.params.parentNoteId;
|
||||||
@@ -28,24 +29,32 @@ async function importToBranch(req) {
|
|||||||
// and may produce unintended consequences
|
// and may produce unintended consequences
|
||||||
cls.disableEntityEvents();
|
cls.disableEntityEvents();
|
||||||
|
|
||||||
|
let note; // typically root of the import - client can show it after finishing the import
|
||||||
|
|
||||||
if (extension === '.tar') {
|
if (extension === '.tar') {
|
||||||
return await tarImportService.importTar(file.buffer, parentNote);
|
note = await tarImportService.importTar(file.buffer, parentNote);
|
||||||
}
|
}
|
||||||
else if (extension === '.opml') {
|
else if (extension === '.opml') {
|
||||||
return await opmlImportService.importOpml(file.buffer, parentNote);
|
note = await opmlImportService.importOpml(file.buffer, parentNote);
|
||||||
}
|
}
|
||||||
else if (extension === '.md') {
|
else if (extension === '.md') {
|
||||||
return await singleImportService.importMarkdown(file, parentNote);
|
note = await singleImportService.importMarkdown(file, parentNote);
|
||||||
}
|
}
|
||||||
else if (extension === '.html' || extension === '.htm') {
|
else if (extension === '.html' || extension === '.htm') {
|
||||||
return await singleImportService.importHtml(file, parentNote);
|
note = await singleImportService.importHtml(file, parentNote);
|
||||||
}
|
}
|
||||||
else if (extension === '.enex') {
|
else if (extension === '.enex') {
|
||||||
return await enexImportService.importEnex(file, parentNote);
|
note = await enexImportService.importEnex(file, parentNote);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return [400, `Unrecognized extension ${extension}, must be .tar or .opml`];
|
return [400, `Unrecognized extension ${extension}, must be .tar or .opml`];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// import has deactivated note events so note cache is not updated
|
||||||
|
// instead we force it to reload (can be async)
|
||||||
|
noteCacheService.load();
|
||||||
|
|
||||||
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ async function loginSync(req) {
|
|||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
if (Math.abs(timestamp.getTime() - now.getTime()) > 5000) {
|
// login token is valid for 5 minutes
|
||||||
|
if (Math.abs(timestamp.getTime() - now.getTime()) > 5 * 60 * 1000) {
|
||||||
return [400, { message: 'Auth request time is out of sync' }];
|
return [400, { message: 'Auth request time is out of sync' }];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const log = require('../../services/log');
|
|||||||
|
|
||||||
// options allowed to be updated directly in options dialog
|
// options allowed to be updated directly in options dialog
|
||||||
const ALLOWED_OPTIONS = ['protectedSessionTimeout', 'noteRevisionSnapshotTimeInterval',
|
const ALLOWED_OPTIONS = ['protectedSessionTimeout', 'noteRevisionSnapshotTimeInterval',
|
||||||
'zoomFactor', 'theme', 'syncServerHost', 'syncServerTimeout', 'syncProxy', 'leftPaneMinWidth', 'leftPaneWidthPercent', 'hoistedNoteId'];
|
'zoomFactor', 'theme', 'syncServerHost', 'syncServerTimeout', 'syncProxy', 'leftPaneMinWidth', 'leftPaneWidthPercent', 'hoistedNoteId', 'mainFontSize', 'treeFontSize', 'detailFontSize'];
|
||||||
|
|
||||||
async function getOptions() {
|
async function getOptions() {
|
||||||
return await optionService.getOptionsMap(ALLOWED_OPTIONS);
|
return await optionService.getOptionsMap(ALLOWED_OPTIONS);
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ async function getStartupBundles() {
|
|||||||
const bundles = [];
|
const bundles = [];
|
||||||
|
|
||||||
for (const note of notes) {
|
for (const note of notes) {
|
||||||
const bundle = await scriptService.getScriptBundle(note);
|
const bundle = await scriptService.getScriptBundleForFrontend(note);
|
||||||
|
|
||||||
if (bundle) {
|
if (bundle) {
|
||||||
bundles.push(bundle);
|
bundles.push(bundle);
|
||||||
@@ -53,14 +53,26 @@ async function getRelationBundles(req) {
|
|||||||
const bundles = [];
|
const bundles = [];
|
||||||
|
|
||||||
for (const noteId of uniqueNoteIds) {
|
for (const noteId of uniqueNoteIds) {
|
||||||
bundles.push(await scriptService.getScriptBundleForNoteId(noteId));
|
const note = await repository.getNote(noteId);
|
||||||
|
|
||||||
|
if (!note.isJavaScript() || note.getScriptEnv() !== 'frontend') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundle = await scriptService.getScriptBundleForFrontend(note);
|
||||||
|
|
||||||
|
if (bundle) {
|
||||||
|
bundles.push(bundle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bundles;
|
return bundles;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getBundle(req) {
|
async function getBundle(req) {
|
||||||
return await scriptService.getScriptBundleForNoteId(req.params.noteId);
|
const note = await repository.getNote(req.params.noteId);
|
||||||
|
|
||||||
|
return await scriptService.getScriptBundleForFrontend(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ async function index(req, res) {
|
|||||||
leftPaneMinWidth: parseInt(options.leftPaneMinWidth),
|
leftPaneMinWidth: parseInt(options.leftPaneMinWidth),
|
||||||
leftPaneWidthPercent: parseInt(options.leftPaneWidthPercent),
|
leftPaneWidthPercent: parseInt(options.leftPaneWidthPercent),
|
||||||
rightPaneWidthPercent: 100 - parseInt(options.leftPaneWidthPercent),
|
rightPaneWidthPercent: 100 - parseInt(options.leftPaneWidthPercent),
|
||||||
|
mainFontSize: parseInt(options.mainFontSize),
|
||||||
|
treeFontSize: parseInt(options.treeFontSize),
|
||||||
|
detailFontSize: parseInt(options.detailFontSize),
|
||||||
sourceId: await sourceIdService.generateSourceId(),
|
sourceId: await sourceIdService.generateSourceId(),
|
||||||
maxSyncIdAtLoad: await sql.getValue("SELECT MAX(id) FROM sync"),
|
maxSyncIdAtLoad: await sql.getValue("SELECT MAX(id) FROM sync"),
|
||||||
instanceName: config.General ? config.General.instanceName : null,
|
instanceName: config.General ? config.General.instanceName : null,
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ async function anonymize() {
|
|||||||
await db.run("UPDATE notes SET title = 'title', content = 'text'");
|
await db.run("UPDATE notes SET title = 'title', content = 'text'");
|
||||||
await db.run("UPDATE note_revisions SET title = 'title', content = 'text'");
|
await db.run("UPDATE note_revisions SET title = 'title', content = 'text'");
|
||||||
await db.run("UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL");
|
await db.run("UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL");
|
||||||
await db.run("UPDATE images SET data = NULL");
|
|
||||||
await db.run(`UPDATE options SET value = 'anonymized' WHERE name IN
|
await db.run(`UPDATE options SET value = 'anonymized' WHERE name IN
|
||||||
('documentSecret', 'encryptedDataKey', 'passwordVerificationHash',
|
('documentSecret', 'encryptedDataKey', 'passwordVerificationHash',
|
||||||
'passwordVerificationSalt', 'passwordDerivedKeySalt')`);
|
'passwordVerificationSalt', 'passwordDerivedKeySalt')`);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const {APP_PNG_ICON_DIR, ELECTRON_APP_ROOT_DIR} = require("./resource_dir");
|
const {ELECTRON_APP_ROOT_DIR} = require("./resource_dir");
|
||||||
const log = require("./log");
|
const log = require("./log");
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@@ -11,7 +11,7 @@ const utils = require('./utils');
|
|||||||
const template = `[Desktop Entry]
|
const template = `[Desktop Entry]
|
||||||
Type=Application
|
Type=Application
|
||||||
Name=Trilium Notes
|
Name=Trilium Notes
|
||||||
Icon=#APP_PNG_ICON_DIR#/128x128.png
|
Icon=#APP_ROOT_DIR#/icon.png
|
||||||
Exec=#EXE_PATH#
|
Exec=#EXE_PATH#
|
||||||
Categories=Office
|
Categories=Office
|
||||||
Terminal=false
|
Terminal=false
|
||||||
@@ -50,7 +50,7 @@ function installLocalAppIcon() {
|
|||||||
|
|
||||||
function getDesktopFileContent() {
|
function getDesktopFileContent() {
|
||||||
return template
|
return template
|
||||||
.replace("#APP_PNG_ICON_DIR#", escapePath(APP_PNG_ICON_DIR))
|
.replace("#APP_ROOT_DIR#", escapePath(ELECTRON_APP_ROOT_DIR))
|
||||||
.replace("#EXE_PATH#", escapePath(getExePath()));
|
.replace("#EXE_PATH#", escapePath(getExePath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ const build = require('./build');
|
|||||||
const packageJson = require('../../package');
|
const packageJson = require('../../package');
|
||||||
const {TRILIUM_DATA_DIR} = require('./data_dir');
|
const {TRILIUM_DATA_DIR} = require('./data_dir');
|
||||||
|
|
||||||
const APP_DB_VERSION = 121;
|
const APP_DB_VERSION = 124;
|
||||||
const SYNC_VERSION = 3;
|
const SYNC_VERSION = 4;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
appVersion: packageJson.version,
|
appVersion: packageJson.version,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const repository = require('./repository');
|
|||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const cloningService = require('./cloning');
|
const cloningService = require('./cloning');
|
||||||
const messagingService = require('./messaging');
|
const messagingService = require('./messaging');
|
||||||
|
const appInfo = require('./app_info');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the main backend API interface for scripts. It's published in the local "api" object.
|
* This is the main backend API interface for scripts. It's published in the local "api" object.
|
||||||
@@ -234,6 +235,11 @@ function BackendScriptApi(startNote, currentNote, originEntity) {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
this.refreshTree = () => messagingService.sendMessageToAllClients({ type: 'refresh-tree' });
|
this.refreshTree = () => messagingService.sendMessageToAllClients({ type: 'refresh-tree' });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {{syncVersion, appVersion, buildRevision, dbVersion, dataDirectory, buildDate}|*} - object representing basic info about running Trilium version
|
||||||
|
*/
|
||||||
|
this.getAppInfo = () => appInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = BackendScriptApi;
|
module.exports = BackendScriptApi;
|
||||||
@@ -1 +1 @@
|
|||||||
module.exports = { buildDate:"2019-01-04T23:33:32+01:00", buildRevision: "5d74dcd2564ff1341550ade1250aa9d790abc056" };
|
module.exports = { buildDate:"2019-01-22T23:01:32+01:00", buildRevision: "2ac560c56e2d347fccc0ad51b8d62999408a7f74" };
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ const repository = require('./repository');
|
|||||||
const Branch = require('../entities/branch');
|
const Branch = require('../entities/branch');
|
||||||
|
|
||||||
async function cloneNoteToParent(noteId, parentNoteId, prefix) {
|
async function cloneNoteToParent(noteId, parentNoteId, prefix) {
|
||||||
|
if (await isNoteDeleted(noteId) || await isNoteDeleted(parentNoteId)) {
|
||||||
|
return { success: false, message: 'Note is deleted.' };
|
||||||
|
}
|
||||||
|
|
||||||
const validationResult = await treeService.validateParentChild(parentNoteId, noteId);
|
const validationResult = await treeService.validateParentChild(parentNoteId, noteId);
|
||||||
|
|
||||||
if (!validationResult.success) {
|
if (!validationResult.success) {
|
||||||
@@ -27,6 +31,10 @@ async function cloneNoteToParent(noteId, parentNoteId, prefix) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) {
|
async function ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) {
|
||||||
|
if (await isNoteDeleted(noteId) || await isNoteDeleted(parentNoteId)) {
|
||||||
|
return { success: false, message: 'Note is deleted.' };
|
||||||
|
}
|
||||||
|
|
||||||
const validationResult = await treeService.validateParentChild(parentNoteId, noteId);
|
const validationResult = await treeService.validateParentChild(parentNoteId, noteId);
|
||||||
|
|
||||||
if (!validationResult.success) {
|
if (!validationResult.success) {
|
||||||
@@ -61,6 +69,10 @@ async function toggleNoteInParent(present, noteId, parentNoteId, prefix) {
|
|||||||
async function cloneNoteAfter(noteId, afterBranchId) {
|
async function cloneNoteAfter(noteId, afterBranchId) {
|
||||||
const afterNote = await treeService.getBranch(afterBranchId);
|
const afterNote = await treeService.getBranch(afterBranchId);
|
||||||
|
|
||||||
|
if (await isNoteDeleted(noteId) || await isNoteDeleted(afterNote.parentNoteId)) {
|
||||||
|
return { success: false, message: 'Note is deleted.' };
|
||||||
|
}
|
||||||
|
|
||||||
const validationResult = await treeService.validateParentChild(afterNote.parentNoteId, noteId);
|
const validationResult = await treeService.validateParentChild(afterNote.parentNoteId, noteId);
|
||||||
|
|
||||||
if (!validationResult.result) {
|
if (!validationResult.result) {
|
||||||
@@ -84,6 +96,12 @@ async function cloneNoteAfter(noteId, afterBranchId) {
|
|||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function isNoteDeleted(noteId) {
|
||||||
|
const note = await repository.getNote(noteId);
|
||||||
|
|
||||||
|
return note.isDeleted;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
cloneNoteToParent,
|
cloneNoteToParent,
|
||||||
ensureNoteIsPresentInParent,
|
ensureNoteIsPresentInParent,
|
||||||
|
|||||||
@@ -101,6 +101,26 @@ async function fixEmptyRelationTargets(errorList) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fixUndeletedBranches() {
|
||||||
|
const undeletedBranches = await sql.getRows(`
|
||||||
|
SELECT
|
||||||
|
branchId, noteId
|
||||||
|
FROM
|
||||||
|
branches
|
||||||
|
JOIN notes USING(noteId)
|
||||||
|
WHERE
|
||||||
|
notes.isDeleted = 1
|
||||||
|
AND branches.isDeleted = 0`);
|
||||||
|
|
||||||
|
for (const {branchId, noteId} of undeletedBranches) {
|
||||||
|
const branch = await repository.getBranch(branchId);
|
||||||
|
branch.isDeleted = true;
|
||||||
|
await branch.save();
|
||||||
|
|
||||||
|
log.info(`Branch ${branchId} has been deleted since associated note ${noteId} is deleted.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function runAllChecks() {
|
async function runAllChecks() {
|
||||||
const errorList = [];
|
const errorList = [];
|
||||||
|
|
||||||
@@ -125,16 +145,7 @@ async function runAllChecks() {
|
|||||||
notes.noteId IS NULL`,
|
notes.noteId IS NULL`,
|
||||||
"Missing notes records for following branch ID > note ID", errorList);
|
"Missing notes records for following branch ID > note ID", errorList);
|
||||||
|
|
||||||
await runCheck(`
|
await fixUndeletedBranches();
|
||||||
SELECT
|
|
||||||
branchId
|
|
||||||
FROM
|
|
||||||
branches
|
|
||||||
JOIN notes USING(noteId)
|
|
||||||
WHERE
|
|
||||||
notes.isDeleted = 1
|
|
||||||
AND branches.isDeleted = 0`,
|
|
||||||
"Branch is not deleted even though main note is deleted for following branch IDs", errorList);
|
|
||||||
|
|
||||||
await runCheck(`
|
await runCheck(`
|
||||||
SELECT
|
SELECT
|
||||||
|
|||||||
@@ -18,25 +18,26 @@ function shaArray(content) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function pad(data) {
|
function pad(data) {
|
||||||
let padded = Array.from(data);
|
if (data.length > 16) {
|
||||||
|
data = data.slice(0, 16);
|
||||||
if (data.length >= 16) {
|
|
||||||
padded = padded.slice(0, 16);
|
|
||||||
}
|
}
|
||||||
else {
|
else if (data.length < 16) {
|
||||||
padded = padded.concat(Array(16 - padded.length).fill(0));
|
const zeros = Array(16 - data.length).fill(0);
|
||||||
|
|
||||||
|
data = Buffer.concat([data, Buffer.from(zeros)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Buffer.from(padded);
|
return Buffer.from(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
function encrypt(key, iv, plainText) {
|
function encrypt(key, plainText, ivLength = 13) {
|
||||||
if (!key) {
|
if (!key) {
|
||||||
throw new Error("No data key!");
|
throw new Error("No data key!");
|
||||||
}
|
}
|
||||||
|
|
||||||
const plainTextBuffer = Buffer.from(plainText);
|
const plainTextBuffer = Buffer.from(plainText);
|
||||||
|
|
||||||
|
const iv = crypto.randomBytes(ivLength);
|
||||||
const cipher = crypto.createCipheriv('aes-128-cbc', pad(key), pad(iv));
|
const cipher = crypto.createCipheriv('aes-128-cbc', pad(key), pad(iv));
|
||||||
|
|
||||||
const digest = shaArray(plainTextBuffer).slice(0, 4);
|
const digest = shaArray(plainTextBuffer).slice(0, 4);
|
||||||
@@ -45,17 +46,23 @@ function encrypt(key, iv, plainText) {
|
|||||||
|
|
||||||
const encryptedData = Buffer.concat([cipher.update(digestWithPayload), cipher.final()]);
|
const encryptedData = Buffer.concat([cipher.update(digestWithPayload), cipher.final()]);
|
||||||
|
|
||||||
return encryptedData.toString('base64');
|
const encryptedDataWithIv = Buffer.concat([iv, encryptedData]);
|
||||||
|
|
||||||
|
return encryptedDataWithIv.toString('base64');
|
||||||
}
|
}
|
||||||
|
|
||||||
function decrypt(key, iv, cipherText) {
|
function decrypt(key, cipherText, ivLength = 13) {
|
||||||
if (!key) {
|
if (!key) {
|
||||||
return "[protected]";
|
return "[protected]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cipherTextBufferWithIv = Buffer.from(cipherText, 'base64');
|
||||||
|
const iv = cipherTextBufferWithIv.slice(0, ivLength);
|
||||||
|
|
||||||
|
const cipherTextBuffer = cipherTextBufferWithIv.slice(ivLength);
|
||||||
|
|
||||||
const decipher = crypto.createDecipheriv('aes-128-cbc', pad(key), pad(iv));
|
const decipher = crypto.createDecipheriv('aes-128-cbc', pad(key), pad(iv));
|
||||||
|
|
||||||
const cipherTextBuffer = Buffer.from(cipherText, 'base64');
|
|
||||||
const decryptedBytes = Buffer.concat([decipher.update(cipherTextBuffer), decipher.final()]);
|
const decryptedBytes = Buffer.concat([decipher.update(cipherTextBuffer), decipher.final()]);
|
||||||
|
|
||||||
const digest = decryptedBytes.slice(0, 4);
|
const digest = decryptedBytes.slice(0, 4);
|
||||||
@@ -70,8 +77,8 @@ function decrypt(key, iv, cipherText) {
|
|||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
function decryptString(dataKey, iv, cipherText) {
|
function decryptString(dataKey, cipherText) {
|
||||||
const buffer = decrypt(dataKey, iv, cipherText);
|
const buffer = decrypt(dataKey, cipherText);
|
||||||
|
|
||||||
const str = buffer.toString('utf-8');
|
const str = buffer.toString('utf-8');
|
||||||
|
|
||||||
@@ -84,26 +91,8 @@ function decryptString(dataKey, iv, cipherText) {
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
function noteTitleIv(iv) {
|
|
||||||
if (!iv) {
|
|
||||||
throw new Error("Empty iv!");
|
|
||||||
}
|
|
||||||
|
|
||||||
return "0" + iv;
|
|
||||||
}
|
|
||||||
|
|
||||||
function noteContentIv(iv) {
|
|
||||||
if (!iv) {
|
|
||||||
throw new Error("Empty iv!");
|
|
||||||
}
|
|
||||||
|
|
||||||
return "1" + iv;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
encrypt,
|
encrypt,
|
||||||
decrypt,
|
decrypt,
|
||||||
decryptString,
|
decryptString
|
||||||
noteTitleIv,
|
|
||||||
noteContentIv
|
|
||||||
};
|
};
|
||||||
@@ -47,8 +47,8 @@ async function getRootCalendarNote() {
|
|||||||
return rootNote;
|
return rootNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getYearNote(dateTimeStr, rootNote) {
|
async function getYearNote(dateStr, rootNote) {
|
||||||
const yearStr = dateTimeStr.substr(0, 4);
|
const yearStr = dateStr.substr(0, 4);
|
||||||
|
|
||||||
let yearNote = await attributeService.getNoteWithLabel(YEAR_LABEL, yearStr);
|
let yearNote = await attributeService.getNoteWithLabel(YEAR_LABEL, yearStr);
|
||||||
|
|
||||||
@@ -66,19 +66,19 @@ async function getYearNote(dateTimeStr, rootNote) {
|
|||||||
return yearNote;
|
return yearNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMonthNote(dateTimeStr, rootNote) {
|
async function getMonthNote(dateStr, rootNote) {
|
||||||
const monthStr = dateTimeStr.substr(0, 7);
|
const monthStr = dateStr.substr(0, 7);
|
||||||
const monthNumber = dateTimeStr.substr(5, 2);
|
const monthNumber = dateStr.substr(5, 2);
|
||||||
|
|
||||||
let monthNote = await attributeService.getNoteWithLabel(MONTH_LABEL, monthStr);
|
let monthNote = await attributeService.getNoteWithLabel(MONTH_LABEL, monthStr);
|
||||||
|
|
||||||
if (!monthNote) {
|
if (!monthNote) {
|
||||||
const yearNote = await getYearNote(dateTimeStr, rootNote);
|
const yearNote = await getYearNote(dateStr, rootNote);
|
||||||
|
|
||||||
monthNote = await getNoteStartingWith(yearNote.noteId, monthNumber);
|
monthNote = await getNoteStartingWith(yearNote.noteId, monthNumber);
|
||||||
|
|
||||||
if (!monthNote) {
|
if (!monthNote) {
|
||||||
const dateObj = dateUtils.parseDate(dateTimeStr);
|
const dateObj = dateUtils.parseLocalDate(dateStr);
|
||||||
|
|
||||||
const noteTitle = monthNumber + " - " + MONTHS[dateObj.getMonth()];
|
const noteTitle = monthNumber + " - " + MONTHS[dateObj.getMonth()];
|
||||||
|
|
||||||
@@ -92,21 +92,20 @@ async function getMonthNote(dateTimeStr, rootNote) {
|
|||||||
return monthNote;
|
return monthNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getDateNote(dateTimeStr) {
|
async function getDateNote(dateStr) {
|
||||||
const rootNote = await getRootCalendarNote();
|
const rootNote = await getRootCalendarNote();
|
||||||
|
|
||||||
const dateStr = dateTimeStr.substr(0, 10);
|
const dayNumber = dateStr.substr(8, 2);
|
||||||
const dayNumber = dateTimeStr.substr(8, 2);
|
|
||||||
|
|
||||||
let dateNote = await attributeService.getNoteWithLabel(DATE_LABEL, dateStr);
|
let dateNote = await attributeService.getNoteWithLabel(DATE_LABEL, dateStr);
|
||||||
|
|
||||||
if (!dateNote) {
|
if (!dateNote) {
|
||||||
const monthNote = await getMonthNote(dateTimeStr, rootNote);
|
const monthNote = await getMonthNote(dateStr, rootNote);
|
||||||
|
|
||||||
dateNote = await getNoteStartingWith(monthNote.noteId, dayNumber);
|
dateNote = await getNoteStartingWith(monthNote.noteId, dayNumber);
|
||||||
|
|
||||||
if (!dateNote) {
|
if (!dateNote) {
|
||||||
const dateObj = dateUtils.parseDate(dateTimeStr);
|
const dateObj = dateUtils.parseLocalDate(dateStr);
|
||||||
|
|
||||||
const noteTitle = dayNumber + " - " + DAYS[dateObj.getDay()];
|
const noteTitle = dayNumber + " - " + DAYS[dateObj.getDay()];
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,16 @@ function nowDate() {
|
|||||||
return dateStr(new Date());
|
return dateStr(new Date());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nowLocalDate() {
|
||||||
|
const date = new Date();
|
||||||
|
|
||||||
|
return date.getFullYear() + "-" + pad(date.getMonth() + 1) + "-" + pad(date.getDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
function pad(num) {
|
||||||
|
return num <= 9 ? `0${num}` : `${num}`;
|
||||||
|
}
|
||||||
|
|
||||||
function dateStr(date) {
|
function dateStr(date) {
|
||||||
return date.toISOString();
|
return date.toISOString();
|
||||||
}
|
}
|
||||||
@@ -25,14 +35,23 @@ function parseDate(str) {
|
|||||||
return parseDateTime(datePart + "T12:00:00.000Z");
|
return parseDateTime(datePart + "T12:00:00.000Z");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseLocalDate(str) {
|
||||||
|
const datePart = str.substr(0, 10);
|
||||||
|
|
||||||
|
// not specifying the timezone and specifying the time means Date.parse() will use the local timezone
|
||||||
|
return parseDateTime(datePart + " 12:00:00.000");
|
||||||
|
}
|
||||||
|
|
||||||
function getDateTimeForFile() {
|
function getDateTimeForFile() {
|
||||||
return new Date().toISOString().substr(0, 19).replace(/:/g, '');
|
return new Date().toISOString().substr(0, 19).replace(/:/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
nowDate,
|
nowDate,
|
||||||
|
nowLocalDate,
|
||||||
dateStr,
|
dateStr,
|
||||||
parseDate,
|
parseDate,
|
||||||
parseDateTime,
|
parseDateTime,
|
||||||
|
parseLocalDate,
|
||||||
getDateTimeForFile
|
getDateTimeForFile
|
||||||
};
|
};
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const sanitize = require("sanitize-filename");
|
const repository = require("../repository");
|
||||||
const repository = require("../../services/repository");
|
const utils = require('../utils');
|
||||||
const utils = require('../../services/utils');
|
|
||||||
|
|
||||||
async function exportToOpml(branch, res) {
|
async function exportToOpml(branch, res) {
|
||||||
const note = await branch.getNote();
|
const note = await branch.getNote();
|
||||||
const title = (branch.prefix ? (branch.prefix + ' - ') : '') + note.title;
|
|
||||||
const sanitizedTitle = sanitize(title);
|
|
||||||
|
|
||||||
async function exportNoteInner(branchId) {
|
async function exportNoteInner(branchId) {
|
||||||
const branch = await repository.getBranch(branchId);
|
const branch = await repository.getBranch(branchId);
|
||||||
@@ -31,7 +28,9 @@ async function exportToOpml(branch, res) {
|
|||||||
res.write('</outline>');
|
res.write('</outline>');
|
||||||
}
|
}
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'file; filename="' + sanitizedTitle + '.opml"');
|
const filename = (branch.prefix ? (branch.prefix + ' - ') : '') + note.title + ".opml";
|
||||||
|
|
||||||
|
res.setHeader('Content-Disposition', utils.getContentDisposition(filename));
|
||||||
res.setHeader('Content-Type', 'text/x-opml');
|
res.setHeader('Content-Type', 'text/x-opml');
|
||||||
|
|
||||||
res.write(`<?xml version="1.0" encoding="UTF-8"?>
|
res.write(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const sanitize = require("sanitize-filename");
|
|
||||||
const TurndownService = require('turndown');
|
const TurndownService = require('turndown');
|
||||||
const mimeTypes = require('mime-types');
|
const mimeTypes = require('mime-types');
|
||||||
const html = require('html');
|
const html = require('html');
|
||||||
|
const utils = require('../utils');
|
||||||
|
|
||||||
async function exportSingleNote(branch, format, res) {
|
async function exportSingleNote(branch, format, res) {
|
||||||
const note = await branch.getNote();
|
const note = await branch.getNote();
|
||||||
@@ -42,11 +42,9 @@ async function exportSingleNote(branch, format, res) {
|
|||||||
mime = 'application/json';
|
mime = 'application/json';
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = sanitize(note.title);
|
const filename = note.title + "." + extension;
|
||||||
|
|
||||||
console.log(name, extension, mime);
|
res.setHeader('Content-Disposition', utils.getContentDisposition(filename));
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', `file; filename="${name}.${extension}"`);
|
|
||||||
res.setHeader('Content-Type', mime + '; charset=UTF-8');
|
res.setHeader('Content-Type', mime + '; charset=UTF-8');
|
||||||
|
|
||||||
res.send(payload);
|
res.send(payload);
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ const html = require('html');
|
|||||||
const repository = require('../repository');
|
const repository = require('../repository');
|
||||||
const tar = require('tar-stream');
|
const tar = require('tar-stream');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const sanitize = require("sanitize-filename");
|
|
||||||
const mimeTypes = require('mime-types');
|
const mimeTypes = require('mime-types');
|
||||||
const TurndownService = require('turndown');
|
const TurndownService = require('turndown');
|
||||||
const packageInfo = require('../../../package.json');
|
const packageInfo = require('../../../package.json');
|
||||||
|
const utils = require('../utils');
|
||||||
|
const sanitize = require("sanitize-filename");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param format - 'html' or 'markdown'
|
* @param format - 'html' or 'markdown'
|
||||||
@@ -219,9 +220,9 @@ async function exportToTar(branch, format, res) {
|
|||||||
pack.finalize();
|
pack.finalize();
|
||||||
|
|
||||||
const note = await branch.getNote();
|
const note = await branch.getNote();
|
||||||
const tarFileName = sanitize((branch.prefix ? (branch.prefix + " - ") : "") + note.title);
|
const tarFileName = (branch.prefix ? (branch.prefix + " - ") : "") + note.title + ".tar";
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', `file; filename="${tarFileName}.tar"`);
|
res.setHeader('Content-Disposition', utils.getContentDisposition(tarFileName));
|
||||||
res.setHeader('Content-Type', 'application/tar');
|
res.setHeader('Content-Type', 'application/tar');
|
||||||
|
|
||||||
pack.pipe(res);
|
pack.pipe(res);
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ eventService.subscribe(eventService.NOTE_TITLE_CHANGED, async note => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
eventService.subscribe(eventService.ENTITY_CHANGED, async ({ entityName, entity }) => {
|
eventService.subscribe([ eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED ], async ({ entityName, entity }) => {
|
||||||
if (entityName === 'attributes') {
|
if (entityName === 'attributes') {
|
||||||
await runAttachedRelations(await entity.getNote(), 'runOnAttributeChange', entity);
|
await runAttachedRelations(await entity.getNote(), 'runOnAttributeChange', entity);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const repository = require('./repository');
|
const repository = require('./repository');
|
||||||
|
const log = require('./log');
|
||||||
const protectedSessionService = require('./protected_session');
|
const protectedSessionService = require('./protected_session');
|
||||||
const noteService = require('./notes');
|
const noteService = require('./notes');
|
||||||
const imagemin = require('imagemin');
|
const imagemin = require('imagemin');
|
||||||
@@ -13,7 +14,13 @@ const sanitizeFilename = require('sanitize-filename');
|
|||||||
|
|
||||||
async function saveImage(buffer, originalName, parentNoteId) {
|
async function saveImage(buffer, originalName, parentNoteId) {
|
||||||
const resizedImage = await resize(buffer);
|
const resizedImage = await resize(buffer);
|
||||||
const optimizedImage = await optimize(resizedImage);
|
let optimizedImage;
|
||||||
|
try {
|
||||||
|
optimizedImage = await optimize(resizedImage);
|
||||||
|
} catch (e) {
|
||||||
|
log.error(e);
|
||||||
|
optimizedImage = resizedImage;
|
||||||
|
}
|
||||||
|
|
||||||
const imageFormat = imageType(optimizedImage);
|
const imageFormat = imageType(optimizedImage);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const sax = require("sax");
|
const sax = require("sax");
|
||||||
|
const fileType = require('file-type');
|
||||||
const stream = require('stream');
|
const stream = require('stream');
|
||||||
const xml2js = require('xml2js');
|
const xml2js = require('xml2js');
|
||||||
const log = require("../log");
|
const log = require("../log");
|
||||||
@@ -144,7 +145,7 @@ async function importEnex(file, parentNote) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (currentTag === 'mime') {
|
else if (currentTag === 'mime') {
|
||||||
resource.mime = text;
|
resource.mime = text.toLowerCase();
|
||||||
|
|
||||||
if (text.startsWith("image/")) {
|
if (text.startsWith("image/")) {
|
||||||
resource.title = "image";
|
resource.title = "image";
|
||||||
@@ -222,7 +223,26 @@ async function importEnex(file, parentNote) {
|
|||||||
|
|
||||||
const mediaRegex = new RegExp(`<en-media hash="${hash}"[^>]*>`, 'g');
|
const mediaRegex = new RegExp(`<en-media hash="${hash}"[^>]*>`, 'g');
|
||||||
|
|
||||||
if (resource.mime.startsWith("image/")) {
|
const fileTypeFromBuffer = fileType(resource.content);
|
||||||
|
if (fileTypeFromBuffer) {
|
||||||
|
// If fileType returns something for buffer, then set the mime given
|
||||||
|
resource.mime = fileTypeFromBuffer.mime;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createResourceNote = async () => {
|
||||||
|
const resourceNote = (await noteService.createNote(noteEntity.noteId, resource.title, resource.content, {
|
||||||
|
attributes: resource.attributes,
|
||||||
|
type: 'file',
|
||||||
|
mime: resource.mime
|
||||||
|
})).note;
|
||||||
|
|
||||||
|
const resourceLink = `<a href="#root/${resourceNote.noteId}">${utils.escapeHtml(resource.title)}</a>`;
|
||||||
|
|
||||||
|
noteEntity.content = noteEntity.content.replace(mediaRegex, resourceLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (["image/jpeg", "image/png", "image/gif"].includes(resource.mime)) {
|
||||||
|
try {
|
||||||
const originalName = "image." + resource.mime.substr(6);
|
const originalName = "image." + resource.mime.substr(6);
|
||||||
|
|
||||||
const { url } = await imageService.saveImage(resource.content, originalName, noteEntity.noteId);
|
const { url } = await imageService.saveImage(resource.content, originalName, noteEntity.noteId);
|
||||||
@@ -236,17 +256,13 @@ async function importEnex(file, parentNote) {
|
|||||||
// otherwise image would be removed since no note would include it
|
// otherwise image would be removed since no note would include it
|
||||||
note.content += imageLink;
|
note.content += imageLink;
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.error("error when saving image from ENEX file: " + e);
|
||||||
|
await createResourceNote();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const resourceNote = (await noteService.createNote(noteEntity.noteId, resource.title, resource.content, {
|
await createResourceNote();
|
||||||
attributes: resource.attributes,
|
|
||||||
type: 'file',
|
|
||||||
mime: resource.mime
|
|
||||||
})).note;
|
|
||||||
|
|
||||||
const resourceLink = `<a href="#root/${resourceNote.noteId}">${utils.escapeHtml(resource.title)}</a>`;
|
|
||||||
|
|
||||||
noteEntity.content = noteEntity.content.replace(mediaRegex, resourceLink);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,9 +33,21 @@ async function load() {
|
|||||||
|
|
||||||
archived = await sql.getMap(`SELECT noteId, isInheritable FROM attributes WHERE isDeleted = 0 AND type = 'label' AND name = 'archived'`);
|
archived = await sql.getMap(`SELECT noteId, isInheritable FROM attributes WHERE isDeleted = 0 AND type = 'label' AND name = 'archived'`);
|
||||||
|
|
||||||
|
if (protectedSessionService.isProtectedSessionAvailable()) {
|
||||||
|
await loadProtectedNotes();
|
||||||
|
}
|
||||||
|
|
||||||
loaded = true;
|
loaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadProtectedNotes() {
|
||||||
|
protectedNoteTitles = await sql.getMap(`SELECT noteId, title FROM notes WHERE isDeleted = 0 AND isProtected = 1`);
|
||||||
|
|
||||||
|
for (const noteId in protectedNoteTitles) {
|
||||||
|
protectedNoteTitles[noteId] = protectedSessionService.decryptNoteTitle(noteId, protectedNoteTitles[noteId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function highlightResults(results, allTokens) {
|
function highlightResults(results, allTokens) {
|
||||||
// we remove < signs because they can cause trouble in matching and overwriting existing highlighted chunks
|
// we remove < signs because they can cause trouble in matching and overwriting existing highlighted chunks
|
||||||
// which would make the resulting HTML string invalid.
|
// which would make the resulting HTML string invalid.
|
||||||
@@ -314,7 +326,16 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED
|
|||||||
delete childToParent[note.noteId];
|
delete childToParent[note.noteId];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
noteTitles[note.noteId] = note.title;
|
if (note.isProtected) {
|
||||||
|
// we can assume we have protected session since we managed to update
|
||||||
|
// removing from the maps is important when switching between protected & unprotected
|
||||||
|
protectedNoteTitles[note.noteId] = note.title;
|
||||||
|
delete noteTitles[note.noteId];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
noteTitles[note.noteId] = note.title;
|
||||||
|
delete protectedNoteTitles[note.noteId];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (entityName === 'branches') {
|
else if (entityName === 'branches') {
|
||||||
@@ -355,15 +376,9 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, async () => {
|
eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => {
|
||||||
if (!loaded) {
|
if (loaded) {
|
||||||
return;
|
loadProtectedNotes();
|
||||||
}
|
|
||||||
|
|
||||||
protectedNoteTitles = await sql.getMap(`SELECT noteId, title FROM notes WHERE isDeleted = 0 AND isProtected = 1`);
|
|
||||||
|
|
||||||
for (const noteId in protectedNoteTitles) {
|
|
||||||
protectedNoteTitles[noteId] = protectedSessionService.decryptNoteTitle(noteId, protectedNoteTitles[noteId]);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -372,5 +387,6 @@ sqlInit.dbReady.then(() => utils.stopWatch("Autocomplete load", load));
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
findNotes,
|
findNotes,
|
||||||
getNotePath,
|
getNotePath,
|
||||||
getNoteTitleForPath
|
getNoteTitleForPath,
|
||||||
|
load
|
||||||
};
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
const sql = require('./sql');
|
const sql = require('./sql');
|
||||||
|
const sqlInit = require('./sql_init');
|
||||||
const optionService = require('./options');
|
const optionService = require('./options');
|
||||||
const dateUtils = require('./date_utils');
|
const dateUtils = require('./date_utils');
|
||||||
const syncTableService = require('./sync_table');
|
const syncTableService = require('./sync_table');
|
||||||
@@ -358,18 +359,8 @@ async function deleteNote(branch) {
|
|||||||
const notDeletedBranches = await note.getBranches();
|
const notDeletedBranches = await note.getBranches();
|
||||||
|
|
||||||
if (notDeletedBranches.length === 0) {
|
if (notDeletedBranches.length === 0) {
|
||||||
// maybe a bit counter-intuitively, protected notes can be deleted also outside of protected session
|
note.isDeleted = true;
|
||||||
// this is because protected notes offer only confidentiality which makes some things simpler - e.g. deletion UI
|
await note.save();
|
||||||
// to allow this, we just set the isDeleted flag, otherwise saving would fail because of attempt to encrypt
|
|
||||||
// content with non-existent protected session key
|
|
||||||
// we don't reset content here, that's postponed and done later to give the user a chance to correct a mistake
|
|
||||||
await sql.execute("UPDATE notes SET isDeleted = 1 WHERE noteId = ?", [note.noteId]);
|
|
||||||
// need to manually trigger sync since it's not taken care of by note save
|
|
||||||
await syncTableService.addNoteSync(note.noteId);
|
|
||||||
|
|
||||||
for (const noteRevision of await note.getRevisions()) {
|
|
||||||
await noteRevision.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const childBranch of await note.getChildBranches()) {
|
for (const childBranch of await note.getChildBranches()) {
|
||||||
await deleteNote(childBranch);
|
await deleteNote(childBranch);
|
||||||
@@ -408,10 +399,12 @@ async function cleanupDeletedNotes() {
|
|||||||
await sql.execute("UPDATE note_revisions SET content = NULL WHERE note_revisions.content IS NOT NULL AND noteId IN (SELECT noteId FROM notes WHERE isDeleted = 1 AND notes.dateModified <= ?)", [dateUtils.dateStr(cutoffDate)]);
|
await sql.execute("UPDATE note_revisions SET content = NULL WHERE note_revisions.content IS NOT NULL AND noteId IN (SELECT noteId FROM notes WHERE isDeleted = 1 AND notes.dateModified <= ?)", [dateUtils.dateStr(cutoffDate)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// first cleanup kickoff 5 minutes after startup
|
sqlInit.dbReady.then(() => {
|
||||||
setTimeout(cls.wrap(cleanupDeletedNotes), 5 * 60 * 1000);
|
// first cleanup kickoff 5 minutes after startup
|
||||||
|
setTimeout(cls.wrap(cleanupDeletedNotes), 5 * 60 * 1000);
|
||||||
|
|
||||||
setInterval(cls.wrap(cleanupDeletedNotes), 4 * 3600 * 1000);
|
setInterval(cls.wrap(cleanupDeletedNotes), 4 * 3600 * 1000);
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createNewNote,
|
createNewNote,
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ async function initSyncedOptions(username, password) {
|
|||||||
|
|
||||||
// passwordEncryptionService expects these options to already exist
|
// passwordEncryptionService expects these options to already exist
|
||||||
await optionService.createOption('encryptedDataKey', '', true);
|
await optionService.createOption('encryptedDataKey', '', true);
|
||||||
await optionService.createOption('encryptedDataKeyIv', '', true);
|
|
||||||
|
|
||||||
await passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16), true);
|
await passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16), true);
|
||||||
}
|
}
|
||||||
@@ -50,6 +49,10 @@ async function initNotSyncedOptions(initialized, startNotePath = 'root', syncSer
|
|||||||
await optionService.createOption('syncServerTimeout', 5000, false);
|
await optionService.createOption('syncServerTimeout', 5000, false);
|
||||||
await optionService.createOption('syncProxy', syncProxy, false);
|
await optionService.createOption('syncProxy', syncProxy, false);
|
||||||
|
|
||||||
|
await optionService.createOption('mainFontSize', '100', false);
|
||||||
|
await optionService.createOption('treeFontSize', '100', false);
|
||||||
|
await optionService.createOption('detailFontSize', '110', false);
|
||||||
|
|
||||||
await optionService.createOption('initialized', initialized ? 'true' : 'false', false);
|
await optionService.createOption('initialized', initialized ? 'true' : 'false', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,13 +14,7 @@ async function verifyPassword(password) {
|
|||||||
async function setDataKey(password, plainTextDataKey) {
|
async function setDataKey(password, plainTextDataKey) {
|
||||||
const passwordDerivedKey = await myScryptService.getPasswordDerivedKey(password);
|
const passwordDerivedKey = await myScryptService.getPasswordDerivedKey(password);
|
||||||
|
|
||||||
const encryptedDataKeyIv = utils.randomString(16);
|
const newEncryptedDataKey = dataEncryptionService.encrypt(passwordDerivedKey, plainTextDataKey, 16);
|
||||||
|
|
||||||
await optionService.setOption('encryptedDataKeyIv', encryptedDataKeyIv);
|
|
||||||
|
|
||||||
const buffer = Buffer.from(plainTextDataKey);
|
|
||||||
|
|
||||||
const newEncryptedDataKey = dataEncryptionService.encrypt(passwordDerivedKey, encryptedDataKeyIv, buffer);
|
|
||||||
|
|
||||||
await optionService.setOption('encryptedDataKey', newEncryptedDataKey);
|
await optionService.setOption('encryptedDataKey', newEncryptedDataKey);
|
||||||
}
|
}
|
||||||
@@ -28,10 +22,9 @@ async function setDataKey(password, plainTextDataKey) {
|
|||||||
async function getDataKey(password) {
|
async function getDataKey(password) {
|
||||||
const passwordDerivedKey = await myScryptService.getPasswordDerivedKey(password);
|
const passwordDerivedKey = await myScryptService.getPasswordDerivedKey(password);
|
||||||
|
|
||||||
const encryptedDataKeyIv = await optionService.getOption('encryptedDataKeyIv');
|
|
||||||
const encryptedDataKey = await optionService.getOption('encryptedDataKey');
|
const encryptedDataKey = await optionService.getOption('encryptedDataKey');
|
||||||
|
|
||||||
const decryptedDataKey = dataEncryptionService.decrypt(passwordDerivedKey, encryptedDataKeyIv, encryptedDataKey);
|
const decryptedDataKey = dataEncryptionService.decrypt(passwordDerivedKey, encryptedDataKey, 16);
|
||||||
|
|
||||||
return decryptedDataKey;
|
return decryptedDataKey;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,9 +38,7 @@ function decryptNoteTitle(noteId, encryptedTitle) {
|
|||||||
const dataKey = getDataKey();
|
const dataKey = getDataKey();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const iv = dataEncryptionService.noteTitleIv(noteId);
|
return dataEncryptionService.decryptString(dataKey, encryptedTitle);
|
||||||
|
|
||||||
return dataEncryptionService.decryptString(dataKey, iv, encryptedTitle);
|
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
e.message = `Cannot decrypt note title for noteId=${noteId}: ` + e.message;
|
e.message = `Cannot decrypt note title for noteId=${noteId}: ` + e.message;
|
||||||
@@ -57,17 +55,15 @@ function decryptNote(note) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (note.title) {
|
if (note.title) {
|
||||||
note.title = dataEncryptionService.decryptString(dataKey, dataEncryptionService.noteTitleIv(note.noteId), note.title);
|
note.title = dataEncryptionService.decryptString(dataKey, note.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (note.content) {
|
if (note.content) {
|
||||||
const contentIv = dataEncryptionService.noteContentIv(note.noteId);
|
if (note.type === 'file' || note.type === 'image') {
|
||||||
|
note.content = dataEncryptionService.decrypt(dataKey, note.content);
|
||||||
if (note.type === 'file') {
|
|
||||||
note.content = dataEncryptionService.decrypt(dataKey, contentIv, note.content);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
note.content = dataEncryptionService.decryptString(dataKey, contentIv, note.content);
|
note.content = dataEncryptionService.decryptString(dataKey, note.content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,26 +87,26 @@ function decryptNoteRevision(hist) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hist.title) {
|
if (hist.title) {
|
||||||
hist.title = dataEncryptionService.decryptString(dataKey, dataEncryptionService.noteTitleIv(hist.noteRevisionId), hist.title);
|
hist.title = dataEncryptionService.decryptString(dataKey, hist.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hist.content) {
|
if (hist.content) {
|
||||||
hist.content = dataEncryptionService.decryptString(dataKey, dataEncryptionService.noteContentIv(hist.noteRevisionId), hist.content);
|
hist.content = dataEncryptionService.decryptString(dataKey, hist.content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function encryptNote(note) {
|
function encryptNote(note) {
|
||||||
const dataKey = getDataKey();
|
const dataKey = getDataKey();
|
||||||
|
|
||||||
note.title = dataEncryptionService.encrypt(dataKey, dataEncryptionService.noteTitleIv(note.noteId), note.title);
|
note.title = dataEncryptionService.encrypt(dataKey, note.title);
|
||||||
note.content = dataEncryptionService.encrypt(dataKey, dataEncryptionService.noteContentIv(note.noteId), note.content);
|
note.content = dataEncryptionService.encrypt(dataKey, note.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
function encryptNoteRevision(revision) {
|
function encryptNoteRevision(revision) {
|
||||||
const dataKey = getDataKey();
|
const dataKey = getDataKey();
|
||||||
|
|
||||||
revision.title = dataEncryptionService.encrypt(dataKey, dataEncryptionService.noteTitleIv(revision.noteRevisionId), revision.title);
|
revision.title = dataEncryptionService.encrypt(dataKey, revision.title);
|
||||||
revision.content = dataEncryptionService.encrypt(dataKey, dataEncryptionService.noteContentIv(revision.noteRevisionId), revision.content);
|
revision.content = dataEncryptionService.encrypt(dataKey, revision.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@@ -47,11 +47,6 @@ async function getBranch(branchId) {
|
|||||||
return await getEntity("SELECT * FROM branches WHERE branchId = ?", [branchId]);
|
return await getEntity("SELECT * FROM branches WHERE branchId = ?", [branchId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Image|null} */
|
|
||||||
async function getImage(imageId) {
|
|
||||||
return await getEntity("SELECT * FROM images WHERE imageId = ?", [imageId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @returns {Attribute|null} */
|
/** @returns {Attribute|null} */
|
||||||
async function getAttribute(attributeId) {
|
async function getAttribute(attributeId) {
|
||||||
return await getEntity("SELECT * FROM attributes WHERE attributeId = ?", [attributeId]);
|
return await getEntity("SELECT * FROM attributes WHERE attributeId = ?", [attributeId]);
|
||||||
@@ -122,7 +117,6 @@ module.exports = {
|
|||||||
getEntity,
|
getEntity,
|
||||||
getNote,
|
getNote,
|
||||||
getBranch,
|
getBranch,
|
||||||
getImage,
|
|
||||||
getAttribute,
|
getAttribute,
|
||||||
getOption,
|
getOption,
|
||||||
updateEntity,
|
updateEntity,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ const RESOURCE_DIR = path.resolve(__dirname, "../..");
|
|||||||
// where "trilium" executable is
|
// where "trilium" executable is
|
||||||
const ELECTRON_APP_ROOT_DIR = path.resolve(RESOURCE_DIR, "../..");
|
const ELECTRON_APP_ROOT_DIR = path.resolve(RESOURCE_DIR, "../..");
|
||||||
const DB_INIT_DIR = path.resolve(RESOURCE_DIR, "db");
|
const DB_INIT_DIR = path.resolve(RESOURCE_DIR, "db");
|
||||||
const APP_PNG_ICON_DIR = path.resolve(RESOURCE_DIR, "src/public/images/app-icons/png");
|
|
||||||
|
|
||||||
if (!fs.existsSync(DB_INIT_DIR)) {
|
if (!fs.existsSync(DB_INIT_DIR)) {
|
||||||
log.error("Could not find DB initialization directory: " + DB_INIT_DIR);
|
log.error("Could not find DB initialization directory: " + DB_INIT_DIR);
|
||||||
@@ -25,6 +24,5 @@ module.exports = {
|
|||||||
RESOURCE_DIR,
|
RESOURCE_DIR,
|
||||||
MIGRATIONS_DIR,
|
MIGRATIONS_DIR,
|
||||||
DB_INIT_DIR,
|
DB_INIT_DIR,
|
||||||
ELECTRON_APP_ROOT_DIR,
|
ELECTRON_APP_ROOT_DIR
|
||||||
APP_PNG_ICON_DIR
|
|
||||||
};
|
};
|
||||||
@@ -6,7 +6,7 @@ const sourceIdService = require('./source_id');
|
|||||||
const log = require('./log');
|
const log = require('./log');
|
||||||
|
|
||||||
async function executeNote(note, originEntity) {
|
async function executeNote(note, originEntity) {
|
||||||
if (!note.isJavaScript()) {
|
if (!note.isJavaScript() || note.getScriptEnv() !== 'backend' || !note.isContentAvailable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +79,24 @@ function getParams(params) {
|
|||||||
}).join(",");
|
}).join(",");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getScriptBundleForFrontend(note) {
|
||||||
|
const bundle = await getScriptBundle(note);
|
||||||
|
|
||||||
|
// for frontend we return just noteIds because frontend needs to use its own entity instances
|
||||||
|
bundle.noteId = bundle.note.noteId;
|
||||||
|
delete bundle.note;
|
||||||
|
|
||||||
|
bundle.allNoteIds = bundle.allNotes.map(note => note.noteId);
|
||||||
|
delete bundle.allNotes;
|
||||||
|
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
async function getScriptBundle(note, root = true, scriptEnv = null, includedNoteIds = []) {
|
async function getScriptBundle(note, root = true, scriptEnv = null, includedNoteIds = []) {
|
||||||
|
if (!note.isContentAvailable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!note.isJavaScript() && !note.isHtml()) {
|
if (!note.isJavaScript() && !note.isHtml()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -149,14 +166,8 @@ function sanitizeVariableName(str) {
|
|||||||
return str.replace(/[^a-z0-9_]/gim, "");
|
return str.replace(/[^a-z0-9_]/gim, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getScriptBundleForNoteId(noteId) {
|
|
||||||
const note = await repository.getNote(noteId);
|
|
||||||
return await getScriptBundle(note);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
executeNote,
|
executeNote,
|
||||||
executeScript,
|
executeScript,
|
||||||
getScriptBundle,
|
getScriptBundleForFrontend
|
||||||
getScriptBundleForNoteId
|
|
||||||
};
|
};
|
||||||
@@ -123,7 +123,7 @@ async function execute(query, params = []) {
|
|||||||
|
|
||||||
const milliseconds = Date.now() - startTimestamp;
|
const milliseconds = Date.now() - startTimestamp;
|
||||||
if (milliseconds >= 200) {
|
if (milliseconds >= 200) {
|
||||||
log.info(`Slow query took ${milliseconds}ms: ${query}, params=${params}`);
|
log.info(`Slow query took ${milliseconds}ms: ${query}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -155,6 +155,10 @@ async function dbInitialized() {
|
|||||||
await initDbConnection();
|
await initDbConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dbReady.then(async () => {
|
||||||
|
log.info("DB size: " + await sql.getValue("SELECT page_count * page_size / 1000 as size FROM pragma_page_count(), pragma_page_size()") + " KB");
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
dbReady,
|
dbReady,
|
||||||
schemaExists,
|
schemaExists,
|
||||||
|
|||||||
@@ -111,6 +111,12 @@ async function sortNotesAlphabetically(parentNoteId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function setNoteToParent(noteId, prefix, parentNoteId) {
|
async function setNoteToParent(noteId, prefix, parentNoteId) {
|
||||||
|
const parentNote = await repository.getNote(parentNoteId);
|
||||||
|
|
||||||
|
if (parentNote && parentNote.isDeleted) {
|
||||||
|
throw new Error(`Cannot move note to deleted parent note ${parentNoteId}`);
|
||||||
|
}
|
||||||
|
|
||||||
// case where there might be more such branches is ignored. It's expected there should be just one
|
// case where there might be more such branches is ignored. It's expected there should be just one
|
||||||
const branch = await repository.getEntity("SELECT * FROM branches WHERE isDeleted = 0 AND noteId = ? AND prefix = ?", [noteId, prefix]);
|
const branch = await repository.getEntity("SELECT * FROM branches WHERE isDeleted = 0 AND noteId = ? AND prefix = ?", [noteId, prefix]);
|
||||||
|
|
||||||
@@ -126,6 +132,12 @@ async function setNoteToParent(noteId, prefix, parentNoteId) {
|
|||||||
await branch.save();
|
await branch.save();
|
||||||
}
|
}
|
||||||
else if (parentNoteId) {
|
else if (parentNoteId) {
|
||||||
|
const note = await repository.getNote(noteId);
|
||||||
|
|
||||||
|
if (note.isDeleted) {
|
||||||
|
throw new Error(`Cannot create a branch for ${noteId} which is deleted.`);
|
||||||
|
}
|
||||||
|
|
||||||
await new Branch({
|
await new Branch({
|
||||||
noteId: noteId,
|
noteId: noteId,
|
||||||
parentNoteId: parentNoteId,
|
parentNoteId: parentNoteId,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const crypto = require('crypto');
|
|||||||
const randtoken = require('rand-token').generator({source: 'crypto'});
|
const randtoken = require('rand-token').generator({source: 'crypto'});
|
||||||
const unescape = require('unescape');
|
const unescape = require('unescape');
|
||||||
const escape = require('escape-html');
|
const escape = require('escape-html');
|
||||||
|
const sanitize = require("sanitize-filename");
|
||||||
|
|
||||||
function newEntityId() {
|
function newEntityId() {
|
||||||
return randomString(12);
|
return randomString(12);
|
||||||
@@ -127,6 +128,22 @@ function crash() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sanitizeFilenameForHeader(filename) {
|
||||||
|
let sanitizedFilename = sanitize(filename);
|
||||||
|
|
||||||
|
if (sanitizedFilename.trim().length === 0) {
|
||||||
|
sanitizedFilename = "file";
|
||||||
|
}
|
||||||
|
|
||||||
|
return encodeURIComponent(sanitizedFilename)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getContentDisposition(filename) {
|
||||||
|
const sanitizedFilename = sanitizeFilenameForHeader(filename);
|
||||||
|
|
||||||
|
return `file; filename="${sanitizedFilename}"; filename*=UTF-8''${sanitizedFilename}`;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
randomSecureToken,
|
randomSecureToken,
|
||||||
randomString,
|
randomString,
|
||||||
@@ -147,5 +164,7 @@ module.exports = {
|
|||||||
intersection,
|
intersection,
|
||||||
union,
|
union,
|
||||||
escapeRegExp,
|
escapeRegExp,
|
||||||
crash
|
crash,
|
||||||
|
sanitizeFilenameForHeader,
|
||||||
|
getContentDisposition
|
||||||
};
|
};
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class="theme-<%= theme %>">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Trilium Notes</title>
|
<title>Trilium Notes</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="desktop">
|
<body class="desktop theme-<%= theme %>" style="--main-font-size: <%= mainFontSize %>%; --tree-font-size: <%= treeFontSize %>%; --detail-font-size: <%= detailFontSize %>%;">
|
||||||
<div id="container" style="display: none; grid-template-columns: minmax(<%= leftPaneMinWidth %>px, <%= leftPaneWidthPercent %>fr) <%= rightPaneWidthPercent %>fr">
|
<div id="container" style="display: none; grid-template-columns: minmax(<%= leftPaneMinWidth %>px, <%= leftPaneWidthPercent %>fr) <%= rightPaneWidthPercent %>fr">
|
||||||
<div id="header" class="hide-toggle">
|
<div id="header" class="hide-toggle">
|
||||||
<div id="history-navigation" style="display: none;">
|
<div id="history-navigation" style="display: none;">
|
||||||
<a id="history-back-button" title="Go to previous note." class="icon-action jam jam-arrow-square-left"></a>
|
<a id="history-back-button" title="Go to previous note." class="icon-action jam jam-arrow-square-left"></a>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a id="history-forward-button" title="Go to next note." class="icon-action jam jam-arrow-square-right"></a>
|
<a id="history-forward-button" title="Go to next note." class="icon-action jam jam-arrow-square-right"></a>
|
||||||
</div>
|
</div>
|
||||||
@@ -75,8 +75,8 @@
|
|||||||
<input type="file" id="import-upload" style="display: none" />
|
<input type="file" id="import-upload" style="display: none" />
|
||||||
|
|
||||||
<div id="search-box">
|
<div id="search-box">
|
||||||
<div style="display: flex; align-items: center;">
|
<div style="display: flex; align-items: center; flex-wrap: wrap;">
|
||||||
<input name="search-text" placeholder="Search text, labels" style="flex-grow: 100; margin-left: 5px; margin-right: 5px;" autocomplete="off">
|
<input name="search-text" placeholder="Search text, labels" style="flex-grow: 100; margin-left: 5px; margin-right: 5px; flex-basis: 5em; min-width: 0;" autocomplete="off">
|
||||||
<button id="do-search-button" class="btn btn-sm icon-button jam jam-search" title="Search (enter)"></button>
|
<button id="do-search-button" class="btn btn-sm icon-button jam jam-search" title="Search (enter)"></button>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
<div id="note-detail-search" class="note-detail-component">
|
<div id="note-detail-search" class="note-detail-component">
|
||||||
<div style="display: flex; align-items: center;">
|
<div style="display: flex; align-items: center;">
|
||||||
<strong>Search string: </strong>
|
<strong>Search string: </strong>
|
||||||
<textarea rows="4" cols="50" id="search-string"></textarea>
|
<textarea rows="4" cols="40" id="search-string"></textarea>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-primary" id="note-detail-search-refresh-results-button">Refresh tree</button>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<div id="confirm-dialog-custom"></div>
|
<div id="confirm-dialog-custom"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn btn-default btn-sm" id="confirm-dialog-cancel-button">Cancel</button>
|
<button class="btn btn-secondary btn-sm" id="confirm-dialog-cancel-button">Cancel</button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,46 @@
|
|||||||
|
|
||||||
<p>Zooming can be controlled with CTRL-+ and CTRL-= shortcuts as well.</p>
|
<p>Zooming can be controlled with CTRL-+ and CTRL-= shortcuts as well.</p>
|
||||||
|
|
||||||
|
<h3>Font sizes</h3>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-4">
|
||||||
|
<label for="main-font-size">Main font size</label>
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="number" class="form-control" id="main-font-size" min="50" max="200" step="10"/>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<span class="input-group-text">%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-4">
|
||||||
|
<label for="tree-font-size">Note tree font size</label>
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="number" class="form-control" id="tree-font-size" min="50" max="200" step="10"/>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<span class="input-group-text">%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-4">
|
||||||
|
<label for="detail-font-size">Note detail font size</label>
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="number" class="form-control" id="detail-font-size" min="50" max="200" step="10"/>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<span class="input-group-text">%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Note that tree and detail font sizing is relative to the main font size setting.</p>
|
||||||
|
|
||||||
<h3>Left pane sizing</h3>
|
<h3>Left pane sizing</h3>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -108,7 +148,7 @@
|
|||||||
<div style="display: flex; justify-content: space-between;">
|
<div style="display: flex; justify-content: space-between;">
|
||||||
<button class="btn btn-primary">Save</button>
|
<button class="btn btn-primary">Save</button>
|
||||||
|
|
||||||
<button class="btn btn-default" type="button" data-help-page="Protected-notes">Help</button>
|
<button class="btn btn-secondary" type="button" data-help-page="Protected-notes">Help</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -125,7 +165,7 @@
|
|||||||
<div style="display: flex; justify-content: space-between;">
|
<div style="display: flex; justify-content: space-between;">
|
||||||
<button class="btn btn-primary">Save</button>
|
<button class="btn btn-primary">Save</button>
|
||||||
|
|
||||||
<button class="btn btn-default" type="button" data-help-page="Note-revisions">Help</button>
|
<button class="btn btn-secondary" type="button" data-help-page="Note-revisions">Help</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -154,7 +194,7 @@
|
|||||||
<div style="display: flex; justify-content: space-between;">
|
<div style="display: flex; justify-content: space-between;">
|
||||||
<button class="btn btn-primary">Save</button>
|
<button class="btn btn-primary">Save</button>
|
||||||
|
|
||||||
<button class="btn btn-default" type="button" data-help-page="Synchronization">Help</button>
|
<button class="btn btn-secondary" type="button" data-help-page="Synchronization">Help</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@@ -164,24 +204,24 @@
|
|||||||
|
|
||||||
<p>This will test connection and handshake to the sync server. If sync server isn't initialized, this will set it up to sync with local document.</p>
|
<p>This will test connection and handshake to the sync server. If sync server isn't initialized, this will set it up to sync with local document.</p>
|
||||||
|
|
||||||
<button id="test-sync-button" class="btn btn-default">Test sync</button>
|
<button id="test-sync-button" class="btn btn-secondary">Test sync</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="advanced" class="tab-pane">
|
<div id="advanced" class="tab-pane">
|
||||||
<h4 style="margin-top: 0px;">Sync</h4>
|
<h4 style="margin-top: 0px;">Sync</h4>
|
||||||
<button id="force-full-sync-button" class="btn btn-default">Force full sync</button>
|
<button id="force-full-sync-button" class="btn btn-secondary">Force full sync</button>
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<button id="fill-sync-rows-button" class="btn btn-default">Fill sync rows</button>
|
<button id="fill-sync-rows-button" class="btn btn-secondary">Fill sync rows</button>
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<h4>Debugging</h4>
|
<h4>Debugging</h4>
|
||||||
|
|
||||||
<button id="anonymize-button" class="btn btn-default">Save anonymized database</button><br/><br/>
|
<button id="anonymize-button" class="btn btn-secondary">Save anonymized database</button><br/><br/>
|
||||||
|
|
||||||
<p>This action will create a new copy of the database and anonymise it (remove all note content and leave only structure and metadata)
|
<p>This action will create a new copy of the database and anonymise it (remove all note content and leave only structure and metadata)
|
||||||
for sharing online for debugging purposes without fear of leaking your personal data.</p>
|
for sharing online for debugging purposes without fear of leaking your personal data.</p>
|
||||||
@@ -190,7 +230,7 @@
|
|||||||
|
|
||||||
<p>This will rebuild database which will typically result in smaller database file. No data will be actually changed.</p>
|
<p>This will rebuild database which will typically result in smaller database file. No data will be actually changed.</p>
|
||||||
|
|
||||||
<button id="vacuum-database-button" class="btn btn-default">Vacuum database</button>
|
<button id="vacuum-database-button" class="btn btn-secondary">Vacuum database</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="about" class="tab-pane">
|
<div id="about" class="tab-pane">
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="col-md-5 offset-md-3" style="padding-top: 25px;">
|
<div class="col-xs-12 col-sm-10 col-md-6 col-lg-4 col-xl-4 mx-auto" style="padding-top: 25px;">
|
||||||
<h1>Trilium login</h1>
|
<h1>Trilium login</h1>
|
||||||
|
|
||||||
<% if (failedAuth) { %>
|
<% if (failedAuth) { %>
|
||||||
@@ -60,6 +60,8 @@
|
|||||||
device = /Mobi/.test(navigator.userAgent) ? "mobile" : "desktop";
|
device = /Mobi/.test(navigator.userAgent) ? "mobile" : "desktop";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Setting device cookie to:", device);
|
||||||
|
|
||||||
setCookie("trilium-device", device);
|
setCookie("trilium-device", device);
|
||||||
|
|
||||||
function setCookie(name, value) {
|
function setCookie(name, value) {
|
||||||
|
|||||||
@@ -2,113 +2,116 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
<title>Setup</title>
|
<title>Setup</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="setup-dialog" style="width: 700px; margin: auto; padding-top: 50px; display:none; font-size: larger;">
|
<div class="container">
|
||||||
<h1>Trilium Notes setup</h1>
|
<div id="setup-dialog" class="col-md-12 col-lg-8 col-xl-6 mx-auto" style="padding-top: 25px; font-size: larger; display: none;">
|
||||||
|
<h1>Trilium Notes setup</h1>
|
||||||
|
|
||||||
<div class="alert alert-warning" id="alert" style="display: none;">
|
<div class="alert alert-warning" id="alert" style="display: none;">
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="setup-type" data-bind="visible: step() == 'setup-type'" style="margin-top: 20px;">
|
|
||||||
<div class="radio" style="margin-bottom: 15px;">
|
|
||||||
<label><input type="radio" name="setup-type" value="new-document" data-bind="checked: setupType">
|
|
||||||
I'm a new user and I want to create new Trilium document for my notes</label>
|
|
||||||
</div>
|
|
||||||
<div class="radio" data-bind="if: instanceType == 'server'" style="margin-bottom: 15px;">
|
|
||||||
<label><input type="radio" name="setup-type" value="sync-from-desktop" data-bind="checked: setupType">
|
|
||||||
I have desktop instance already and I want to setup sync with it</label>
|
|
||||||
</div>
|
|
||||||
<div class="radio" data-bind="if: instanceType == 'desktop'" style="margin-bottom: 15px;">
|
|
||||||
<label><input type="radio" name="setup-type" value="sync-from-server" data-bind="checked: setupType">
|
|
||||||
I have server instance up and I want to setup sync with it</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" data-bind="disable: !setupTypeSelected(), click: selectSetupType" class="btn btn-primary">Next</button>
|
<div id="setup-type" data-bind="visible: step() == 'setup-type'" style="margin-top: 20px;">
|
||||||
</div>
|
<div class="radio" style="margin-bottom: 15px;">
|
||||||
|
<label><input type="radio" name="setup-type" value="new-document" data-bind="checked: setupType">
|
||||||
|
I'm a new user and I want to create new Trilium document for my notes</label>
|
||||||
|
</div>
|
||||||
|
<div class="radio" data-bind="if: instanceType == 'server'" style="margin-bottom: 15px;">
|
||||||
|
<label><input type="radio" name="setup-type" value="sync-from-desktop" data-bind="checked: setupType">
|
||||||
|
I have desktop instance already and I want to setup sync with it</label>
|
||||||
|
</div>
|
||||||
|
<div class="radio" data-bind="if: instanceType == 'desktop'" style="margin-bottom: 15px;">
|
||||||
|
<label><input type="radio" name="setup-type" value="sync-from-server" data-bind="checked: setupType">
|
||||||
|
I have server instance up and I want to setup sync with it</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div data-bind="visible: step() == 'new-document'">
|
<button type="button" data-bind="disable: !setupTypeSelected(), click: selectSetupType" class="btn btn-primary">Next</button>
|
||||||
<h2>New document</h2>
|
|
||||||
|
|
||||||
<p>You're almost done with the setup. The last thing is to choose username and password using which you'll login to the application.
|
|
||||||
This password is also used for generating encryption key which encrypts protected notes.</p>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="username">Username</label>
|
|
||||||
<input type="text" class="form-control" data-bind="value: username" placeholder="Choose alphanumeric username">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="password1">Password</label>
|
|
||||||
<input type="password" class="form-control" data-bind="value: password1" placeholder="Password">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="password2">Repeat password</label>
|
|
||||||
<input type="password" class="form-control" data-bind="value: password2" placeholder="Password">
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" data-bind="click: back" class="btn btn-default">Back</button>
|
<div data-bind="visible: step() == 'new-document'">
|
||||||
|
<h2>New document</h2>
|
||||||
|
|
||||||
|
<p>You're almost done with the setup. The last thing is to choose username and password using which you'll login to the application.
|
||||||
|
This password is also used for generating encryption key which encrypts protected notes.</p>
|
||||||
|
|
||||||
<button type="button" data-bind="click: finish" class="btn btn-primary">Finish setup</button>
|
<div class="form-group">
|
||||||
</div>
|
<label for="username">Username</label>
|
||||||
|
<input type="text" class="form-control" data-bind="value: username" placeholder="Choose alphanumeric username">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password1">Password</label>
|
||||||
|
<input type="password" class="form-control" data-bind="value: password1" placeholder="Password">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password2">Repeat password</label>
|
||||||
|
<input type="password" class="form-control" data-bind="value: password2" placeholder="Password">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div data-bind="visible: step() == 'sync-from-desktop'">
|
<button type="button" data-bind="click: back" class="btn btn-secondary">Back</button>
|
||||||
<h2>Sync from Desktop</h2>
|
|
||||||
|
|
||||||
<p>This setup needs to be initiated from the desktop instance:</p>
|
|
||||||
|
|
||||||
<ol>
|
<button type="button" data-bind="click: finish" class="btn btn-primary">Finish setup</button>
|
||||||
<li>please open your desktop instance of Trilium Notes</li>
|
|
||||||
<li>click on Options button in the top right</li>
|
|
||||||
<li>click on Sync tab</li>
|
|
||||||
<li>configure server instance address to the: <span id="current-host"></span> and click save.</li>
|
|
||||||
<li>click on "Test sync" button</li>
|
|
||||||
<li>once you've done all this, click <a href="/">here</a></li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<button type="button" data-bind="click: back" class="btn btn-default">Back</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div data-bind="visible: step() == 'sync-from-server'">
|
|
||||||
<h2>Sync from Server</h2>
|
|
||||||
|
|
||||||
<p>Please enter Trilium server address and credentials below. This will download the whole Trilium document from server and setup sync to it. Depending on the document size and your connection speed, this may take a while.</p>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="sync-server-host">Trilium server address</label>
|
|
||||||
<input type="text" id="syncServerHost" class="form-control" data-bind="value: syncServerHost" placeholder="https://<hostname>:<port>">
|
|
||||||
</div>
|
|
||||||
<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>
|
|
||||||
<input type="text" id="username" class="form-control" data-bind="value: username" placeholder="Username">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="password1">Password</label>
|
|
||||||
<input type="password" id="password1" class="form-control" data-bind="value: password1" placeholder="Password">
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" data-bind="click: back" class="btn btn-default">Back</button>
|
<div data-bind="visible: step() == 'sync-from-desktop'">
|
||||||
|
<h2>Sync from Desktop</h2>
|
||||||
|
|
||||||
|
<p>This setup needs to be initiated from the desktop instance:</p>
|
||||||
|
|
||||||
<button type="button" data-bind="click: finish" class="btn btn-primary">Finish setup</button>
|
<ol>
|
||||||
</div>
|
<li>please open your desktop instance of Trilium Notes</li>
|
||||||
|
<li>click on Options button in the top right</li>
|
||||||
|
<li>click on Sync tab</li>
|
||||||
|
<li>configure server instance address to the: <span id="current-host"></span> and click save.</li>
|
||||||
|
<li>click on "Test sync" button</li>
|
||||||
|
<li>once you've done all this, click <a href="/">here</a></li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
<div data-bind="visible: step() == 'sync-in-progress'">
|
<button type="button" data-bind="click: back" class="btn btn-secondary">Back</button>
|
||||||
<h2>Sync in progress</h2>
|
</div>
|
||||||
|
|
||||||
<div class="alert alert-success">Sync has been correctly set up. It will take some time for the initial sync to finish. Once it's done, you'll be redirected to the login page.</div>
|
<div data-bind="visible: step() == 'sync-from-server'">
|
||||||
|
<h2>Sync from Server</h2>
|
||||||
|
|
||||||
<div data-bind="if: instanceType == 'desktop'">
|
<p>Please enter Trilium server address and credentials below. This will download the whole Trilium document from server and setup sync to it. Depending on the document size and your connection speed, this may take a while.</p>
|
||||||
Outstanding sync items: <strong id="outstanding-syncs">N/A</strong>
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sync-server-host">Trilium server address</label>
|
||||||
|
<input type="text" id="syncServerHost" class="form-control" data-bind="value: syncServerHost" placeholder="https://<hostname>:<port>">
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
<input type="text" id="username" class="form-control" data-bind="value: username" placeholder="Username">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password1">Password</label>
|
||||||
|
<input type="password" id="password1" class="form-control" data-bind="value: password1" placeholder="Password">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" data-bind="click: back" class="btn btn-secondary">Back</button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<button type="button" data-bind="click: finish" class="btn btn-primary">Finish setup</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-bind="visible: step() == 'sync-in-progress'">
|
||||||
|
<h2>Sync in progress</h2>
|
||||||
|
|
||||||
|
<div class="alert alert-success">Sync has been correctly set up. It will take some time for the initial sync to finish. Once it's done, you'll be redirected to the login page.</div>
|
||||||
|
|
||||||
|
<div data-bind="if: instanceType == 'desktop'">
|
||||||
|
Outstanding sync items: <strong id="outstanding-syncs">N/A</strong>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -125,6 +128,7 @@
|
|||||||
<script>if (typeof module === 'object') {window.module = module; module = undefined;}</script>
|
<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>
|
||||||
|
<script src="libraries/jquery.hotkeys.js"></script>
|
||||||
|
|
||||||
<link href="libraries/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
<link href="libraries/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<script src="libraries/bootstrap/js/bootstrap.bundle.min.js"></script>
|
<script src="libraries/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user