Compare commits

..

24 Commits

Author SHA1 Message Date
azivner
bd4db406de release 0.28.0-beta 2019-01-14 23:51:55 +01:00
azivner
e50f9cd0a3 fix device detection in setup 2019-01-14 23:50:45 +01:00
azivner
2b64cbce2c added dark theme (to have same set of themes as before) 2019-01-13 23:25:30 +01:00
azivner
2797c942ab changes to options dialog to allow configuring font size, closes #326 2019-01-13 22:03:06 +01:00
azivner
f1c3278874 store font size in options #326 2019-01-13 21:27:32 +01:00
azivner
424c22dcde split UI to 3 different font sizes #326 2019-01-13 21:16:51 +01:00
azivner
5c223dfd12 all fonts are relative so it's easier to scale them properly 2019-01-13 21:04:08 +01:00
azivner
6a3e7a5a8e generate css classes for each mime type, #328 2019-01-13 20:14:33 +01:00
azivner
f88cdac000 fixes for black theme for relation map and code notes 2019-01-13 20:03:28 +01:00
azivner
eeead90f32 improved theme support using CSS variables, #328 2019-01-13 18:57:46 +01:00
azivner
b607857409 fix weight tracker demo to use relations instead of hardcoded noteId, fixes #329 2019-01-13 12:21:17 +01:00
azivner
9268f88bc3 frontend scripts now have startNote, currentNote and targetNote as NoteShort entities which e.g. provides easy access to relations/labels 2019-01-13 12:16:05 +01:00
azivner
f7f0560a9f simplification of script bundles on backend 2019-01-13 11:56:50 +01:00
azivner
3d8905207e fixed export with non-ASCII characters in note title, fixes #285, #331 2019-01-13 10:22:17 +01:00
azivner
dbc312010b update fancytree to 2.30.2 2019-01-13 09:24:00 +01:00
azivner
b115a7cf19 delete note through its entity instead of manually with SQL, closes #303 2019-01-13 00:24:51 +01:00
azivner
348562352c fixes in ASAR build and zipping 2019-01-12 19:48:45 +01:00
azivner
70fd917e7c build desktop versions into ASAR, closes #271 2019-01-12 00:05:13 +01:00
zadam
d2b60764cd Merge pull request #289 from perissology/electron-builder
use electron-builder for dev install
2019-01-11 23:54:11 +01:00
azivner
581b1fdaa5 trigger runOnAttributeChange, runOnNoteChange on entity deletions as well 2019-01-11 23:43:22 +01:00
azivner
3c19a712c0 active protected state button has darker background and is disabled 2019-01-11 23:29:56 +01:00
azivner
cc27f16088 store iv directly in the respective columns 2019-01-11 23:04:51 +01:00
azivner
dffdb82288 after creating new note, it is focused and now also selected so renaming is super easy, #318 2019-01-10 22:46:08 +01:00
perissology
62b44e3549 use electron-builder for npm install
This will build deps as required for electron as well as the os.

This fixes the missing binding error for sqlite3 when trying to run the
electron app after a fresh npm i
2019-01-08 06:50:48 -08:00
50 changed files with 11431 additions and 9486 deletions

View File

