Compare commits

...

34 Commits

Author SHA1 Message Date
zadam
7672f22ce0 release 0.46.7 2021-04-03 22:37:04 +02:00
zadam
ef37a52a06 when leaving protected session don't forget to reset note cache (titles), #1810 2021-04-03 22:02:25 +02:00
zadam
2318d615bb make note cache decryption more robust - one failure will not crash the whole process, #1810 2021-04-03 21:48:16 +02:00
zadam
bc14c3d665 backport not deforming standard top widget in small window size from master 2021-03-31 22:59:07 +02:00
zadam
b89ea9a684 check for note cache branch existence, #1808 2021-03-31 22:35:10 +02:00
zadam
496767a52b release 0.46.6 2021-03-25 20:28:57 +01:00
zadam
9139c597e5 use only one label for icon class, fixes #1791 2021-03-25 19:46:10 +01:00
zadam
942132c01d release 0.46.6 2021-03-23 23:57:48 +01:00
zadam
1862acd1ff avoid getting refreshes on note title 2021-03-23 22:46:18 +01:00
zadam
ce7e18d0b0 fix syncing protected notes in case there wasn't any other change, closes #1778 2021-03-23 22:18:23 +01:00
zadam
65280d5ba3 avoid ugly error in the logs, #1778 2021-03-22 23:27:41 +01:00
zadam
7e3d424e23 fix deleting all revisions, closes #1774 2021-03-22 23:07:43 +01:00
zadam
bff04c121a fix scrolling in mobile frontend, closes #1768 2021-03-20 12:47:47 +01:00
zadam
e8903e82a1 add option to disable note tree auto collapse, fixes #1751 2021-03-18 20:11:58 +01:00
zadam
0cfd95d9b8 not finding node for a note path does not have to be always an error 2021-03-17 23:17:54 +01:00
zadam
1aa5349628 make edit button visible for empty readonly notes, fixes #1760 2021-03-17 22:41:37 +01:00
zadam
4e21d12202 fix nodejs 10 compatibility, closes #1746 2021-03-15 20:31:12 +01:00
zadam
fdce218e88 release 0.46.5 2021-03-14 22:56:27 +01:00
zadam
6c8d20288d fix 0.46 regression to set up sync from client, fixes #1742 2021-03-13 22:54:00 +01:00
zadam
88d04772c4 make sure the CLS entity changes are cleared after roll backed transaction, #1736 2021-03-12 23:48:14 +01:00
zadam
9fd26a9b9f when creating new note into inbox use current hoisted note in new tab as well 2021-03-12 21:47:26 +01:00
zadam
98f02c3c9a added "hoistedInbox" label 2021-03-12 21:29:50 +01:00
zadam
584fea1992 Revert "delete notes skeleton dialog"
This reverts commit 03a11e6f
2021-03-12 20:44:19 +01:00
zadam
af50a1ec52 Merge remote-tracking branch 'origin/stable' into stable 2021-03-12 20:39:51 +01:00
zadam
4e76d1fa85 hide some "boring" attributes from book listing view 2021-03-12 20:39:42 +01:00
zadam
03a11e6f77 delete notes skeleton dialog 2021-03-11 22:35:53 +01:00
zadam
e1a16b4a9f defensive check, #1736 2021-03-11 20:28:07 +01:00
zadam
12b468d3dc release 0.46.4-beta 2021-03-10 23:35:12 +01:00
zadam
6f901e6852 use icons instead of plain text to differentiate between different paths in note paths widget 2021-03-10 23:11:48 +01:00
zadam
09e9ac4d00 prevent cycles in resolving the notepath, fixes #1730 2021-03-10 22:54:55 +01:00
zadam
a33ac65fdf fix focus on moving notes with keyboard 2021-03-09 22:24:59 +01:00
zadam
f8fb071a6f added option to bring back plain (non-markdown) headings, closes #1678 2021-03-09 22:06:40 +01:00
zadam
a654078e56 fix broken template copy, closes #1724 2021-03-09 20:51:57 +01:00
zadam
fba68681aa when resolving note path check if there's a hoisted note in it, if not, try again to find some path with hoisted note, closes #1718 2021-03-09 20:37:56 +01:00
44 changed files with 300 additions and 106 deletions

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "trilium", "name": "trilium",
"version": "0.46.2-beta", "version": "0.46.5",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@@ -2,7 +2,7 @@
"name": "trilium", "name": "trilium",
"productName": "Trilium Notes", "productName": "Trilium Notes",
"description": "Trilium Notes", "description": "Trilium Notes",
"version": "0.46.3-beta", "version": "0.46.7",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"main": "electron.js", "main": "electron.js",
"bin": { "bin": {
@@ -14,7 +14,7 @@
}, },
"scripts": { "scripts": {
"start-server": "cross-env TRILIUM_ENV=dev node ./src/www", "start-server": "cross-env TRILIUM_ENV=dev node ./src/www",
"start-electron": "cross-env TRILIUM_ENV=dev electron .", "start-electron": "cross-env TRILIUM_ENV=dev electron --inspect=5858 .",
"build-backend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/entities/*.js src/services/backend_script_api.js", "build-backend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/entities/*.js src/services/backend_script_api.js",
"build-frontend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/collapsible_widget.js", "build-frontend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/collapsible_widget.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",

View File

@@ -49,7 +49,12 @@ class Note extends Entity {
this.isContentAvailable = protectedSessionService.isProtectedSessionAvailable(); this.isContentAvailable = protectedSessionService.isProtectedSessionAvailable();
if (this.isContentAvailable) { if (this.isContentAvailable) {
this.title = protectedSessionService.decryptString(this.title); try {
this.title = protectedSessionService.decryptString(this.title);
}
catch (e) {
throw new Error(`Could not decrypt title of note ${this.noteId}: ${e.message} ${e.stack}`)
}
} }
else { else {
this.title = "[protected]"; this.title = "[protected]";
@@ -156,14 +161,14 @@ class Note extends Entity {
sql.upsert("note_contents", "noteId", pojo); sql.upsert("note_contents", "noteId", pojo);
const hash = utils.hash(this.noteId + "|" + content.toString()); const hash = utils.hash(this.noteId + "|" + pojo.content.toString());
entityChangesService.addEntityChange({ entityChangesService.addEntityChange({
entityName: 'note_contents', entityName: 'note_contents',
entityId: this.noteId, entityId: this.noteId,
hash: hash, hash: hash,
isErased: false, isErased: false,
utcDateChanged: this.getUtcDateChanged() utcDateChanged: pojo.utcDateModified
}, null); }, null);
} }

View File

@@ -28,6 +28,16 @@ const TPL = `
</select> </select>
</div> </div>
</div> </div>
<div class="form-group row">
<div class="col-4">
<label for="heading-style">Heading style</label>
<select class="form-control" id="heading-style">
<option value="plain">Plain</option>
<option value="markdown">Markdown-style</option>
</select>
</div>
</div>
<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>
@@ -78,6 +88,7 @@ export default class ApperanceOptions {
this.$themeSelect = $("#theme-select"); this.$themeSelect = $("#theme-select");
this.$zoomFactorSelect = $("#zoom-factor-select"); this.$zoomFactorSelect = $("#zoom-factor-select");
this.$nativeTitleBarSelect = $("#native-title-bar-select"); this.$nativeTitleBarSelect = $("#native-title-bar-select");
this.$headingStyle = $("#heading-style");
this.$mainFontSize = $("#main-font-size"); this.$mainFontSize = $("#main-font-size");
this.$treeFontSize = $("#tree-font-size"); this.$treeFontSize = $("#tree-font-size");
this.$detailFontSize = $("#detail-font-size"); this.$detailFontSize = $("#detail-font-size");
@@ -86,11 +97,7 @@ export default class ApperanceOptions {
this.$themeSelect.on('change', () => { this.$themeSelect.on('change', () => {
const newTheme = this.$themeSelect.val(); const newTheme = this.$themeSelect.val();
for (const clazz of Array.from(this.$body[0].classList)) { // create copy to safely iterate over while removing classes this.toggleBodyClass("theme-", newTheme);
if (clazz.startsWith("theme-")) {
this.$body.removeClass(clazz);
}
}
const noteId = $(this).find(":selected").attr("data-note-id"); const noteId = $(this).find(":selected").attr("data-note-id");
@@ -100,8 +107,6 @@ export default class ApperanceOptions {
libraryLoader.requireCss(`api/notes/download/${noteId}`); libraryLoader.requireCss(`api/notes/download/${noteId}`);
} }
this.$body.addClass("theme-" + newTheme);
server.put('options/theme/' + newTheme); server.put('options/theme/' + newTheme);
}); });
@@ -113,6 +118,14 @@ export default class ApperanceOptions {
server.put('options/nativeTitleBarVisible/' + nativeTitleBarVisible); server.put('options/nativeTitleBarVisible/' + nativeTitleBarVisible);
}); });
this.$headingStyle.on('change', () => {
const newHeadingStyle = this.$headingStyle.val();
this.toggleBodyClass("heading-style-", newHeadingStyle);
server.put('options/headingStyle/' + newHeadingStyle);
});
this.$mainFontSize.on('change', async () => { this.$mainFontSize.on('change', async () => {
await server.put('options/mainFontSize/' + this.$mainFontSize.val()); await server.put('options/mainFontSize/' + this.$mainFontSize.val());
@@ -132,6 +145,16 @@ export default class ApperanceOptions {
}); });
} }
toggleBodyClass(prefix, value) {
for (const clazz of Array.from(this.$body[0].classList)) { // create copy to safely iterate over while removing classes
if (clazz.startsWith(prefix)) {
this.$body.removeClass(clazz);
}
}
this.$body.addClass(prefix + value);
}
async optionsLoaded(options) { async optionsLoaded(options) {
const themes = [ const themes = [
{ val: 'white', title: 'White' }, { val: 'white', title: 'White' },
@@ -159,6 +182,8 @@ export default class ApperanceOptions {
this.$nativeTitleBarSelect.val(options.nativeTitleBarVisible === 'true' ? 'show' : 'hide'); this.$nativeTitleBarSelect.val(options.nativeTitleBarVisible === 'true' ? 'show' : 'hide');
this.$headingStyle.val(options.headingStyle);
this.$mainFontSize.val(options.mainFontSize); this.$mainFontSize.val(options.mainFontSize);
this.$treeFontSize.val(options.treeFontSize); this.$treeFontSize.val(options.treeFontSize);
this.$detailFontSize.val(options.detailFontSize); this.$detailFontSize.val(options.detailFontSize);

View File

@@ -254,22 +254,39 @@ class NoteShort {
return noteAttributeCache.attributes[this.noteId]; return noteAttributeCache.attributes[this.noteId];
} }
getAllNotePaths() { getAllNotePaths(encounteredNoteIds = null) {
if (this.noteId === 'root') { if (this.noteId === 'root') {
return [['root']]; return [['root']];
} }
if (!encounteredNoteIds) {
encounteredNoteIds = new Set();
}
encounteredNoteIds.add(this.noteId);
const parentNotes = this.getParentNotes(); const parentNotes = this.getParentNotes();
let paths; let paths;
if (parentNotes.length === 1) { // optimization for the most common case if (parentNotes.length === 1) { // optimization for the most common case
paths = parentNotes[0].getAllNotePaths(); if (encounteredNoteIds.has(parentNotes[0].noteId)) {
return [];
}
else {
paths = parentNotes[0].getAllNotePaths(encounteredNoteIds);
}
} }
else { else {
paths = []; paths = [];
for (const parentNote of parentNotes) { for (const parentNote of parentNotes) {
paths.push(...parentNote.getAllNotePaths()); if (encounteredNoteIds.has(parentNote.noteId)) {
continue;
}
const newSet = new Set(encounteredNoteIds);
paths.push(...parentNote.getAllNotePaths(newSet));
} }
} }
@@ -342,7 +359,7 @@ class NoteShort {
const workspaceIconClass = this.getWorkspaceIconClass(); const workspaceIconClass = this.getWorkspaceIconClass();
if (iconClassLabels.length > 0) { if (iconClassLabels.length > 0) {
return iconClassLabels.map(l => l.value).join(' '); return iconClassLabels[0].value;
} }
else if (workspaceIconClass) { else if (workspaceIconClass) {
return workspaceIconClass; return workspaceIconClass;

View File

@@ -83,6 +83,9 @@ export default class MobileLayout {
.child(new NoteTitleWidget()) .child(new NoteTitleWidget())
.child(new CloseDetailButtonWidget())) .child(new CloseDetailButtonWidget()))
.child(new NoteDetailWidget() .child(new NoteDetailWidget()
.css('padding', '5px 20px 10px 0'))); .css('padding', '5px 20px 10px 0')
.css('overflow', 'auto')
.css('height', '100%')
));
} }
} }

View File

@@ -79,6 +79,15 @@ async function renderAttributes(attributes, renderIsInheritable) {
return $container; return $container;
} }
const HIDDEN_ATTRIBUTES = [
'originalFileName',
'template',
'cssClass',
'iconClass',
'pageSize',
'viewType'
];
async function renderNormalAttributes(note) { async function renderNormalAttributes(note) {
const promotedDefinitionAttributes = note.getPromotedDefinitionAttributes(); const promotedDefinitionAttributes = note.getPromotedDefinitionAttributes();
let attrs = note.getAttributes(); let attrs = note.getAttributes();
@@ -90,6 +99,7 @@ async function renderNormalAttributes(note) {
attrs = attrs.filter( attrs = attrs.filter(
attr => !attr.isDefinition() attr => !attr.isDefinition()
&& !attr.isAutoLink && !attr.isAutoLink
&& !HIDDEN_ATTRIBUTES.includes(attr.name)
&& attr.noteId === note.noteId && attr.noteId === note.noteId
); );
} }

View File

@@ -74,7 +74,11 @@ export default class Entrypoints extends Component {
await ws.waitForMaxKnownEntityChangeId(); await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.openTabWithNote(note.noteId, true); const hoistedNoteId = appContext.tabManager.getActiveTabContext()
? appContext.tabManager.getActiveTabContext().hoistedNoteId
: 'root';
await appContext.tabManager.openTabWithNote(note.noteId, true, null, hoistedNoteId);
appContext.triggerEvent('focusAndSelectTitle'); appContext.triggerEvent('focusAndSelectTitle');
} }

View File

@@ -26,9 +26,7 @@ function isHoistedNode(node) {
} }
async function checkNoteAccess(notePath, tabContext) { async function checkNoteAccess(notePath, tabContext) {
// notePath argument can contain only noteId which is not good when hoisted since const resolvedNotePath = await treeService.resolveNotePath(notePath, tabContext.hoistedNoteId);
// then we need to check the whole note path
const resolvedNotePath = await treeService.resolveNotePath(notePath);
if (!resolvedNotePath) { if (!resolvedNotePath) {
console.log("Cannot activate " + notePath); console.log("Cannot activate " + notePath);
@@ -37,7 +35,7 @@ async function checkNoteAccess(notePath, tabContext) {
const hoistedNoteId = tabContext.hoistedNoteId; const hoistedNoteId = tabContext.hoistedNoteId;
if (hoistedNoteId !== 'root' && !resolvedNotePath.includes(hoistedNoteId)) { if (!resolvedNotePath.includes(hoistedNoteId)) {
const confirmDialog = await import('../dialogs/confirm.js'); const confirmDialog = await import('../dialogs/confirm.js');
if (!await confirmDialog.confirm("Requested note is outside of hoisted note subtree and you must unhoist to access the note. Do you want to proceed with unhoisting?")) { if (!await confirmDialog.confirm("Requested note is outside of hoisted note subtree and you must unhoist to access the note. Do you want to proceed with unhoisting?")) {

View File

@@ -1,5 +1,6 @@
import utils from "./utils.js"; import utils from "./utils.js";
import options from './options.js'; import options from './options.js';
import server from "./server.js";
const PROTECTED_SESSION_ID_KEY = 'protectedSessionId'; const PROTECTED_SESSION_ID_KEY = 'protectedSessionId';
@@ -23,11 +24,11 @@ function resetSessionCookie() {
utils.setSessionCookie(PROTECTED_SESSION_ID_KEY, null); utils.setSessionCookie(PROTECTED_SESSION_ID_KEY, null);
} }
function resetProtectedSession() { async function resetProtectedSession() {
resetSessionCookie(); resetSessionCookie();
// most secure solution - guarantees nothing remained in memory await server.post("logout/protected");
// since this expires because user doesn't use the app, it shouldn't be disruptive
utils.reloadApp(); utils.reloadApp();
} }

View File

@@ -37,7 +37,7 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr
path.push('root'); path.push('root');
} }
const effectivePath = []; const effectivePathSegments = [];
let childNoteId = null; let childNoteId = null;
let i = 0; let i = 0;
@@ -81,7 +81,7 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr
const pathToRoot = someNotePath.split("/").reverse().slice(1); const pathToRoot = someNotePath.split("/").reverse().slice(1);
for (const noteId of pathToRoot) { for (const noteId of pathToRoot) {
effectivePath.push(noteId); effectivePathSegments.push(noteId);
} }
} }
@@ -89,11 +89,23 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr
} }
} }
effectivePath.push(parentNoteId); effectivePathSegments.push(parentNoteId);
childNoteId = parentNoteId; childNoteId = parentNoteId;
} }
return effectivePath.reverse(); effectivePathSegments.reverse();
if (effectivePathSegments.includes(hoistedNoteId)) {
return effectivePathSegments;
}
else {
const note = await treeCache.getNote(getNoteIdFromNotePath(notePath));
const someNotePathSegments = getSomeNotePathSegments(note, hoistedNoteId);
// if there isn't actually any note path with hoisted note then return the original resolved note path
return someNotePathSegments.includes(hoistedNoteId) ? someNotePathSegments : effectivePathSegments;
}
} }
function getSomeNotePathSegments(note, hoistedNotePath = 'root') { function getSomeNotePathSegments(note, hoistedNotePath = 'root') {

View File

@@ -194,9 +194,18 @@ const ATTR_HELP = {
"appTheme": "marks CSS notes which are full Trilium themes and are thus available in Trilium options.", "appTheme": "marks CSS notes which are full Trilium themes and are thus available in Trilium options.",
"cssClass": "value of this label is then added as CSS class to the node representing given note in the tree. This can be useful for advanced theming. Can be used in template notes.", "cssClass": "value of this label is then added as CSS class to the node representing given note in the tree. This can be useful for advanced theming. Can be used in template notes.",
"iconClass": "value of this label is added as a CSS class to the icon on the tree which can help visually distinguish the notes in the tree. Example might be bx bx-home - icons are taken from boxicons. Can be used in template notes.", "iconClass": "value of this label is added as a CSS class to the icon on the tree which can help visually distinguish the notes in the tree. Example might be bx bx-home - icons are taken from boxicons. Can be used in template notes.",
"bookZoomLevel": 'applies only to book note and sets the "zoom level" (how many notes fit on 1 row)', "pageSize": "number of items per page in note listing",
"customRequestHandler": 'see <a href="javascript:" data-help-page="Custom request handler">Custom request handler</a>', "customRequestHandler": 'see <a href="javascript:" data-help-page="Custom request handler">Custom request handler</a>',
"customResourceProvider": 'see <a href="javascript:" data-help-page="Custom request handler">Custom request handler</a>' "customResourceProvider": 'see <a href="javascript:" data-help-page="Custom request handler">Custom request handler</a>',
"widget": "marks this note as a custom widget which will be added to the Trilium component tree",
"workspace": "marks this note as a workspace which allows easy hoisting",
"workspaceIconClass": "defines box icon CSS class which will be used in tab when hoisted to this note",
"workspaceTabBackgroundColor": "CSS color used in the note tab when hoisted to this note",
"searchHome": "new search notes will be created as children of this note",
"hoistedSearchHome": "new search notes will be created as children of this note when hoisted to some ancestor of this note",
"inbox": "default inbox location for new notes",
"hoistedInbox": "default inbox location for new notes when hoisted to some ancestor of this note",
"sqlConsoleHome": "default location of SQL console notes",
}, },
"relation": { "relation": {
"runOnNoteCreation": "executes when note is created on backend", "runOnNoteCreation": "executes when note is created on backend",

View File

@@ -278,7 +278,7 @@ export default class NoteDetailWidget extends TabAwareWidget {
const label = attrs.find(attr => const label = attrs.find(attr =>
attr.type === 'label' attr.type === 'label'
&& ['readOnly', 'autoReadOnlyDisabled', 'cssClass', 'bookZoomLevel', 'displayRelations'].includes(attr.name) && ['readOnly', 'autoReadOnlyDisabled', 'cssClass', 'displayRelations'].includes(attr.name)
&& attr.isAffecting(this.note)); && attr.isAffecting(this.note));
const relation = attrs.find(attr => const relation = attrs.find(attr =>

View File

@@ -88,7 +88,7 @@ export default class NotePathsWidget extends TabAwareWidget {
.find('a') .find('a')
.addClass("no-tooltip-preview"); .addClass("no-tooltip-preview");
const comments = []; const icons = [];
if (this.notePath === notePath) { if (this.notePath === notePath) {
$noteLink.addClass("path-current"); $noteLink.addClass("path-current");
@@ -98,23 +98,23 @@ export default class NotePathsWidget extends TabAwareWidget {
$noteLink.addClass("path-in-hoisted-subtree"); $noteLink.addClass("path-in-hoisted-subtree");
} }
else { else {
comments.push("outside of hoisting"); icons.push(`<span class="bx bx-trending-up" title="This path is outside of hoisted note and you would have to unhoist."></span>`);
} }
if (notePathRecord.isArchived) { if (notePathRecord.isArchived) {
$noteLink.addClass("path-archived"); $noteLink.addClass("path-archived");
comments.push("archived"); icons.push(`<span class="bx bx-archive" title="Archived"></span>`);
} }
if (notePathRecord.isSearch) { if (notePathRecord.isSearch) {
$noteLink.addClass("path-search"); $noteLink.addClass("path-search");
comments.push("search"); icons.push(`<span class="bx bx-search" title="Search"></span>`);
} }
if (comments.length > 0) { if (icons.length > 0) {
$noteLink.append(` (${comments.join(', ')})`); $noteLink.append(` ${icons.join(' ')}`);
} }
this.$notePathList.append($noteLink); this.$notePathList.append($noteLink);

View File

@@ -36,7 +36,7 @@ export default class NoteTitleWidget extends TabAwareWidget {
protectedSessionHolder.touchProtectedSessionIfNecessary(this.note); protectedSessionHolder.touchProtectedSessionIfNecessary(this.note);
await server.put(`notes/${this.noteId}/change-title`, {title}); await server.put(`notes/${this.noteId}/change-title`, {title}, this.componentId);
}); });
appContext.addBeforeUnloadListener(this); appContext.addBeforeUnloadListener(this);

View File

@@ -176,6 +176,15 @@ const TPL = `
title="Images which are shown in the parent text note will not be displayed in the tree"></span> title="Images which are shown in the parent text note will not be displayed in the tree"></span>
</label> </label>
</div> </div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input auto-collapse-note-tree" type="checkbox" value="">
Automatically collapse notes
<span class="bx bx-info-circle"
title="Notes will be collapsed after period of inactivity to declutter the tree."></span>
</label>
</div>
<br/> <br/>
@@ -235,6 +244,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
this.$treeSettingsPopup = this.$widget.find('.tree-settings-popup'); this.$treeSettingsPopup = this.$widget.find('.tree-settings-popup');
this.$hideArchivedNotesCheckbox = this.$treeSettingsPopup.find('.hide-archived-notes'); this.$hideArchivedNotesCheckbox = this.$treeSettingsPopup.find('.hide-archived-notes');
this.$hideIncludedImages = this.$treeSettingsPopup.find('.hide-included-images'); this.$hideIncludedImages = this.$treeSettingsPopup.find('.hide-included-images');
this.$autoCollapseNoteTree = this.$treeSettingsPopup.find('.auto-collapse-note-tree');
this.$treeSettingsButton = this.$widget.find('.tree-settings-button'); this.$treeSettingsButton = this.$widget.find('.tree-settings-button');
this.$treeSettingsButton.on("click", e => { this.$treeSettingsButton.on("click", e => {
@@ -245,6 +255,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
this.$hideArchivedNotesCheckbox.prop("checked", this.hideArchivedNotes); this.$hideArchivedNotesCheckbox.prop("checked", this.hideArchivedNotes);
this.$hideIncludedImages.prop("checked", this.hideIncludedImages); this.$hideIncludedImages.prop("checked", this.hideIncludedImages);
this.$autoCollapseNoteTree.prop("checked", this.autoCollapseNoteTree);
let top = this.$treeSettingsButton[0].offsetTop; let top = this.$treeSettingsButton[0].offsetTop;
let left = this.$treeSettingsButton[0].offsetLeft; let left = this.$treeSettingsButton[0].offsetLeft;
@@ -272,6 +283,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
this.$saveTreeSettingsButton.on('click', async () => { this.$saveTreeSettingsButton.on('click', async () => {
await this.setHideArchivedNotes(this.$hideArchivedNotesCheckbox.prop("checked")); await this.setHideArchivedNotes(this.$hideArchivedNotesCheckbox.prop("checked"));
await this.setHideIncludedImages(this.$hideIncludedImages.prop("checked")); await this.setHideIncludedImages(this.$hideIncludedImages.prop("checked"));
await this.setAutoCollapseNoteTree(this.$autoCollapseNoteTree.prop("checked"));
this.$treeSettingsPopup.hide(); this.$treeSettingsPopup.hide();
@@ -327,6 +339,14 @@ export default class NoteTreeWidget extends TabAwareWidget {
await options.save("hideIncludedImages_" + this.treeName, val.toString()); await options.save("hideIncludedImages_" + this.treeName, val.toString());
} }
get autoCollapseNoteTree() {
return options.is("autoCollapseNoteTree");
}
async setAutoCollapseNoteTree(val) {
await options.save("autoCollapseNoteTree", val.toString());
}
initFancyTree() { initFancyTree() {
const treeData = [this.prepareRootNode()]; const treeData = [this.prepareRootNode()];
@@ -797,8 +817,10 @@ export default class NoteTreeWidget extends TabAwareWidget {
const node = await this.expandToNote(activeContext.notePath); const node = await this.expandToNote(activeContext.notePath);
await node.makeVisible({scrollIntoView: true}); if (node) {
node.setActive(true, {noEvents: true, noFocus: false}); await node.makeVisible({scrollIntoView: true});
node.setActive(true, {noEvents: true, noFocus: false});
}
} }
} }
@@ -851,7 +873,11 @@ export default class NoteTreeWidget extends TabAwareWidget {
// these are real notes with real notePath, user can display them in a detail // these are real notes with real notePath, user can display them in a detail
// but they don't have a node in the tree // but they don't have a node in the tree
ws.logError(`Can't find node for child node of noteId=${childNoteId} for parent of noteId=${parentNode.data.noteId} and hoistedNoteId=${hoistedNoteService.getHoistedNoteId()}, requested path is ${notePath}`); const childNote = await treeCache.getNote(childNoteId);
if (!childNote || childNote.type !== 'image') {
ws.logError(`Can't find node for child node of noteId=${childNoteId} for parent of noteId=${parentNode.data.noteId} and hoistedNoteId=${hoistedNoteService.getHoistedNoteId()}, requested path is ${notePath}`);
}
} }
return; return;
@@ -955,6 +981,10 @@ export default class NoteTreeWidget extends TabAwareWidget {
} }
this.autoCollapseTimeoutId = setTimeout(() => { this.autoCollapseTimeoutId = setTimeout(() => {
if (!this.autoCollapseNoteTree) {
return;
}
/* /*
* We're collapsing notes after period of inactivity to "cleanup" the tree - users rarely * We're collapsing notes after period of inactivity to "cleanup" the tree - users rarely
* collapse the notes and the tree becomes unusuably large. * collapse the notes and the tree becomes unusuably large.
@@ -994,8 +1024,6 @@ export default class NoteTreeWidget extends TabAwareWidget {
const nextNode = activeNode ? (activeNode.getNextSibling() || activeNode.getPrevSibling() || activeNode.getParent()) : null; const nextNode = activeNode ? (activeNode.getNextSibling() || activeNode.getPrevSibling() || activeNode.getParent()) : null;
const activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null; const activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null;
console.log(activeNotePath, activeNodeFocused);
const nextNotePath = nextNode ? treeService.getNotePath(nextNode) : null; const nextNotePath = nextNode ? treeService.getNotePath(nextNode) : null;
const activeNoteId = activeNode ? activeNode.data.noteId : null; const activeNoteId = activeNode ? activeNode.data.noteId : null;
@@ -1117,6 +1145,10 @@ export default class NoteTreeWidget extends TabAwareWidget {
if (node) { if (node) {
node.setActive(true, {noEvents: true, noFocus: !activeNodeFocused}); node.setActive(true, {noEvents: true, noFocus: !activeNodeFocused});
if (activeNodeFocused) {
node.setFocus(true);
}
} }
else { else {
// this is used when original note has been deleted and we want to move the focus to the note above/below // this is used when original note has been deleted and we want to move the focus to the note above/below

View File

@@ -14,6 +14,10 @@ const TPL = `
height: 35px; height: 35px;
} }
.standard-top-widget > div {
flex-shrink: 0; /* fixes https://github.com/zadam/trilium/issues/1745 */
}
.standard-top-widget button.noborder { .standard-top-widget button.noborder {
padding: 1px 5px 1px 5px; padding: 1px 5px 1px 5px;
font-size: 90%; font-size: 90%;

View File

@@ -49,15 +49,16 @@ const TPL = `
} }
.note-detail-editable-text h2 { font-size: 1.8em; } .note-detail-editable-text h2 { font-size: 1.8em; }
.note-detail-editable-text h2::before { content: "##\\2004"; color: var(--muted-text-color); }
.note-detail-editable-text h3 { font-size: 1.6em; } .note-detail-editable-text h3 { font-size: 1.6em; }
.note-detail-editable-text h3::before { content: "###\\2004"; color: var(--muted-text-color); }
.note-detail-editable-text h4 { font-size: 1.4em; } .note-detail-editable-text h4 { font-size: 1.4em; }
.note-detail-editable-text h4:not(.include-note-title)::before { content: "####\\2004"; color: var(--muted-text-color); }
.note-detail-editable-text h5 { font-size: 1.2em; } .note-detail-editable-text h5 { font-size: 1.2em; }
.note-detail-editable-text h5::before { content: "#####\\2004"; color: var(--muted-text-color); }
.note-detail-editable-text h6 { font-size: 1.1em; } .note-detail-editable-text h6 { font-size: 1.1em; }
.note-detail-editable-text h6::before { content: "######\\2004"; color: var(--muted-text-color); }
body.heading-style-markdown .note-detail-editable-text h2::before { content: "##\\2004"; color: var(--muted-text-color); }
body.heading-style-markdown .note-detail-editable-text h3::before { content: "###\\2004"; color: var(--muted-text-color); }
body.heading-style-markdown .note-detail-editable-text h4:not(.include-note-title)::before { content: "####\\2004"; color: var(--muted-text-color); }
body.heading-style-markdown .note-detail-editable-text h5::before { content: "#####\\2004"; color: var(--muted-text-color); }
body.heading-style-markdown .note-detail-editable-text h6::before { content: "######\\2004"; color: var(--muted-text-color); }
.note-detail-editable-text-editor { .note-detail-editable-text-editor {
padding-top: 10px; padding-top: 10px;

View File

@@ -5,6 +5,7 @@ const TPL = `
<style> <style>
.note-detail-read-only-code { .note-detail-read-only-code {
position: relative; position: relative;
min-height: 50px;
} }
.note-detail-read-only-code-content { .note-detail-read-only-code-content {

View File

@@ -14,22 +14,18 @@ const TPL = `
.note-detail-readonly-text h5 { font-size: 1.2em; } .note-detail-readonly-text h5 { font-size: 1.2em; }
.note-detail-readonly-text h6 { font-size: 1.1em; } .note-detail-readonly-text h6 { font-size: 1.1em; }
.note-detail-readonly-text h2 { font-size: 1.8em; } body.heading-style-markdown .note-detail-readonly-text h2::before { content: "##\\2004"; color: var(--muted-text-color); }
.note-detail-readonly-text h2::before { content: "##\\2004"; color: var(--muted-text-color); } body.heading-style-markdown .note-detail-readonly-text h3::before { content: "###\\2004"; color: var(--muted-text-color); }
.note-detail-readonly-text h3 { font-size: 1.6em; } body.heading-style-markdown .note-detail-readonly-text h4:not(.include-note-title)::before { content: "####\\2004"; color: var(--muted-text-color); }
.note-detail-readonly-text h3::before { content: "###\\2004"; color: var(--muted-text-color); } body.heading-style-markdown .note-detail-readonly-text h5::before { content: "#####\\2004"; color: var(--muted-text-color); }
.note-detail-readonly-text h4 { font-size: 1.4em; } body.heading-style-markdown .note-detail-readonly-text h6::before { content: "######\\2004"; color: var(--muted-text-color); }
.note-detail-readonly-text h4:not(.include-note-title)::before { content: "####\\2004"; color: var(--muted-text-color); }
.note-detail-readonly-text h5 { font-size: 1.2em; }
.note-detail-readonly-text h5::before { content: "#####\\2004"; color: var(--muted-text-color); }
.note-detail-readonly-text h6 { font-size: 1.1em; }
.note-detail-readonly-text h6::before { content: "######\\2004"; color: var(--muted-text-color); }
.note-detail-readonly-text { .note-detail-readonly-text {
padding-left: 22px; padding-left: 22px;
padding-top: 10px; padding-top: 10px;
font-family: var(--detail-text-font-family); font-family: var(--detail-text-font-family);
position: relative; position: relative;
min-height: 50px;
} }
.note-detail-readonly-text p:first-child, .note-detail-readonly-text::before { .note-detail-readonly-text p:first-child, .note-detail-readonly-text::before {

View File

@@ -106,7 +106,7 @@ function processContent(images, note, content) {
for (const {src, dataUrl, imageId} of images) { for (const {src, dataUrl, imageId} of images) {
const filename = path.basename(src); const filename = path.basename(src);
if (!dataUrl.startsWith("data:image")) { if (!dataUrl || !dataUrl.startsWith("data:image")) {
log.info("Image could not be recognized as data URL:", dataUrl.substr(0, Math.min(100, dataUrl.length))); log.info("Image could not be recognized as data URL:", dataUrl.substr(0, Math.min(100, dataUrl.length)));
continue; continue;
} }

View File

@@ -9,8 +9,27 @@ const cls = require('../../services/cls');
const repository = require('../../services/repository'); const repository = require('../../services/repository');
function getInboxNote(req) { function getInboxNote(req) {
return attributeService.getNoteWithLabel('inbox') const hoistedNote = getHoistedNote();
|| dateNoteService.getDateNote(req.params.date);
let inbox;
if (hoistedNote) {
([inbox] = hoistedNote.getDescendantNotesWithLabel('hoistedInbox'));
if (!inbox) {
([inbox] = hoistedNote.getDescendantNotesWithLabel('inbox'));
}
if (!inbox) {
inbox = hoistedNote;
}
}
else {
inbox = attributeService.getNoteWithLabel('inbox')
|| dateNoteService.getDateNote(req.params.date);
}
return inbox;
} }
function getDateNote(req) { function getDateNote(req) {
@@ -66,27 +85,18 @@ function createSearchNote(req) {
const searchString = params.searchString || ""; const searchString = params.searchString || "";
let ancestorNoteId = params.ancestorNoteId; let ancestorNoteId = params.ancestorNoteId;
const hoistedNote = cls.getHoistedNoteId() && cls.getHoistedNoteId() !== 'root' const hoistedNote = getHoistedNote();
? repository.getNote(cls.getHoistedNoteId())
: null;
let searchHome; let searchHome;
if (hoistedNote) { if (hoistedNote) {
([searchHome] = hoistedNote.getDescendantNotesWithLabel('hoistedSearchHome')); ([searchHome] = hoistedNote.getDescendantNotesWithLabel('hoistedSearchHome'));
}
if (!searchHome) { if (!searchHome) {
const today = dateUtils.localNowDate(); ([searchHome] = hoistedNote.getDescendantNotesWithLabel('searchHome'));
}
searchHome = attributeService.getNoteWithLabel('searchHome') if (!searchHome) {
|| dateNoteService.getDateNote(today);
}
if (hoistedNote) {
if (!hoistedNote.getDescendantNoteIds().includes(searchHome.noteId)) {
// otherwise the note would be saved outside of the hoisted context which is weird
searchHome = hoistedNote; searchHome = hoistedNote;
} }
@@ -94,6 +104,12 @@ function createSearchNote(req) {
ancestorNoteId = hoistedNote.noteId; ancestorNoteId = hoistedNote.noteId;
} }
} }
else {
const today = dateUtils.localNowDate();
searchHome = attributeService.getNoteWithLabel('searchHome')
|| dateNoteService.getDateNote(today);
}
const {note} = noteService.createNewNote({ const {note} = noteService.createNewNote({
parentNoteId: searchHome.noteId, parentNoteId: searchHome.noteId,
@@ -112,6 +128,12 @@ function createSearchNote(req) {
return note; return note;
} }
function getHoistedNote() {
return cls.getHoistedNoteId() && cls.getHoistedNoteId() !== 'root'
? repository.getNote(cls.getHoistedNoteId())
: null;
}
module.exports = { module.exports = {
getInboxNote, getInboxNote,
getDateNote, getDateNote,

View File

@@ -78,6 +78,12 @@ function loginToProtectedSession(req) {
}; };
} }
function logoutFromProtectedSession() {
protectedSessionService.resetDataKey();
eventService.emit(eventService.LEAVE_PROTECTED_SESSION);
}
function token(req) { function token(req) {
const username = req.body.username; const username = req.body.username;
const password = req.body.password; const password = req.body.password;
@@ -101,5 +107,6 @@ function token(req) {
module.exports = { module.exports = {
loginSync, loginSync,
loginToProtectedSession, loginToProtectedSession,
logoutFromProtectedSession,
token token
}; };

View File

@@ -5,6 +5,7 @@ const noteCacheService = require('../../services/note_cache/note_cache_service')
const protectedSessionService = require('../../services/protected_session'); const protectedSessionService = require('../../services/protected_session');
const noteRevisionService = require('../../services/note_revisions'); const noteRevisionService = require('../../services/note_revisions');
const utils = require('../../services/utils'); const utils = require('../../services/utils');
const sql = require('../../services/sql');
const path = require('path'); const path = require('path');
function getNoteRevisions(req) { function getNoteRevisions(req) {

View File

@@ -40,7 +40,9 @@ const ALLOWED_OPTIONS = new Set([
'nativeTitleBarVisible', 'nativeTitleBarVisible',
'attributeListExpanded', 'attributeListExpanded',
'promotedAttributesExpanded', 'promotedAttributesExpanded',
'similarNotesExpanded' 'similarNotesExpanded',
'headingStyle',
'autoCollapseNoteTree'
]); ]);
function getOptions() { function getOptions() {

View File

@@ -19,6 +19,7 @@ function index(req, res) {
res.render(view, { res.render(view, {
csrfToken: csrfToken, csrfToken: csrfToken,
theme: options.theme, theme: options.theme,
headingStyle: options.headingStyle,
mainFontSize: parseInt(options.mainFontSize), mainFontSize: parseInt(options.mainFontSize),
treeFontSize: parseInt(options.treeFontSize), treeFontSize: parseInt(options.treeFontSize),
detailFontSize: parseInt(options.detailFontSize), detailFontSize: parseInt(options.detailFontSize),

View File

@@ -270,6 +270,8 @@ function register(app) {
route(POST, '/api/login/sync', [], loginApiRoute.loginSync, apiResultHandler); route(POST, '/api/login/sync', [], loginApiRoute.loginSync, apiResultHandler);
// this is for entering protected mode so user has to be already logged-in (that's the reason we don't require username) // this is for entering protected mode so user has to be already logged-in (that's the reason we don't require username)
apiRoute(POST, '/api/login/protected', loginApiRoute.loginToProtectedSession); apiRoute(POST, '/api/login/protected', loginApiRoute.loginToProtectedSession);
apiRoute(POST, '/api/logout/protected', loginApiRoute.logoutFromProtectedSession);
route(POST, '/api/login/token', [], loginApiRoute.token, apiResultHandler); route(POST, '/api/login/token', [], loginApiRoute.token, apiResultHandler);
// in case of local electron, local calls are allowed unauthenticated, for server they need auth // in case of local electron, local calls are allowed unauthenticated, for server they need auth

View File

@@ -27,7 +27,6 @@ const BUILTIN_ATTRIBUTES = [
{ type: 'label', name: 'run', isDangerous: true }, { type: 'label', name: 'run', isDangerous: true },
{ type: 'label', name: 'customRequestHandler', isDangerous: true }, { type: 'label', name: 'customRequestHandler', isDangerous: true },
{ type: 'label', name: 'customResourceProvider', isDangerous: true }, { type: 'label', name: 'customResourceProvider', isDangerous: true },
{ type: 'label', name: 'bookZoomLevel', isDangerous: false },
{ type: 'label', name: 'widget', isDangerous: true }, { type: 'label', name: 'widget', isDangerous: true },
{ type: 'label', name: 'noteInfoWidgetDisabled' }, { type: 'label', name: 'noteInfoWidgetDisabled' },
{ type: 'label', name: 'linkMapWidgetDisabled' }, { type: 'label', name: 'linkMapWidgetDisabled' },
@@ -38,9 +37,12 @@ const BUILTIN_ATTRIBUTES = [
{ type: 'label', name: 'workspaceIconClass' }, { type: 'label', name: 'workspaceIconClass' },
{ type: 'label', name: 'workspaceTabBackgroundColor' }, { type: 'label', name: 'workspaceTabBackgroundColor' },
{ type: 'label', name: 'searchHome' }, { type: 'label', name: 'searchHome' },
{ type: 'label', name: 'hoistedInbox' },
{ type: 'label', name: 'hoistedSearchHome' }, { type: 'label', name: 'hoistedSearchHome' },
{ type: 'label', name: 'sqlConsoleHome' }, { type: 'label', name: 'sqlConsoleHome' },
{ type: 'label', name: 'datePattern' }, { type: 'label', name: 'datePattern' },
{ type: 'label', name: 'pageSize' },
{ type: 'label', name: 'viewType' },
// relation names // relation names
{ type: 'relation', name: 'runOnNoteCreation', isDangerous: true }, { type: 'relation', name: 'runOnNoteCreation', isDangerous: true },

View File

@@ -1 +1 @@
module.exports = { buildDate:"2021-03-08T23:11:11+01:00", buildRevision: "f27370d44f08afaa22d4cd86cba489584f9c878b" }; module.exports = { buildDate:"2021-04-03T22:37:04+02:00", buildRevision: "ef37a52a06b471e60f9c0f11da704283bbcef6ab" };

View File

@@ -44,10 +44,14 @@ function isEntityEventsDisabled() {
return !!namespace.get('disableEntityEvents'); return !!namespace.get('disableEntityEvents');
} }
function clearEntityChanges() {
namespace.set('entityChanges', []);
}
function getAndClearEntityChanges() { function getAndClearEntityChanges() {
const entityChanges = namespace.get('entityChanges') || []; const entityChanges = namespace.get('entityChanges') || [];
namespace.set('entityChanges', []); clearEntityChanges();
return entityChanges; return entityChanges;
} }
@@ -92,6 +96,7 @@ module.exports = {
disableEntityEvents, disableEntityEvents,
isEntityEventsDisabled, isEntityEventsDisabled,
reset, reset,
clearEntityChanges,
getAndClearEntityChanges, getAndClearEntityChanges,
addEntityChange, addEntityChange,
getEntityFromCache, getEntityFromCache,

View File

@@ -2,6 +2,7 @@ const log = require('./log');
const NOTE_TITLE_CHANGED = "NOTE_TITLE_CHANGED"; const NOTE_TITLE_CHANGED = "NOTE_TITLE_CHANGED";
const ENTER_PROTECTED_SESSION = "ENTER_PROTECTED_SESSION"; const ENTER_PROTECTED_SESSION = "ENTER_PROTECTED_SESSION";
const LEAVE_PROTECTED_SESSION = "LEAVE_PROTECTED_SESSION";
const ENTITY_CREATED = "ENTITY_CREATED"; const ENTITY_CREATED = "ENTITY_CREATED";
const ENTITY_CHANGED = "ENTITY_CHANGED"; const ENTITY_CHANGED = "ENTITY_CHANGED";
const ENTITY_DELETED = "ENTITY_DELETED"; const ENTITY_DELETED = "ENTITY_DELETED";
@@ -47,6 +48,7 @@ module.exports = {
// event types: // event types:
NOTE_TITLE_CHANGED, NOTE_TITLE_CHANGED,
ENTER_PROTECTED_SESSION, ENTER_PROTECTED_SESSION,
LEAVE_PROTECTED_SESSION,
ENTITY_CREATED, ENTITY_CREATED,
ENTITY_CHANGED, ENTITY_CHANGED,
ENTITY_DELETED, ENTITY_DELETED,

View File

@@ -61,7 +61,7 @@ eventService.subscribe(eventService.ENTITY_CREATED, ({ entityName, entity }) =>
const content = note.getContent(); const content = note.getContent();
if (!["text", "code"].includes(note.type) if (["text", "code"].includes(note.type)
// if the note has already content we're not going to overwrite it with template's one // if the note has already content we're not going to overwrite it with template's one
&& (!content || content.trim().length === 0) && (!content || content.trim().length === 0)
&& templateNote.isStringNote()) { && templateNote.isStringNote()) {

View File

@@ -1,6 +1,7 @@
"use strict"; "use strict";
const protectedSessionService = require('../../protected_session'); const protectedSessionService = require('../../protected_session');
const log = require('../../log');
class Note { class Note {
constructor(noteCache, row) { constructor(noteCache, row) {
@@ -405,7 +406,7 @@ class Note {
return 0; return 0;
} }
let minDistance = 999_999; let minDistance = 999999;
for (const parent of this.parents) { for (const parent of this.parents) {
minDistance = Math.min(minDistance, parent.getDistanceToAncestor(ancestorNoteId) + 1); minDistance = Math.min(minDistance, parent.getDistanceToAncestor(ancestorNoteId) + 1);
@@ -416,9 +417,14 @@ class Note {
decrypt() { decrypt() {
if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) { if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) {
this.title = protectedSessionService.decryptString(this.title); try {
this.title = protectedSessionService.decryptString(this.title);
this.isDecrypted = true; this.isDecrypted = true;
}
catch (e) {
log.error(`Could not decrypt note ${this.noteId}: ${e.message} ${e.stack}`);
}
} }
} }

View File

@@ -177,6 +177,10 @@ eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => {
} }
}); });
eventService.subscribe(eventService.LEAVE_PROTECTED_SESSION, () => {
load();
});
module.exports = { module.exports = {
load load
}; };

View File

@@ -23,7 +23,6 @@ const IGNORED_ATTR_NAMES = [
"archived", "archived",
"hidepromotedattributes", "hidepromotedattributes",
"keyboardshortcut", "keyboardshortcut",
"bookzoomlevel",
"noteinfowidgetdisabled", "noteinfowidgetdisabled",
"linkmapwidgetdisabled", "linkmapwidgetdisabled",
"noterevisionswidgetdisabled", "noterevisionswidgetdisabled",
@@ -234,7 +233,7 @@ async function findSimilarNotes(noteId) {
const baseNote = noteCache.notes[noteId]; const baseNote = noteCache.notes[noteId];
if (!baseNote) { if (!baseNote || !baseNote.utcDateCreated) {
return []; return [];
} }

View File

@@ -84,7 +84,9 @@ const defaultOptions = [
{ name: 'attributeListExpanded', value: 'false', isSynced: false }, { name: 'attributeListExpanded', value: 'false', isSynced: false },
{ name: 'promotedAttributesExpanded', value: 'true', isSynced: true }, { name: 'promotedAttributesExpanded', value: 'true', isSynced: true },
{ name: 'similarNotesExpanded', value: 'true', isSynced: true }, { name: 'similarNotesExpanded', value: 'true', isSynced: true },
{ name: 'debugModeEnabled', value: 'false', isSynced: false } { name: 'debugModeEnabled', value: 'false', isSynced: false },
{ name: 'headingStyle', value: 'markdown', isSynced: true },
{ name: 'autoCollapseNoteTree', value: 'true', isSynced: true },
]; ];
function initStartupOptions() { function initStartupOptions() {

View File

@@ -5,7 +5,7 @@ const log = require('./log');
const dataEncryptionService = require('./data_encryption'); const dataEncryptionService = require('./data_encryption');
const cls = require('./cls'); const cls = require('./cls');
const dataKeyMap = {}; let dataKeyMap = {};
function setDataKey(decryptedDataKey) { function setDataKey(decryptedDataKey) {
const protectedSessionId = utils.randomSecureToken(32); const protectedSessionId = utils.randomSecureToken(32);
@@ -29,6 +29,10 @@ function getDataKey() {
return dataKeyMap[protectedSessionId]; return dataKeyMap[protectedSessionId];
} }
function resetDataKey() {
dataKeyMap = {};
}
function isProtectedSessionAvailable() { function isProtectedSessionAvailable() {
const protectedSessionId = getProtectedSessionId(); const protectedSessionId = getProtectedSessionId();
@@ -71,6 +75,7 @@ function decryptString(cipherText) {
module.exports = { module.exports = {
setDataKey, setDataKey,
getDataKey, getDataKey,
resetDataKey,
isProtectedSessionAvailable, isProtectedSessionAvailable,
encrypt, encrypt,
decrypt, decrypt,

View File

@@ -84,7 +84,15 @@ function exec(opts) {
}); });
}); });
request.end(opts.body); let payload;
if (opts.body) {
payload = typeof opts.body === 'object'
? JSON.stringify(opts.body)
: opts.body;
}
request.end(payload);
} }
catch (e) { catch (e) {
reject(generateError(opts, e.message)); reject(generateError(opts, e.message));

View File

@@ -3,6 +3,7 @@
const log = require('./log'); const log = require('./log');
const Database = require('better-sqlite3'); const Database = require('better-sqlite3');
const dataDir = require('./data_dir'); const dataDir = require('./data_dir');
const cls = require('./cls');
const dbConnection = new Database(dataDir.DOCUMENT_PATH); const dbConnection = new Database(dataDir.DOCUMENT_PATH);
dbConnection.pragma('journal_mode = WAL'); dbConnection.pragma('journal_mode = WAL');
@@ -229,13 +230,20 @@ function wrap(query, func) {
} }
function transactional(func) { function transactional(func) {
const ret = dbConnection.transaction(func).deferred(); try {
const ret = dbConnection.transaction(func).deferred();
if (!dbConnection.inTransaction) { // i.e. transaction was really committed (and not just savepoint released) if (!dbConnection.inTransaction) { // i.e. transaction was really committed (and not just savepoint released)
require('./ws.js').sendTransactionSyncsToAllClients(); require('./ws.js').sendTransactionSyncsToAllClients();
}
return ret;
} }
catch (e) {
cls.clearEntityChanges();
return ret; throw e;
}
} }
function fillNoteIdList(noteIds, truncate = true) { function fillNoteIdList(noteIds, truncate = true) {

View File

@@ -26,9 +26,7 @@ function updateEntity(entityChange, entity, sourceId) {
? updateNoteReordering(entityChange, entity, sourceId) ? updateNoteReordering(entityChange, entity, sourceId)
: updateNormalEntity(entityChange, entity, sourceId); : updateNormalEntity(entityChange, entity, sourceId);
// currently making exception for protected notes and note revisions because here if (updated && !entityChange.isErased) {
// the title and content are not available decrypted as listeners would expect
if (updated && !entity.isProtected && !entityChange.isErased) {
eventService.emit(eventService.ENTITY_SYNCED, { eventService.emit(eventService.ENTITY_SYNCED, {
entityName: entityChange.entityName, entityName: entityChange.entityName,
entity entity
@@ -44,7 +42,7 @@ function updateNormalEntity(remoteEntityChange, entity, sourceId) {
if (localEntityChange && !localEntityChange.isErased && remoteEntityChange.isErased) { if (localEntityChange && !localEntityChange.isErased && remoteEntityChange.isErased) {
sql.transactional(() => { sql.transactional(() => {
const primaryKey = entityConstructor.getEntityFromEntityName(entityName).primaryKeyName; const primaryKey = entityConstructor.getEntityFromEntityName(remoteEntityChange.entityName).primaryKeyName;
sql.execute(`DELETE FROM ${remoteEntityChange.entityName} WHERE ${primaryKey} = ?`, remoteEntityChange.entityId); sql.execute(`DELETE FROM ${remoteEntityChange.entityName} WHERE ${primaryKey} = ?`, remoteEntityChange.entityId);

View File

@@ -1,6 +1,7 @@
"use strict"; "use strict";
const sql = require('./sql'); const sql = require('./sql');
const log = require('./log');
const repository = require('./repository'); const repository = require('./repository');
const Branch = require('../entities/branch'); const Branch = require('../entities/branch');
const entityChangesService = require('./entity_changes.js'); const entityChangesService = require('./entity_changes.js');
@@ -139,7 +140,12 @@ function sortNotesByTitle(parentNoteId, foldersFirst = false, reverse = false) {
sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?",
[position, note.branchId]); [position, note.branchId]);
noteCache.branches[note.branchId].notePosition = position; if (note.branchId in noteCache.branches) {
noteCache.branches[note.branchId].notePosition = position;
}
else {
log.info(`Branch "${note.branchId}" was not found in note cache.`);
}
position += 10; position += 10;
} }

View File

@@ -106,8 +106,6 @@ function sendPing(client, entityChanges = []) {
} }
} }
const stats = require('./sync').stats;
sendMessage(client, { sendMessage(client, {
type: 'sync', type: 'sync',
data: entityChanges data: entityChanges
@@ -118,9 +116,7 @@ function sendTransactionSyncsToAllClients() {
if (webSocketServer) { if (webSocketServer) {
const entityChanges = cls.getAndClearEntityChanges(); const entityChanges = cls.getAndClearEntityChanges();
webSocketServer.clients.forEach(function each(client) { webSocketServer.clients.forEach(client => sendPing(client, entityChanges));
sendPing(client, entityChanges);
});
} }
} }

View File

@@ -5,7 +5,7 @@
<link rel="shortcut icon" href="favicon.ico"> <link rel="shortcut icon" href="favicon.ico">
<title>Trilium Notes</title> <title>Trilium Notes</title>
</head> </head>
<body class="desktop theme-<%= theme %>" style="--main-font-size: <%= mainFontSize %>%; --tree-font-size: <%= treeFontSize %>%; --detail-font-size: <%= detailFontSize %>%;"> <body class="desktop theme-<%= theme %> heading-style-<%= headingStyle %>" style="--main-font-size: <%= mainFontSize %>%; --tree-font-size: <%= treeFontSize %>%; --detail-font-size: <%= detailFontSize %>%;">
<noscript>Trilium requires JavaScript to be enabled.</noscript> <noscript>Trilium requires JavaScript to be enabled.</noscript>
<script> <script>

View File

@@ -95,7 +95,7 @@
} }
</style> </style>
</head> </head>
<body class="mobile theme-<%= theme %>"> <body class="mobile theme-<%= theme %> heading-style-<%= headingStyle %>">
<noscript>Trilium requires JavaScript to be enabled.</noscript> <noscript>Trilium requires JavaScript to be enabled.</noscript>
<div id="toast-container" class="d-flex flex-column justify-content-center align-items-center"></div> <div id="toast-container" class="d-flex flex-column justify-content-center align-items-center"></div>