mirror of
https://github.com/zadam/trilium.git
synced 2025-10-29 17:26:38 +01:00
Compare commits
34 Commits
v0.46.3-be
...
v0.46.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7672f22ce0 | ||
|
|
ef37a52a06 | ||
|
|
2318d615bb | ||
|
|
bc14c3d665 | ||
|
|
b89ea9a684 | ||
|
|
496767a52b | ||
|
|
9139c597e5 | ||
|
|
942132c01d | ||
|
|
1862acd1ff | ||
|
|
ce7e18d0b0 | ||
|
|
65280d5ba3 | ||
|
|
7e3d424e23 | ||
|
|
bff04c121a | ||
|
|
e8903e82a1 | ||
|
|
0cfd95d9b8 | ||
|
|
1aa5349628 | ||
|
|
4e21d12202 | ||
|
|
fdce218e88 | ||
|
|
6c8d20288d | ||
|
|
88d04772c4 | ||
|
|
9fd26a9b9f | ||
|
|
98f02c3c9a | ||
|
|
584fea1992 | ||
|
|
af50a1ec52 | ||
|
|
4e76d1fa85 | ||
|
|
03a11e6f77 | ||
|
|
e1a16b4a9f | ||
|
|
12b468d3dc | ||
|
|
6f901e6852 | ||
|
|
09e9ac4d00 | ||
|
|
a33ac65fdf | ||
|
|
f8fb071a6f | ||
|
|
a654078e56 | ||
|
|
fba68681aa |
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "trilium",
|
||||
"version": "0.46.2-beta",
|
||||
"version": "0.46.5",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.46.3-beta",
|
||||
"version": "0.46.7",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"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-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",
|
||||
|
||||
@@ -49,7 +49,12 @@ class Note extends Entity {
|
||||
this.isContentAvailable = protectedSessionService.isProtectedSessionAvailable();
|
||||
|
||||
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 {
|
||||
this.title = "[protected]";
|
||||
@@ -156,14 +161,14 @@ class Note extends Entity {
|
||||
|
||||
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({
|
||||
entityName: 'note_contents',
|
||||
entityId: this.noteId,
|
||||
hash: hash,
|
||||
isErased: false,
|
||||
utcDateChanged: this.getUtcDateChanged()
|
||||
utcDateChanged: pojo.utcDateModified
|
||||
}, null);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,16 @@ const TPL = `
|
||||
</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>
|
||||
|
||||
<h4>Font sizes</h4>
|
||||
@@ -78,6 +88,7 @@ export default class ApperanceOptions {
|
||||
this.$themeSelect = $("#theme-select");
|
||||
this.$zoomFactorSelect = $("#zoom-factor-select");
|
||||
this.$nativeTitleBarSelect = $("#native-title-bar-select");
|
||||
this.$headingStyle = $("#heading-style");
|
||||
this.$mainFontSize = $("#main-font-size");
|
||||
this.$treeFontSize = $("#tree-font-size");
|
||||
this.$detailFontSize = $("#detail-font-size");
|
||||
@@ -86,11 +97,7 @@ export default class ApperanceOptions {
|
||||
this.$themeSelect.on('change', () => {
|
||||
const newTheme = this.$themeSelect.val();
|
||||
|
||||
for (const clazz of Array.from(this.$body[0].classList)) { // create copy to safely iterate over while removing classes
|
||||
if (clazz.startsWith("theme-")) {
|
||||
this.$body.removeClass(clazz);
|
||||
}
|
||||
}
|
||||
this.toggleBodyClass("theme-", newTheme);
|
||||
|
||||
const noteId = $(this).find(":selected").attr("data-note-id");
|
||||
|
||||
@@ -100,8 +107,6 @@ export default class ApperanceOptions {
|
||||
libraryLoader.requireCss(`api/notes/download/${noteId}`);
|
||||
}
|
||||
|
||||
this.$body.addClass("theme-" + newTheme);
|
||||
|
||||
server.put('options/theme/' + newTheme);
|
||||
});
|
||||
|
||||
@@ -113,6 +118,14 @@ export default class ApperanceOptions {
|
||||
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 () => {
|
||||
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) {
|
||||
const themes = [
|
||||
{ val: 'white', title: 'White' },
|
||||
@@ -159,6 +182,8 @@ export default class ApperanceOptions {
|
||||
|
||||
this.$nativeTitleBarSelect.val(options.nativeTitleBarVisible === 'true' ? 'show' : 'hide');
|
||||
|
||||
this.$headingStyle.val(options.headingStyle);
|
||||
|
||||
this.$mainFontSize.val(options.mainFontSize);
|
||||
this.$treeFontSize.val(options.treeFontSize);
|
||||
this.$detailFontSize.val(options.detailFontSize);
|
||||
|
||||
@@ -254,22 +254,39 @@ class NoteShort {
|
||||
return noteAttributeCache.attributes[this.noteId];
|
||||
}
|
||||
|
||||
getAllNotePaths() {
|
||||
getAllNotePaths(encounteredNoteIds = null) {
|
||||
if (this.noteId === 'root') {
|
||||
return [['root']];
|
||||
}
|
||||
|
||||
if (!encounteredNoteIds) {
|
||||
encounteredNoteIds = new Set();
|
||||
}
|
||||
|
||||
encounteredNoteIds.add(this.noteId);
|
||||
|
||||
const parentNotes = this.getParentNotes();
|
||||
let paths;
|
||||
|
||||
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 {
|
||||
paths = [];
|
||||
|
||||
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();
|
||||
|
||||
if (iconClassLabels.length > 0) {
|
||||
return iconClassLabels.map(l => l.value).join(' ');
|
||||
return iconClassLabels[0].value;
|
||||
}
|
||||
else if (workspaceIconClass) {
|
||||
return workspaceIconClass;
|
||||
|
||||
@@ -83,6 +83,9 @@ export default class MobileLayout {
|
||||
.child(new NoteTitleWidget())
|
||||
.child(new CloseDetailButtonWidget()))
|
||||
.child(new NoteDetailWidget()
|
||||
.css('padding', '5px 20px 10px 0')));
|
||||
.css('padding', '5px 20px 10px 0')
|
||||
.css('overflow', 'auto')
|
||||
.css('height', '100%')
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,15 @@ async function renderAttributes(attributes, renderIsInheritable) {
|
||||
return $container;
|
||||
}
|
||||
|
||||
const HIDDEN_ATTRIBUTES = [
|
||||
'originalFileName',
|
||||
'template',
|
||||
'cssClass',
|
||||
'iconClass',
|
||||
'pageSize',
|
||||
'viewType'
|
||||
];
|
||||
|
||||
async function renderNormalAttributes(note) {
|
||||
const promotedDefinitionAttributes = note.getPromotedDefinitionAttributes();
|
||||
let attrs = note.getAttributes();
|
||||
@@ -90,6 +99,7 @@ async function renderNormalAttributes(note) {
|
||||
attrs = attrs.filter(
|
||||
attr => !attr.isDefinition()
|
||||
&& !attr.isAutoLink
|
||||
&& !HIDDEN_ATTRIBUTES.includes(attr.name)
|
||||
&& attr.noteId === note.noteId
|
||||
);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,11 @@ export default class Entrypoints extends Component {
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
@@ -26,9 +26,7 @@ function isHoistedNode(node) {
|
||||
}
|
||||
|
||||
async function checkNoteAccess(notePath, tabContext) {
|
||||
// notePath argument can contain only noteId which is not good when hoisted since
|
||||
// then we need to check the whole note path
|
||||
const resolvedNotePath = await treeService.resolveNotePath(notePath);
|
||||
const resolvedNotePath = await treeService.resolveNotePath(notePath, tabContext.hoistedNoteId);
|
||||
|
||||
if (!resolvedNotePath) {
|
||||
console.log("Cannot activate " + notePath);
|
||||
@@ -37,7 +35,7 @@ async function checkNoteAccess(notePath, tabContext) {
|
||||
|
||||
const hoistedNoteId = tabContext.hoistedNoteId;
|
||||
|
||||
if (hoistedNoteId !== 'root' && !resolvedNotePath.includes(hoistedNoteId)) {
|
||||
if (!resolvedNotePath.includes(hoistedNoteId)) {
|
||||
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?")) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import utils from "./utils.js";
|
||||
import options from './options.js';
|
||||
import server from "./server.js";
|
||||
|
||||
const PROTECTED_SESSION_ID_KEY = 'protectedSessionId';
|
||||
|
||||
@@ -23,11 +24,11 @@ function resetSessionCookie() {
|
||||
utils.setSessionCookie(PROTECTED_SESSION_ID_KEY, null);
|
||||
}
|
||||
|
||||
function resetProtectedSession() {
|
||||
async function resetProtectedSession() {
|
||||
resetSessionCookie();
|
||||
|
||||
// most secure solution - guarantees nothing remained in memory
|
||||
// since this expires because user doesn't use the app, it shouldn't be disruptive
|
||||
await server.post("logout/protected");
|
||||
|
||||
utils.reloadApp();
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr
|
||||
path.push('root');
|
||||
}
|
||||
|
||||
const effectivePath = [];
|
||||
const effectivePathSegments = [];
|
||||
let childNoteId = null;
|
||||
let i = 0;
|
||||
|
||||
@@ -81,7 +81,7 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr
|
||||
const pathToRoot = someNotePath.split("/").reverse().slice(1);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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') {
|
||||
|
||||
@@ -194,9 +194,18 @@ const ATTR_HELP = {
|
||||
"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.",
|
||||
"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>',
|
||||
"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": {
|
||||
"runOnNoteCreation": "executes when note is created on backend",
|
||||
|
||||
@@ -278,7 +278,7 @@ export default class NoteDetailWidget extends TabAwareWidget {
|
||||
|
||||
const label = attrs.find(attr =>
|
||||
attr.type === 'label'
|
||||
&& ['readOnly', 'autoReadOnlyDisabled', 'cssClass', 'bookZoomLevel', 'displayRelations'].includes(attr.name)
|
||||
&& ['readOnly', 'autoReadOnlyDisabled', 'cssClass', 'displayRelations'].includes(attr.name)
|
||||
&& attr.isAffecting(this.note));
|
||||
|
||||
const relation = attrs.find(attr =>
|
||||
|
||||
@@ -88,7 +88,7 @@ export default class NotePathsWidget extends TabAwareWidget {
|
||||
.find('a')
|
||||
.addClass("no-tooltip-preview");
|
||||
|
||||
const comments = [];
|
||||
const icons = [];
|
||||
|
||||
if (this.notePath === notePath) {
|
||||
$noteLink.addClass("path-current");
|
||||
@@ -98,23 +98,23 @@ export default class NotePathsWidget extends TabAwareWidget {
|
||||
$noteLink.addClass("path-in-hoisted-subtree");
|
||||
}
|
||||
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) {
|
||||
$noteLink.addClass("path-archived");
|
||||
|
||||
comments.push("archived");
|
||||
icons.push(`<span class="bx bx-archive" title="Archived"></span>`);
|
||||
}
|
||||
|
||||
if (notePathRecord.isSearch) {
|
||||
$noteLink.addClass("path-search");
|
||||
|
||||
comments.push("search");
|
||||
icons.push(`<span class="bx bx-search" title="Search"></span>`);
|
||||
}
|
||||
|
||||
if (comments.length > 0) {
|
||||
$noteLink.append(` (${comments.join(', ')})`);
|
||||
if (icons.length > 0) {
|
||||
$noteLink.append(` ${icons.join(' ')}`);
|
||||
}
|
||||
|
||||
this.$notePathList.append($noteLink);
|
||||
|
||||
@@ -36,7 +36,7 @@ export default class NoteTitleWidget extends TabAwareWidget {
|
||||
|
||||
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);
|
||||
|
||||
@@ -176,6 +176,15 @@ const TPL = `
|
||||
title="Images which are shown in the parent text note will not be displayed in the tree"></span>
|
||||
</label>
|
||||
</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/>
|
||||
|
||||
@@ -235,6 +244,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
this.$treeSettingsPopup = this.$widget.find('.tree-settings-popup');
|
||||
this.$hideArchivedNotesCheckbox = this.$treeSettingsPopup.find('.hide-archived-notes');
|
||||
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.on("click", e => {
|
||||
@@ -245,6 +255,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
|
||||
this.$hideArchivedNotesCheckbox.prop("checked", this.hideArchivedNotes);
|
||||
this.$hideIncludedImages.prop("checked", this.hideIncludedImages);
|
||||
this.$autoCollapseNoteTree.prop("checked", this.autoCollapseNoteTree);
|
||||
|
||||
let top = this.$treeSettingsButton[0].offsetTop;
|
||||
let left = this.$treeSettingsButton[0].offsetLeft;
|
||||
@@ -272,6 +283,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
this.$saveTreeSettingsButton.on('click', async () => {
|
||||
await this.setHideArchivedNotes(this.$hideArchivedNotesCheckbox.prop("checked"));
|
||||
await this.setHideIncludedImages(this.$hideIncludedImages.prop("checked"));
|
||||
await this.setAutoCollapseNoteTree(this.$autoCollapseNoteTree.prop("checked"));
|
||||
|
||||
this.$treeSettingsPopup.hide();
|
||||
|
||||
@@ -327,6 +339,14 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
await options.save("hideIncludedImages_" + this.treeName, val.toString());
|
||||
}
|
||||
|
||||
get autoCollapseNoteTree() {
|
||||
return options.is("autoCollapseNoteTree");
|
||||
}
|
||||
|
||||
async setAutoCollapseNoteTree(val) {
|
||||
await options.save("autoCollapseNoteTree", val.toString());
|
||||
}
|
||||
|
||||
initFancyTree() {
|
||||
const treeData = [this.prepareRootNode()];
|
||||
|
||||
@@ -797,8 +817,10 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
|
||||
const node = await this.expandToNote(activeContext.notePath);
|
||||
|
||||
await node.makeVisible({scrollIntoView: true});
|
||||
node.setActive(true, {noEvents: true, noFocus: false});
|
||||
if (node) {
|
||||
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
|
||||
// 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;
|
||||
@@ -955,6 +981,10 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
}
|
||||
|
||||
this.autoCollapseTimeoutId = setTimeout(() => {
|
||||
if (!this.autoCollapseNoteTree) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* We're collapsing notes after period of inactivity to "cleanup" the tree - users rarely
|
||||
* 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 activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null;
|
||||
|
||||
console.log(activeNotePath, activeNodeFocused);
|
||||
|
||||
const nextNotePath = nextNode ? treeService.getNotePath(nextNode) : null;
|
||||
const activeNoteId = activeNode ? activeNode.data.noteId : null;
|
||||
|
||||
@@ -1117,6 +1145,10 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
|
||||
if (node) {
|
||||
node.setActive(true, {noEvents: true, noFocus: !activeNodeFocused});
|
||||
|
||||
if (activeNodeFocused) {
|
||||
node.setFocus(true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// this is used when original note has been deleted and we want to move the focus to the note above/below
|
||||
|
||||
@@ -14,6 +14,10 @@ const TPL = `
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.standard-top-widget > div {
|
||||
flex-shrink: 0; /* fixes https://github.com/zadam/trilium/issues/1745 */
|
||||
}
|
||||
|
||||
.standard-top-widget button.noborder {
|
||||
padding: 1px 5px 1px 5px;
|
||||
font-size: 90%;
|
||||
|
||||
@@ -49,15 +49,16 @@ const TPL = `
|
||||
}
|
||||
|
||||
.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::before { content: "###\\2004"; color: var(--muted-text-color); }
|
||||
.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::before { content: "#####\\2004"; color: var(--muted-text-color); }
|
||||
.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 {
|
||||
padding-top: 10px;
|
||||
|
||||
@@ -5,6 +5,7 @@ const TPL = `
|
||||
<style>
|
||||
.note-detail-read-only-code {
|
||||
position: relative;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.note-detail-read-only-code-content {
|
||||
|
||||
@@ -14,22 +14,18 @@ const TPL = `
|
||||
.note-detail-readonly-text h5 { font-size: 1.2em; }
|
||||
.note-detail-readonly-text h6 { font-size: 1.1em; }
|
||||
|
||||
.note-detail-readonly-text h2 { font-size: 1.8em; }
|
||||
.note-detail-readonly-text h2::before { content: "##\\2004"; color: var(--muted-text-color); }
|
||||
.note-detail-readonly-text h3 { font-size: 1.6em; }
|
||||
.note-detail-readonly-text h3::before { content: "###\\2004"; color: var(--muted-text-color); }
|
||||
.note-detail-readonly-text h4 { font-size: 1.4em; }
|
||||
.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); }
|
||||
body.heading-style-markdown .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); }
|
||||
body.heading-style-markdown .note-detail-readonly-text h4:not(.include-note-title)::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); }
|
||||
body.heading-style-markdown .note-detail-readonly-text h6::before { content: "######\\2004"; color: var(--muted-text-color); }
|
||||
|
||||
.note-detail-readonly-text {
|
||||
padding-left: 22px;
|
||||
padding-top: 10px;
|
||||
font-family: var(--detail-text-font-family);
|
||||
position: relative;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.note-detail-readonly-text p:first-child, .note-detail-readonly-text::before {
|
||||
|
||||
@@ -106,7 +106,7 @@ function processContent(images, note, content) {
|
||||
for (const {src, dataUrl, imageId} of images) {
|
||||
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)));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,27 @@ const cls = require('../../services/cls');
|
||||
const repository = require('../../services/repository');
|
||||
|
||||
function getInboxNote(req) {
|
||||
return attributeService.getNoteWithLabel('inbox')
|
||||
|| dateNoteService.getDateNote(req.params.date);
|
||||
const hoistedNote = getHoistedNote();
|
||||
|
||||
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) {
|
||||
@@ -66,27 +85,18 @@ function createSearchNote(req) {
|
||||
const searchString = params.searchString || "";
|
||||
let ancestorNoteId = params.ancestorNoteId;
|
||||
|
||||
const hoistedNote = cls.getHoistedNoteId() && cls.getHoistedNoteId() !== 'root'
|
||||
? repository.getNote(cls.getHoistedNoteId())
|
||||
: null;
|
||||
const hoistedNote = getHoistedNote();
|
||||
|
||||
let searchHome;
|
||||
|
||||
if (hoistedNote) {
|
||||
([searchHome] = hoistedNote.getDescendantNotesWithLabel('hoistedSearchHome'));
|
||||
}
|
||||
|
||||
if (!searchHome) {
|
||||
const today = dateUtils.localNowDate();
|
||||
if (!searchHome) {
|
||||
([searchHome] = hoistedNote.getDescendantNotesWithLabel('searchHome'));
|
||||
}
|
||||
|
||||
searchHome = attributeService.getNoteWithLabel('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
|
||||
if (!searchHome) {
|
||||
searchHome = hoistedNote;
|
||||
}
|
||||
|
||||
@@ -94,6 +104,12 @@ function createSearchNote(req) {
|
||||
ancestorNoteId = hoistedNote.noteId;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const today = dateUtils.localNowDate();
|
||||
|
||||
searchHome = attributeService.getNoteWithLabel('searchHome')
|
||||
|| dateNoteService.getDateNote(today);
|
||||
}
|
||||
|
||||
const {note} = noteService.createNewNote({
|
||||
parentNoteId: searchHome.noteId,
|
||||
@@ -112,6 +128,12 @@ function createSearchNote(req) {
|
||||
return note;
|
||||
}
|
||||
|
||||
function getHoistedNote() {
|
||||
return cls.getHoistedNoteId() && cls.getHoistedNoteId() !== 'root'
|
||||
? repository.getNote(cls.getHoistedNoteId())
|
||||
: null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getInboxNote,
|
||||
getDateNote,
|
||||
|
||||
@@ -78,6 +78,12 @@ function loginToProtectedSession(req) {
|
||||
};
|
||||
}
|
||||
|
||||
function logoutFromProtectedSession() {
|
||||
protectedSessionService.resetDataKey();
|
||||
|
||||
eventService.emit(eventService.LEAVE_PROTECTED_SESSION);
|
||||
}
|
||||
|
||||
function token(req) {
|
||||
const username = req.body.username;
|
||||
const password = req.body.password;
|
||||
@@ -101,5 +107,6 @@ function token(req) {
|
||||
module.exports = {
|
||||
loginSync,
|
||||
loginToProtectedSession,
|
||||
logoutFromProtectedSession,
|
||||
token
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ const noteCacheService = require('../../services/note_cache/note_cache_service')
|
||||
const protectedSessionService = require('../../services/protected_session');
|
||||
const noteRevisionService = require('../../services/note_revisions');
|
||||
const utils = require('../../services/utils');
|
||||
const sql = require('../../services/sql');
|
||||
const path = require('path');
|
||||
|
||||
function getNoteRevisions(req) {
|
||||
|
||||
@@ -40,7 +40,9 @@ const ALLOWED_OPTIONS = new Set([
|
||||
'nativeTitleBarVisible',
|
||||
'attributeListExpanded',
|
||||
'promotedAttributesExpanded',
|
||||
'similarNotesExpanded'
|
||||
'similarNotesExpanded',
|
||||
'headingStyle',
|
||||
'autoCollapseNoteTree'
|
||||
]);
|
||||
|
||||
function getOptions() {
|
||||
|
||||
@@ -19,6 +19,7 @@ function index(req, res) {
|
||||
res.render(view, {
|
||||
csrfToken: csrfToken,
|
||||
theme: options.theme,
|
||||
headingStyle: options.headingStyle,
|
||||
mainFontSize: parseInt(options.mainFontSize),
|
||||
treeFontSize: parseInt(options.treeFontSize),
|
||||
detailFontSize: parseInt(options.detailFontSize),
|
||||
|
||||
@@ -270,6 +270,8 @@ function register(app) {
|
||||
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)
|
||||
apiRoute(POST, '/api/login/protected', loginApiRoute.loginToProtectedSession);
|
||||
apiRoute(POST, '/api/logout/protected', loginApiRoute.logoutFromProtectedSession);
|
||||
|
||||
route(POST, '/api/login/token', [], loginApiRoute.token, apiResultHandler);
|
||||
|
||||
// in case of local electron, local calls are allowed unauthenticated, for server they need auth
|
||||
|
||||
@@ -27,7 +27,6 @@ const BUILTIN_ATTRIBUTES = [
|
||||
{ type: 'label', name: 'run', isDangerous: true },
|
||||
{ type: 'label', name: 'customRequestHandler', isDangerous: true },
|
||||
{ type: 'label', name: 'customResourceProvider', isDangerous: true },
|
||||
{ type: 'label', name: 'bookZoomLevel', isDangerous: false },
|
||||
{ type: 'label', name: 'widget', isDangerous: true },
|
||||
{ type: 'label', name: 'noteInfoWidgetDisabled' },
|
||||
{ type: 'label', name: 'linkMapWidgetDisabled' },
|
||||
@@ -38,9 +37,12 @@ const BUILTIN_ATTRIBUTES = [
|
||||
{ type: 'label', name: 'workspaceIconClass' },
|
||||
{ type: 'label', name: 'workspaceTabBackgroundColor' },
|
||||
{ type: 'label', name: 'searchHome' },
|
||||
{ type: 'label', name: 'hoistedInbox' },
|
||||
{ type: 'label', name: 'hoistedSearchHome' },
|
||||
{ type: 'label', name: 'sqlConsoleHome' },
|
||||
{ type: 'label', name: 'datePattern' },
|
||||
{ type: 'label', name: 'pageSize' },
|
||||
{ type: 'label', name: 'viewType' },
|
||||
|
||||
// relation names
|
||||
{ type: 'relation', name: 'runOnNoteCreation', isDangerous: true },
|
||||
|
||||
@@ -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" };
|
||||
|
||||
@@ -44,10 +44,14 @@ function isEntityEventsDisabled() {
|
||||
return !!namespace.get('disableEntityEvents');
|
||||
}
|
||||
|
||||
function clearEntityChanges() {
|
||||
namespace.set('entityChanges', []);
|
||||
}
|
||||
|
||||
function getAndClearEntityChanges() {
|
||||
const entityChanges = namespace.get('entityChanges') || [];
|
||||
|
||||
namespace.set('entityChanges', []);
|
||||
clearEntityChanges();
|
||||
|
||||
return entityChanges;
|
||||
}
|
||||
@@ -92,6 +96,7 @@ module.exports = {
|
||||
disableEntityEvents,
|
||||
isEntityEventsDisabled,
|
||||
reset,
|
||||
clearEntityChanges,
|
||||
getAndClearEntityChanges,
|
||||
addEntityChange,
|
||||
getEntityFromCache,
|
||||
|
||||
@@ -2,6 +2,7 @@ const log = require('./log');
|
||||
|
||||
const NOTE_TITLE_CHANGED = "NOTE_TITLE_CHANGED";
|
||||
const ENTER_PROTECTED_SESSION = "ENTER_PROTECTED_SESSION";
|
||||
const LEAVE_PROTECTED_SESSION = "LEAVE_PROTECTED_SESSION";
|
||||
const ENTITY_CREATED = "ENTITY_CREATED";
|
||||
const ENTITY_CHANGED = "ENTITY_CHANGED";
|
||||
const ENTITY_DELETED = "ENTITY_DELETED";
|
||||
@@ -47,6 +48,7 @@ module.exports = {
|
||||
// event types:
|
||||
NOTE_TITLE_CHANGED,
|
||||
ENTER_PROTECTED_SESSION,
|
||||
LEAVE_PROTECTED_SESSION,
|
||||
ENTITY_CREATED,
|
||||
ENTITY_CHANGED,
|
||||
ENTITY_DELETED,
|
||||
|
||||
@@ -61,7 +61,7 @@ eventService.subscribe(eventService.ENTITY_CREATED, ({ entityName, entity }) =>
|
||||
|
||||
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
|
||||
&& (!content || content.trim().length === 0)
|
||||
&& templateNote.isStringNote()) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
const protectedSessionService = require('../../protected_session');
|
||||
const log = require('../../log');
|
||||
|
||||
class Note {
|
||||
constructor(noteCache, row) {
|
||||
@@ -405,7 +406,7 @@ class Note {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let minDistance = 999_999;
|
||||
let minDistance = 999999;
|
||||
|
||||
for (const parent of this.parents) {
|
||||
minDistance = Math.min(minDistance, parent.getDistanceToAncestor(ancestorNoteId) + 1);
|
||||
@@ -416,9 +417,14 @@ class Note {
|
||||
|
||||
decrypt() {
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -177,6 +177,10 @@ eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => {
|
||||
}
|
||||
});
|
||||
|
||||
eventService.subscribe(eventService.LEAVE_PROTECTED_SESSION, () => {
|
||||
load();
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
load
|
||||
};
|
||||
|
||||
@@ -23,7 +23,6 @@ const IGNORED_ATTR_NAMES = [
|
||||
"archived",
|
||||
"hidepromotedattributes",
|
||||
"keyboardshortcut",
|
||||
"bookzoomlevel",
|
||||
"noteinfowidgetdisabled",
|
||||
"linkmapwidgetdisabled",
|
||||
"noterevisionswidgetdisabled",
|
||||
@@ -234,7 +233,7 @@ async function findSimilarNotes(noteId) {
|
||||
|
||||
const baseNote = noteCache.notes[noteId];
|
||||
|
||||
if (!baseNote) {
|
||||
if (!baseNote || !baseNote.utcDateCreated) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,9 @@ const defaultOptions = [
|
||||
{ name: 'attributeListExpanded', value: 'false', isSynced: false },
|
||||
{ name: 'promotedAttributesExpanded', 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() {
|
||||
|
||||
@@ -5,7 +5,7 @@ const log = require('./log');
|
||||
const dataEncryptionService = require('./data_encryption');
|
||||
const cls = require('./cls');
|
||||
|
||||
const dataKeyMap = {};
|
||||
let dataKeyMap = {};
|
||||
|
||||
function setDataKey(decryptedDataKey) {
|
||||
const protectedSessionId = utils.randomSecureToken(32);
|
||||
@@ -29,6 +29,10 @@ function getDataKey() {
|
||||
return dataKeyMap[protectedSessionId];
|
||||
}
|
||||
|
||||
function resetDataKey() {
|
||||
dataKeyMap = {};
|
||||
}
|
||||
|
||||
function isProtectedSessionAvailable() {
|
||||
const protectedSessionId = getProtectedSessionId();
|
||||
|
||||
@@ -71,6 +75,7 @@ function decryptString(cipherText) {
|
||||
module.exports = {
|
||||
setDataKey,
|
||||
getDataKey,
|
||||
resetDataKey,
|
||||
isProtectedSessionAvailable,
|
||||
encrypt,
|
||||
decrypt,
|
||||
|
||||
@@ -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) {
|
||||
reject(generateError(opts, e.message));
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
const log = require('./log');
|
||||
const Database = require('better-sqlite3');
|
||||
const dataDir = require('./data_dir');
|
||||
const cls = require('./cls');
|
||||
|
||||
const dbConnection = new Database(dataDir.DOCUMENT_PATH);
|
||||
dbConnection.pragma('journal_mode = WAL');
|
||||
@@ -229,13 +230,20 @@ function wrap(query, 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)
|
||||
require('./ws.js').sendTransactionSyncsToAllClients();
|
||||
if (!dbConnection.inTransaction) { // i.e. transaction was really committed (and not just savepoint released)
|
||||
require('./ws.js').sendTransactionSyncsToAllClients();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
catch (e) {
|
||||
cls.clearEntityChanges();
|
||||
|
||||
return ret;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
function fillNoteIdList(noteIds, truncate = true) {
|
||||
|
||||
@@ -26,9 +26,7 @@ function updateEntity(entityChange, entity, sourceId) {
|
||||
? updateNoteReordering(entityChange, entity, sourceId)
|
||||
: updateNormalEntity(entityChange, entity, sourceId);
|
||||
|
||||
// currently making exception for protected notes and note revisions because here
|
||||
// the title and content are not available decrypted as listeners would expect
|
||||
if (updated && !entity.isProtected && !entityChange.isErased) {
|
||||
if (updated && !entityChange.isErased) {
|
||||
eventService.emit(eventService.ENTITY_SYNCED, {
|
||||
entityName: entityChange.entityName,
|
||||
entity
|
||||
@@ -44,7 +42,7 @@ function updateNormalEntity(remoteEntityChange, entity, sourceId) {
|
||||
|
||||
if (localEntityChange && !localEntityChange.isErased && remoteEntityChange.isErased) {
|
||||
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);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
const sql = require('./sql');
|
||||
const log = require('./log');
|
||||
const repository = require('./repository');
|
||||
const Branch = require('../entities/branch');
|
||||
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 = ?",
|
||||
[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;
|
||||
}
|
||||
|
||||
@@ -106,8 +106,6 @@ function sendPing(client, entityChanges = []) {
|
||||
}
|
||||
}
|
||||
|
||||
const stats = require('./sync').stats;
|
||||
|
||||
sendMessage(client, {
|
||||
type: 'sync',
|
||||
data: entityChanges
|
||||
@@ -118,9 +116,7 @@ function sendTransactionSyncsToAllClients() {
|
||||
if (webSocketServer) {
|
||||
const entityChanges = cls.getAndClearEntityChanges();
|
||||
|
||||
webSocketServer.clients.forEach(function each(client) {
|
||||
sendPing(client, entityChanges);
|
||||
});
|
||||
webSocketServer.clients.forEach(client => sendPing(client, entityChanges));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<title>Trilium Notes</title>
|
||||
</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>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="mobile theme-<%= theme %>">
|
||||
<body class="mobile theme-<%= theme %> heading-style-<%= headingStyle %>">
|
||||
<noscript>Trilium requires JavaScript to be enabled.</noscript>
|
||||
|
||||
<div id="toast-container" class="d-flex flex-column justify-content-center align-items-center"></div>
|
||||
|
||||
Reference in New Issue
Block a user