@@ -3,22 +3,18 @@
BUILD_DIR=./dist/trilium-linux-x64
rm -rf $BUILD_DIR
# we build x64 as second so that we keep X64 binaries in node_modules for local development and server build
echo "Rebuilding binaries for linux-x64"
./node_modules/.bin/electron-rebuild --arch=x64
rm -r node_modules/sqlite3/lib/binding/*
./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
rm -r "$BUILD_DIR/resources/app/node_modules/sqlite3/lib/binding/*"
cp -r bin/deps/linux-x64/sqlite/electron* "$BUILD_DIR/resources/app/node_modules/sqlite3/lib/binding/"
rm -r $BUILD_DIR/resources/app/bin/deps
# removing software WebGL binaries because they are pretty huge and not necessary
rm -r $BUILD_DIR/swiftshader
echo "Packaging linux x64 electron distribution..."
VERSION=`jq -r ".version" package.json`
7z a $BUILD_DIR-${VERSION}.7z $BUILD_DIR
tar cJf $BUILD_DIR-${VERSION}.tar.xz $BUILD_DIR

View File

@@ -3,25 +3,30 @@
BUILD_DIR=./dist/trilium-mac-x64
rm -rf $BUILD_DIR
./node_modules/.bin/electron-packager . --out=dist --executable-name=trilium --platform=darwin --arch=x64 --overwrite --icon=src/public/images/app-icons/mac/icon.icns
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
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
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..."
echo "Zipping mac x64 electron distribution..."
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

View File

@@ -34,4 +34,5 @@ chmod 755 trilium.sh
cd ..
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.gz trilium-linux-x64-server

View File

@@ -3,23 +3,30 @@
BUILD_DIR=./dist/trilium-windows-x64
rm -rf $BUILD_DIR
./node_modules/.bin/electron-packager . --out=dist --executable-name=trilium --platform=win32 --arch=x64 --overwrite --icon=src/public/images/app-icons/win/icon.ico
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
echo "Copying required windows binaries"
WIN_RES_DIR=$BUILD_DIR/resources/app
cp -r bin/deps/win-x64/sqlite/* $WIN_RES_DIR/node_modules/sqlite3/lib/binding/
cp bin/deps/win-x64/image/cjpeg.exe $WIN_RES_DIR/node_modules/mozjpeg/vendor/
cp bin/deps/win-x64/image/pngquant.exe $WIN_RES_DIR/node_modules/pngquant-bin/vendor/
cp bin/deps/win-x64/image/gifsicle.exe $WIN_RES_DIR/node_modules/giflossy/vendor/
rm -r $WIN_RES_DIR/bin/deps
# removing software WebGL binaries because they are pretty huge and not necessary
rm -r $BUILD_DIR/swiftshader
echo "Packaging windows x64 electron distribution..."
./reset-local.sh
echo "Zipping windows x64 electron distribution..."
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

View File

@@ -42,10 +42,10 @@ git push origin $TAG
bin/build.sh
LINUX_X64_BUILD=trilium-linux-x64-$VERSION.7z
WINDOWS_X64_BUILD=trilium-windows-x64-$VERSION.7z
MAC_X64_BUILD=trilium-mac-x64-$VERSION.7z
SERVER_BUILD=trilium-linux-x64-server-$VERSION.7z
LINUX_X64_BUILD=trilium-linux-x64-$VERSION.tar.gz
WINDOWS_X64_BUILD=trilium-windows-x64-$VERSION.zip
MAC_X64_BUILD=trilium-mac-x64-$VERSION.zip
SERVER_BUILD=trilium-linux-x64-server-$VERSION.tar.gz
echo "Creating release in GitHub"

3
bin/reset-local.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
./node_modules/.bin/electron-rebuild --arch=x64

Binary file not shown.

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

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

15
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "trilium",
"version": "0.27.2-beta",
"version": "0.27.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -3870,9 +3870,9 @@
}
},
"file-type": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz",
"integrity": "sha1-G2AOX8ofvcboDApwxxyNul95BsU="
"version": "10.7.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-10.7.0.tgz",
"integrity": "sha512-AbaGtdWYYRaVrv2MwL/65myuRJ9j3e79e7etJ79US18QHuVlzJBcQHUH+HxDUoLtbyWRTUfLzLkGXX3pP9kfZg=="
},
"filename-regex": {
"version": "2.0.1",
@@ -5000,6 +5000,13 @@
"integrity": "sha1-FQKvMTX5BuEiyHfDHpSve3qRRsU=",
"requires": {
"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": {

View File

@@ -2,7 +2,7 @@
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.27.4",
"version": "0.28.0-beta",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -17,7 +17,8 @@
"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-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": {
"async-mutex": "0.1.3",
@@ -68,6 +69,7 @@
"devDependencies": {
"devtron": "1.4.0",
"electron": "4.0.1",
"electron-builder": "20.38.4",
"electron-compile": "6.4.3",
"electron-packager": "13.0.1",
"electron-rebuild": "1.8.2",

View File

@@ -47,7 +47,18 @@ class Note extends Entity {
if (this.isProtected && this.noteId) {
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);
@@ -629,12 +640,21 @@ class Note extends Entity {
// cannot be static!
updatePojo(pojo) {
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.isContentAvailable;
delete pojo.__attributeCache;
delete pojo.titleCipherText;
delete pojo.contentCipherText;
}
}

View File

@@ -44,7 +44,10 @@ addTabHandler((function() {
const $zoomFactorSelect = $("#zoom-factor-select");
const $leftPaneMinWidth = $("#left-pane-min-width");
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");
function optionsLoaded(options) {
@@ -59,21 +62,27 @@ addTabHandler((function() {
$leftPaneMinWidth.val(options.leftPaneMinWidth);
$leftPaneWidthPercent.val(options.leftPaneWidthPercent);
$mainFontSize.val(options.mainFontSize);
$treeFontSize.val(options.treeFontSize);
$detailFontSize.val(options.detailFontSize);
}
$themeSelect.change(function() {
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);
});
$zoomFactorSelect.change(function() {
const newZoomFactor = $(this).val();
zoomService.setZoomFactorAndSave(newZoomFactor);
});
$zoomFactorSelect.change(function() { zoomService.setZoomFactorAndSave($(this).val()); });
function resizeLeftPanel() {
const leftPanePercent = parseInt($leftPaneWidthPercent.val());
@@ -83,20 +92,42 @@ addTabHandler((function() {
$container.css("grid-template-columns", `minmax(${leftPaneMinWidth}px, ${leftPanePercent}fr) ${rightPanePercent}fr`);
}
$leftPaneMinWidth.change(function() {
const newMinWidth = $(this).val();
$leftPaneMinWidth.change(async function() {
await server.put('options/leftPaneMinWidth/' + $(this).val());
resizeLeftPanel();
server.put('options/leftPaneMinWidth/' + newMinWidth);
});
$leftPaneWidthPercent.change(function() {
const newWidthPercent = $(this).val();
$leftPaneWidthPercent.change(async function() {
await server.put('options/leftPaneWidthPercent/' + $(this).val());
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 {

View File

@@ -9,7 +9,7 @@ async function getAndExecuteBundle(noteId, originEntity = null) {
}
async function executeBundle(bundle, originEntity) {
const apiContext = ScriptContext(bundle.note, bundle.allNotes, originEntity);
const apiContext = await ScriptContext(bundle.noteId, bundle.allNoteIds, originEntity);
try {
return await (function () {

View File

@@ -87,7 +87,7 @@ function registerEntrypoints() {
utils.bindShortcut('ctrl+r', utils.reloadApp);
$(document).bind('keydown', 'ctrl+shift+i', () => {
utils.bindShortcut('ctrl+shift+i', () => {
if (utils.isElectron()) {
require('electron').remote.getCurrentWindow().toggleDevTools();
@@ -135,8 +135,8 @@ function registerEntrypoints() {
});
if (utils.isElectron()) {
$(document).bind('keydown', 'ctrl+-', zoomService.decreaseZoomFactor);
$(document).bind('keydown', 'ctrl+=', zoomService.increaseZoomFactor);
utils.bindShortcut('ctrl+-', zoomService.decreaseZoomFactor);
utils.bindShortcut('ctrl+=', zoomService.increaseZoomFactor);
}
$("#note-title").bind('keydown', 'return', () => $("#note-detail-text").focus());

View File

@@ -43,7 +43,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null) {
this.activateNewNote = async notePath => {
await treeService.reload();
await treeService.activateNote(notePath, noteDetailService.focusOnTitle);
await treeService.activateNote(notePath, noteDetailService.focusAndSelectTitle);
};
/**

View File

@@ -145,8 +145,9 @@ async function saveNoteIfChanged() {
function setNoteBackgroundIfProtected(note) {
$noteDetailWrapper.toggleClass("protected", note.isProtected);
$protectButton.toggleClass("active", note.isProtected);
$protectButton.prop("disabled", note.isProtected);
$unprotectButton.toggleClass("active", !note.isProtected);
$unprotectButton.prop("disabled", !protectedSessionHolder.isProtectedSessionAvailable());
$unprotectButton.prop("disabled", !note.isProtected || !protectedSessionHolder.isProtectedSessionAvailable());
}
async function handleProtectedSession() {
@@ -283,6 +284,10 @@ function focusOnTitle() {
$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,
* we intentionally decouple activation of the note in the tree and full load of the note so just avaiting on
@@ -342,6 +347,7 @@ export default {
getCurrentNoteType,
getCurrentNoteId,
focusOnTitle,
focusAndSelectTitle,
saveNote,
saveNoteIfChanged,
noteChanged,

View File

@@ -1,9 +1,13 @@
import FrontendScriptApi from './frontend_script_api.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 startNote = await treeCache.getNote(startNoteId);
const allNotes = await treeCache.getNotes(allNoteIds);
return {
modules: modules,
notes: utils.toObject(allNotes, note => [note.noteId, note]),

View File

@@ -588,7 +588,7 @@ async function createNote(node, parentNoteId, target, isProtected, saveSelection
await noteDetailService.saveNoteIfChanged();
noteDetailService.addDetailLoadedListener(note.noteId, noteDetailService.focusOnTitle);
noteDetailService.addDetailLoadedListener(note.noteId, noteDetailService.focusAndSelectTitle);
const noteEntity = new NoteShort(treeCache, note);
const branchEntity = new Branch(treeCache, branch);

View File

@@ -168,9 +168,24 @@ async function getExtraClasses(note) {
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(" ");
}
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 {
prepareTree,
prepareBranch,

View File

@@ -151,11 +151,15 @@ function bindShortcut(keyboardShortcut, handler) {
}
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() {
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) {

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

View File

@@ -9,12 +9,12 @@
*
* 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
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.30.0
* @date 2018-09-02T15:42:49Z
* @version 2.30.2
* @date 2019-01-13T08:17:01Z
******************************************************************************/
/*------------------------------------------------------------------------------
* Helpers
@@ -336,7 +336,8 @@ span.fancytree-icon {
.fancytree-loading span.fancytree-expander,
.fancytree-loading span.fancytree-expander:hover,
.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-position: 0px 0px;
}
@@ -479,6 +480,8 @@ ul.fancytree-container.fancytree-rtl.fancytree-no-connector > li {
* 'table' extension
*----------------------------------------------------------------------------*/
table.fancytree-ext-table {
font-family: tahoma, arial, helvetica;
font-size: 10pt;
border-collapse: collapse;
/* ext-ariagrid */
}
@@ -536,6 +539,9 @@ table.fancytree-ext-columnview span.fancytree-node {
display: inline-block;
}
table.fancytree-ext-columnview span.fancytree-node.fancytree-expanded {
background-color: #e0e0e0;
}
table.fancytree-ext-columnview span.fancytree-node.fancytree-active {
background-color: #CBE8F6;
}
table.fancytree-ext-columnview .fancytree-has-children span.fancytree-cv-right {

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,7 @@
body {
font-size: var(--main-font-size);
}
#container {
margin: 0 auto; /* center */
height: 100vh;
@@ -26,6 +30,7 @@
flex-shrink: 1;
flex-basis: 60%;
margin-top: 10px;
font-size: var(--tree-font-size);
}
#left-pane {
@@ -36,7 +41,7 @@
#header {
grid-area: header;
background-color: #f8f8f8;
background-color: var(--header-background-color);
display: flex;
align-items: center;
padding: 4px;
@@ -44,7 +49,7 @@
#header button {
padding: 1px 5px 1px 5px;
font-size: small;
font-size: smaller;
margin-bottom: 2px;
margin-top: 2px;
margin-right: 8px;
@@ -85,4 +90,8 @@
margin-top: 2px;
border-width: 1px;
border-style: solid;
}
#note-detail-wrapper {
font-size: var(--detail-font-size);
}

View File

@@ -12,12 +12,13 @@
.note-box {
padding: 16px;
position: absolute !important;
background-color: var(--accented-background-color);
color: var(--main-text-color);
z-index: 4;
border: 1px solid #666;
box-shadow: 2px 2px 19px #999;
border-radius: 8px;
opacity: 0.8;
background-color: white;
font-size: 11px;
width: auto;
height: auto;
@@ -28,7 +29,7 @@
}
.note-box:hover {
background-color: #ddd;
background-color: var(--more-accented-background-color);
}
.note-box .title {
@@ -37,11 +38,12 @@
}
.connection-label.jtk-hover, .jtk-source-hover, .jtk-target-hover {
background-color: #ddd;
background-color: var(--more-accented-background-color);
}
.connection-label {
background-color: white;
background-color: var(--accented-background-color);
color: var(--main-text-color);
opacity: 0.8;
padding: 0.3em;
border-radius: 0.5em;
@@ -64,10 +66,6 @@
box-shadow: 0 0 6px black;
}
.statemachine-demo .jtk-endpoint {
z-index: 3;
}
.dragHover {
border: 2px solid orange;
}

View File

@@ -1,8 +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 {
/* Fix for CKEditor block gutter icon "stretching" body and causing scrollbar to appear after pressing enter
on the last line of the editor. */
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 {
@@ -11,7 +119,8 @@ body {
#note-title {
margin-left: 15px;
font-size: x-large;
margin-right: 10px;
font-size: 150%;
border: 0;
width: 5em;
flex-grow: 100;
@@ -89,8 +198,6 @@ ul.fancytree-container {
display: none;
}
#note-detail-text { font-size: 1.1em; }
#note-detail-text h1 { font-size: 2.0em; }
#note-detail-text h2 { font-size: 1.8em; }
#note-detail-text h3 { font-size: 1.6em; }
@@ -117,12 +224,18 @@ ul.fancytree-container {
ul.fancytree-container {
outline: none !important;
background-color: inherit !important;
}
.fancytree-custom-icon {
font-size: 1.3em;
}
span.fancytree-title {
color: inherit !important;
background: inherit !important;
}
span.fancytree-node.protected > span.fancytree-custom-icon {
filter: drop-shadow(2px 2px 2px black);
}
@@ -140,22 +253,24 @@ span.fancytree-node.fancytree-active-clone:not(.fancytree-active) .fancytree-tit
padding-left: 5px;
}
/* By default not focused active tree item is not easily visible, this makes it more visible */
span.fancytree-active:not(.fancytree-focused) .fancytree-title {
background-color: #eee !important;
span.fancytree-active.fancytree-focused .fancytree-title {
color: var(--active-item-text-color) !important;
background-color: var(--active-item-background-color) !important;
border-color: #ddd !important;
border-radius: 3px;
}
span.fancytree-active.fancytree-focused .fancytree-title {
background-color: #ddd !important;
border-color: #bbb !important;
span.fancytree-active:not(.fancytree-focused) .fancytree-title {
color: var(--hover-item-text-color) !important;
background-color: var(--hover-item-background-color) !important;
border-color: #ddd !important;
border-radius: 3px;
}
.fancytree-plain span.fancytree-node:hover span.fancytree-title {
background-color: #eee !important;
border-color: #bbb !important;
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;
}
@@ -244,7 +359,7 @@ div.ui-tooltip {
/* Allow to use <kbd> elements inside the title to define shortcut hints. */
.ui-menu kbd, button kbd {
color: black;
color: var(--muted-text-color);
border: none;
background-color: transparent;
box-shadow: none;
@@ -265,8 +380,14 @@ div.ui-tooltip {
display: none;
}
.dropdown-menu {
color: var(--menu-text-color) !important;
background-color: var(--menu-background-color) !important;
}
.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;
}
@@ -276,7 +397,7 @@ div.ui-tooltip {
.dropdown-menu kbd
{
color: black;
color: var(--muted-text-color);
border: none;
background-color: transparent;
box-shadow: none;
@@ -344,7 +465,7 @@ div.ui-tooltip {
#file-table th, #file-table td {
padding: 10px;
font-size: large;
font-size: larger;
}
#children-overview {
@@ -360,9 +481,9 @@ div.ui-tooltip {
.child-overview {
font-weight: bold;
font-size: large;
font-size: larger;
padding: 10px;
background: #f4f4f4;
background: var(--accented-background-color);
width: 150px;
height: 90px;
line-height: 2em;
@@ -376,7 +497,7 @@ div.ui-tooltip {
}
.child-overview a {
color: #444;
color: var(--muted-text-color);
}
#sql-console-query {
@@ -390,13 +511,18 @@ div.ui-tooltip {
height: 150px;
}
.btn {
border-radius: var(--button-border-radius);
}
.btn:not(.btn-primary):not(.btn-secondary):not(.btn-danger) {
border-color: #ddd;
background-color: #eee;
border-color: var(--button-border-color);
background-color: var(--button-background-color);
color: var(--button-text-color);
}
.btn.active:not(.btn-primary) {
background-color: #ccc;
background-color: #ccc !important;
}
#note-path-list a.current {
@@ -417,33 +543,6 @@ button.icon-button {
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 {
transform: translateX(10px);
}
@@ -546,12 +645,12 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
}
.tooltip-inner {
background-color: #fbfbfb !important;
background-color: var(--accented-background-color) !important;
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 */
max-height: 300px;
overflow: hidden;
color: black;
color: var(--main-text-color);
border: 1px solid #ccc;
border-radius: 5px;
text-align: left;
@@ -581,7 +680,7 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
.algolia-autocomplete .aa-dropdown-menu {
width: 100%;
background-color: #fff;
background-color: var(--main-background-color);
border: 1px solid #999;
border-top: none;
z-index: 2000 !important;
@@ -603,7 +702,8 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
}
.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 {
@@ -669,7 +769,7 @@ div[data-notify="container"] {
position: absolute;
right: 10px;
top: 11px;
font-size: x-large;
font-size: 150%;
color: #777;
z-index: 100;
}
@@ -730,4 +830,13 @@ div[data-notify="container"] {
100% {
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;
}

View File

@@ -3,6 +3,7 @@
const noteService = require('../../services/notes');
const protectedSessionService = require('../../services/protected_session');
const repository = require('../../services/repository');
const utils = require('../../services/utils');
async function uploadFile(req) {
const parentNoteId = req.params.parentNoteId;
@@ -49,7 +50,7 @@ async function downloadFile(req, res) {
const originalFileName = await note.getLabel('originalFileName');
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.send(note.content);

View File

@@ -6,7 +6,7 @@ const log = require('../../services/log');
// options allowed to be updated directly in options dialog
const ALLOWED_OPTIONS = ['protectedSessionTimeout', 'noteRevisionSnapshotTimeInterval',
'zoomFactor', 'theme', 'syncServerHost', 'syncServerTimeout', 'syncProxy', 'leftPaneMinWidth', 'leftPaneWidthPercent', 'hoistedNoteId'];
'zoomFactor', 'theme', 'syncServerHost', 'syncServerTimeout', 'syncProxy', 'leftPaneMinWidth', 'leftPaneWidthPercent', 'hoistedNoteId', 'mainFontSize', 'treeFontSize', 'detailFontSize'];
async function getOptions() {
return await optionService.getOptionsMap(ALLOWED_OPTIONS);

View File

@@ -30,7 +30,7 @@ async function getStartupBundles() {
const bundles = [];
for (const note of notes) {
const bundle = await scriptService.getScriptBundle(note);
const bundle = await scriptService.getScriptBundleForFrontend(note);
if (bundle) {
bundles.push(bundle);
@@ -53,14 +53,21 @@ async function getRelationBundles(req) {
const bundles = [];
for (const noteId of uniqueNoteIds) {
bundles.push(await scriptService.getScriptBundleForNoteId(noteId));
const note = await repository.getNote(noteId);
const bundle = await scriptService.getScriptBundleForFrontend(note);
if (bundle) {
bundles.push(bundle);
}
}
return bundles;
}
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 = {

View File

@@ -16,6 +16,9 @@ async function index(req, res) {
leftPaneMinWidth: parseInt(options.leftPaneMinWidth),
leftPaneWidthPercent: parseInt(options.leftPaneWidthPercent),
rightPaneWidthPercent: 100 - parseInt(options.leftPaneWidthPercent),
mainFontSize: parseInt(options.mainFontSize),
treeFontSize: parseInt(options.treeFontSize),
detailFontSize: parseInt(options.detailFontSize),
sourceId: await sourceIdService.generateSourceId(),
maxSyncIdAtLoad: await sql.getValue("SELECT MAX(id) FROM sync"),
instanceName: config.General ? config.General.instanceName : null,

View File

@@ -4,8 +4,8 @@ const build = require('./build');
const packageJson = require('../../package');
const {TRILIUM_DATA_DIR} = require('./data_dir');
const APP_DB_VERSION = 121;
const SYNC_VERSION = 3;
const APP_DB_VERSION = 123;
const SYNC_VERSION = 4;
module.exports = {
appVersion: packageJson.version,

View File

@@ -1 +1 @@
module.exports = { buildDate:"2019-01-10T21:31:30+01:00", buildRevision: "0b251530fa0ee61edc8dcc9235033abb73afc614" };
module.exports = { buildDate:"2019-01-14T23:51:55+01:00", buildRevision: "e50f9cd0a354e29bb40c161cf7288e13b732e0d3" };

View File

@@ -18,25 +18,26 @@ function shaArray(content) {
}
function pad(data) {
let padded = Array.from(data);
if (data.length >= 16) {
padded = padded.slice(0, 16);
if (data.length > 16) {
data = data.slice(0, 16);
}
else {
padded = padded.concat(Array(16 - padded.length).fill(0));
else if (data.length < 16) {
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) {
throw new Error("No data key!");
}
const plainTextBuffer = Buffer.from(plainText);
const iv = crypto.randomBytes(ivLength);
const cipher = crypto.createCipheriv('aes-128-cbc', pad(key), pad(iv));
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()]);
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) {
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 cipherTextBuffer = Buffer.from(cipherText, 'base64');
const decryptedBytes = Buffer.concat([decipher.update(cipherTextBuffer), decipher.final()]);
const digest = decryptedBytes.slice(0, 4);
@@ -70,8 +77,8 @@ function decrypt(key, iv, cipherText) {
return payload;
}
function decryptString(dataKey, iv, cipherText) {
const buffer = decrypt(dataKey, iv, cipherText);
function decryptString(dataKey, cipherText) {
const buffer = decrypt(dataKey, cipherText);
const str = buffer.toString('utf-8');
@@ -84,26 +91,8 @@ function decryptString(dataKey, iv, cipherText) {
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 = {
encrypt,
decrypt,
decryptString,
noteTitleIv,
noteContentIv
decryptString
};

View File

@@ -1,13 +1,10 @@
"use strict";
const sanitize = require("sanitize-filename");
const repository = require("../../services/repository");
const utils = require('../../services/utils');
const repository = require("../repository");
const utils = require('../utils');
async function exportToOpml(branch, res) {
const note = await branch.getNote();
const title = (branch.prefix ? (branch.prefix + ' - ') : '') + note.title;
const sanitizedTitle = sanitize(title);
async function exportNoteInner(branchId) {
const branch = await repository.getBranch(branchId);
@@ -31,7 +28,9 @@ async function exportToOpml(branch, res) {
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.write(`<?xml version="1.0" encoding="UTF-8"?>

View File

@@ -1,9 +1,9 @@
"use strict";
const sanitize = require("sanitize-filename");
const TurndownService = require('turndown');
const mimeTypes = require('mime-types');
const html = require('html');
const utils = require('../utils');
async function exportSingleNote(branch, format, res) {
const note = await branch.getNote();
@@ -42,11 +42,9 @@ async function exportSingleNote(branch, format, res) {
mime = 'application/json';
}
const name = sanitize(note.title);
const filename = note.title + "." + extension;
console.log(name, extension, mime);
res.setHeader('Content-Disposition', `file; filename="${name}.${extension}"`);
res.setHeader('Content-Disposition', utils.getContentDisposition(filename));
res.setHeader('Content-Type', mime + '; charset=UTF-8');
res.send(payload);

View File

@@ -4,10 +4,11 @@ const html = require('html');
const repository = require('../repository');
const tar = require('tar-stream');
const path = require('path');
const sanitize = require("sanitize-filename");
const mimeTypes = require('mime-types');
const TurndownService = require('turndown');
const packageInfo = require('../../../package.json');
const utils = require('../utils');
const sanitize = require("sanitize-filename");
/**
* @param format - 'html' or 'markdown'
@@ -219,9 +220,9 @@ async function exportToTar(branch, format, res) {
pack.finalize();
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');
pack.pipe(res);

View File

@@ -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') {
await runAttachedRelations(await entity.getNote(), 'runOnAttributeChange', entity);
}

View File

@@ -359,18 +359,8 @@ async function deleteNote(branch) {
const notDeletedBranches = await note.getBranches();
if (notDeletedBranches.length === 0) {
// maybe a bit counter-intuitively, protected notes can be deleted also outside of protected session
// this is because protected notes offer only confidentiality which makes some things simpler - e.g. deletion UI
// 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();
}
note.isDeleted = true;
await note.save();
for (const childBranch of await note.getChildBranches()) {
await deleteNote(childBranch);

View File

@@ -24,7 +24,6 @@ async function initSyncedOptions(username, password) {
// passwordEncryptionService expects these options to already exist
await optionService.createOption('encryptedDataKey', '', true);
await optionService.createOption('encryptedDataKeyIv', '', true);
await passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16), true);
}

View File

@@ -14,13 +14,7 @@ async function verifyPassword(password) {
async function setDataKey(password, plainTextDataKey) {
const passwordDerivedKey = await myScryptService.getPasswordDerivedKey(password);
const encryptedDataKeyIv = utils.randomString(16);
await optionService.setOption('encryptedDataKeyIv', encryptedDataKeyIv);
const buffer = Buffer.from(plainTextDataKey);
const newEncryptedDataKey = dataEncryptionService.encrypt(passwordDerivedKey, encryptedDataKeyIv, buffer);
const newEncryptedDataKey = dataEncryptionService.encrypt(passwordDerivedKey, plainTextDataKey, 16);
await optionService.setOption('encryptedDataKey', newEncryptedDataKey);
}
@@ -28,10 +22,9 @@ async function setDataKey(password, plainTextDataKey) {
async function getDataKey(password) {
const passwordDerivedKey = await myScryptService.getPasswordDerivedKey(password);
const encryptedDataKeyIv = await optionService.getOption('encryptedDataKeyIv');
const encryptedDataKey = await optionService.getOption('encryptedDataKey');
const decryptedDataKey = dataEncryptionService.decrypt(passwordDerivedKey, encryptedDataKeyIv, encryptedDataKey);
const decryptedDataKey = dataEncryptionService.decrypt(passwordDerivedKey, encryptedDataKey, 16);
return decryptedDataKey;
}

View File

@@ -38,9 +38,7 @@ function decryptNoteTitle(noteId, encryptedTitle) {
const dataKey = getDataKey();
try {
const iv = dataEncryptionService.noteTitleIv(noteId);
return dataEncryptionService.decryptString(dataKey, iv, encryptedTitle);
return dataEncryptionService.decryptString(dataKey, encryptedTitle);
}
catch (e) {
e.message = `Cannot decrypt note title for noteId=${noteId}: ` + e.message;
@@ -57,17 +55,15 @@ function decryptNote(note) {
try {
if (note.title) {
note.title = dataEncryptionService.decryptString(dataKey, dataEncryptionService.noteTitleIv(note.noteId), note.title);
note.title = dataEncryptionService.decryptString(dataKey, note.title);
}
if (note.content) {
const contentIv = dataEncryptionService.noteContentIv(note.noteId);
if (note.type === 'file') {
note.content = dataEncryptionService.decrypt(dataKey, contentIv, note.content);
if (note.type === 'file' || note.type === 'image') {
note.content = dataEncryptionService.decrypt(dataKey, note.content);
}
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) {
hist.title = dataEncryptionService.decryptString(dataKey, dataEncryptionService.noteTitleIv(hist.noteRevisionId), hist.title);
hist.title = dataEncryptionService.decryptString(dataKey, hist.title);
}
if (hist.content) {
hist.content = dataEncryptionService.decryptString(dataKey, dataEncryptionService.noteContentIv(hist.noteRevisionId), hist.content);
hist.content = dataEncryptionService.decryptString(dataKey, hist.content);
}
}
function encryptNote(note) {
const dataKey = getDataKey();
note.title = dataEncryptionService.encrypt(dataKey, dataEncryptionService.noteTitleIv(note.noteId), note.title);
note.content = dataEncryptionService.encrypt(dataKey, dataEncryptionService.noteContentIv(note.noteId), note.content);
note.title = dataEncryptionService.encrypt(dataKey, note.title);
note.content = dataEncryptionService.encrypt(dataKey, note.content);
}
function encryptNoteRevision(revision) {
const dataKey = getDataKey();
revision.title = dataEncryptionService.encrypt(dataKey, dataEncryptionService.noteTitleIv(revision.noteRevisionId), revision.title);
revision.content = dataEncryptionService.encrypt(dataKey, dataEncryptionService.noteContentIv(revision.noteRevisionId), revision.content);
revision.title = dataEncryptionService.encrypt(dataKey, revision.title);
revision.content = dataEncryptionService.encrypt(dataKey, revision.content);
}
module.exports = {

View File

@@ -47,11 +47,6 @@ async function getBranch(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} */
async function getAttribute(attributeId) {
return await getEntity("SELECT * FROM attributes WHERE attributeId = ?", [attributeId]);
@@ -122,7 +117,6 @@ module.exports = {
getEntity,
getNote,
getBranch,
getImage,
getAttribute,
getOption,
updateEntity,

View File

@@ -79,6 +79,19 @@ function getParams(params) {
}).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 = []) {
if (!note.isContentAvailable) {
return;
@@ -153,14 +166,8 @@ function sanitizeVariableName(str) {
return str.replace(/[^a-z0-9_]/gim, "");
}
async function getScriptBundleForNoteId(noteId) {
const note = await repository.getNote(noteId);
return await getScriptBundle(note);
}
module.exports = {
executeNote,
executeScript,
getScriptBundle,
getScriptBundleForNoteId
getScriptBundleForFrontend
};

View File

@@ -4,6 +4,7 @@ const crypto = require('crypto');
const randtoken = require('rand-token').generator({source: 'crypto'});
const unescape = require('unescape');
const escape = require('escape-html');
const sanitize = require("sanitize-filename");
function newEntityId() {
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 = {
randomSecureToken,
randomString,
@@ -147,5 +164,7 @@ module.exports = {
intersection,
union,
escapeRegExp,
crash
crash,
sanitizeFilenameForHeader,
getContentDisposition
};

View File

@@ -1,10 +1,10 @@
<!DOCTYPE html>
<html lang="en" class="theme-<%= theme %>">
<html lang="en">
<head>
<meta charset="utf-8">
<title>Trilium Notes</title>
</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="header" class="hide-toggle">
<div id="history-navigation" style="display: none;">

View File

@@ -55,6 +55,46 @@
<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>
<div class="form-group">

View File

@@ -7,7 +7,7 @@
</head>
<body>
<div class="container">
<div id="setup-dialog" class="col-md-12 col-lg-8 col-xl-6 mx-auto" style="padding-top: 25px;">
<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;">