mirror of
https://github.com/zadam/trilium.git
synced 2025-10-30 18:05:55 +01:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d616a77d6b | ||
|
|
5b679930de | ||
|
|
067ca9ab16 | ||
|
|
70708b36ef | ||
|
|
fb3d5f25ac | ||
|
|
9d7d79ef94 | ||
|
|
ba33a0d330 | ||
|
|
aea81c9872 | ||
|
|
806ab22fa8 | ||
|
|
9c2b98915e | ||
|
|
f2ca9276d6 | ||
|
|
48b697f408 | ||
|
|
e1e185e5db | ||
|
|
e06c5703ee | ||
|
|
fe3bb2c5f6 | ||
|
|
6afc299efb | ||
|
|
369274ead7 | ||
|
|
04e6431c09 | ||
|
|
e89057a771 | ||
|
|
4f27254e64 | ||
|
|
577dc95ab8 | ||
|
|
a266d6a3d5 | ||
|
|
749b6cb57e | ||
|
|
b0b2951ff6 | ||
|
|
1f3d73b9fd | ||
|
|
bdfd760b9d | ||
|
|
7133e60267 | ||
|
|
fc4edf4aa7 | ||
|
|
eaf93a70cd | ||
|
|
b093569ec5 | ||
|
|
4633c68a0c | ||
|
|
33571e0ef3 | ||
|
|
31876d2cf9 | ||
|
|
81c6043cb6 | ||
|
|
1982d054ef | ||
|
|
e56979c482 | ||
|
|
58555b3660 | ||
|
|
b7b1324dd0 | ||
|
|
e318acc977 | ||
|
|
8ae82f5b69 | ||
|
|
26442f418a | ||
|
|
23a432e7d8 | ||
|
|
984ecaf99c | ||
|
|
21b73a86b2 | ||
|
|
7d8277699c | ||
|
|
928ed7a034 |
@@ -1,10 +1,13 @@
|
|||||||
[General]
|
[General]
|
||||||
# Instance name can be used to distinguish between different instances
|
# Instance name can be used to distinguish between different instances using backend api.getInstanceName()
|
||||||
instanceName=
|
instanceName=
|
||||||
|
|
||||||
# set to true to allow using Trilium without authentication (makes sense for server build only, desktop build doesn't need password)
|
# set to true to allow using Trilium without authentication (makes sense for server build only, desktop build doesn't need password)
|
||||||
noAuthentication=false
|
noAuthentication=false
|
||||||
|
|
||||||
|
# set to true to disable backups (e.g. because of limited space on server)
|
||||||
|
noBackup=false
|
||||||
|
|
||||||
# Disable automatically generating desktop icon
|
# Disable automatically generating desktop icon
|
||||||
# noDesktopIcon=true
|
# noDesktopIcon=true
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* !!!!!! TRILIUM CUSTOM CHANGES !!!!!! */
|
/* !!!!!! TRILIUM CUSTOM CHANGES !!!!!! */
|
||||||
|
|
||||||
.ck-widget__selection-handle, .ck-widget__type-around { /* gets rid of triangles: https://github.com/zadam/trilium/issues/1129 */
|
.printed-content .ck-widget__selection-handle, .printed-content .ck-widget__type-around { /* gets rid of triangles: https://github.com/zadam/trilium/issues/1129 */
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"version": "0.45.4",
|
"version": "0.45.6",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -2654,9 +2654,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"electron": {
|
"electron": {
|
||||||
"version": "9.3.4",
|
"version": "9.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/electron/-/electron-9.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/electron/-/electron-9.3.5.tgz",
|
||||||
"integrity": "sha512-OHP8qMKgW8D8GtH+altB22WJw/lBOyyVdoz5e8D0/iPBmJU3Jm93vO4z4Eh/9DvdSXlH8bMHUCMLL9PVW6f+tw==",
|
"integrity": "sha512-EPmDsp7sO0UPtw7nLD1ufse/nBskP+ifXzBgUg9psCUlapkzuwYi6pmLAzKLW/bVjwgyUKwh1OKWILWfOeLGcQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@electron/get": "^1.0.1",
|
"@electron/get": "^1.0.1",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"productName": "Trilium Notes",
|
"productName": "Trilium Notes",
|
||||||
"description": "Trilium Notes",
|
"description": "Trilium Notes",
|
||||||
"version": "0.45.5",
|
"version": "0.45.10",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"main": "electron.js",
|
"main": "electron.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cross-env": "7.0.2",
|
"cross-env": "7.0.2",
|
||||||
"electron": "9.3.4",
|
"electron": "9.3.5",
|
||||||
"electron-builder": "22.9.1",
|
"electron-builder": "22.9.1",
|
||||||
"electron-packager": "15.1.0",
|
"electron-packager": "15.1.0",
|
||||||
"electron-rebuild": "2.3.2",
|
"electron-rebuild": "2.3.2",
|
||||||
|
|||||||
@@ -51,6 +51,12 @@ const TPL = `
|
|||||||
<label for="erase-notes-after-time-in-seconds">Erase notes after X seconds</label>
|
<label for="erase-notes-after-time-in-seconds">Erase notes after X seconds</label>
|
||||||
<input class="form-control" id="erase-notes-after-time-in-seconds" type="number" min="0">
|
<input class="form-control" id="erase-notes-after-time-in-seconds" type="number" min="0">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p>You can also trigger erasing manually:</p>
|
||||||
|
|
||||||
|
<button id="erase-deleted-notes-now-button" class="btn">Erase deleted notes now</button>
|
||||||
|
|
||||||
|
<br/><br/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -117,6 +123,13 @@ export default class ProtectedSessionOptions {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.$eraseDeletedNotesButton = $("#erase-deleted-notes-now-button");
|
||||||
|
this.$eraseDeletedNotesButton.on('click', () => {
|
||||||
|
server.post('notes/erase-deleted-notes-now').then(() => {
|
||||||
|
toastService.showMessage("Deleted notes have been erased.");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
this.$protectedSessionTimeout = $("#protected-session-timeout-in-seconds");
|
this.$protectedSessionTimeout = $("#protected-session-timeout-in-seconds");
|
||||||
|
|
||||||
this.$protectedSessionTimeout.on('change', () => {
|
this.$protectedSessionTimeout.on('change', () => {
|
||||||
|
|||||||
@@ -75,14 +75,16 @@ class NoteShort {
|
|||||||
this.parentToBranch[parentNoteId] = branchId;
|
this.parentToBranch[parentNoteId] = branchId;
|
||||||
}
|
}
|
||||||
|
|
||||||
addChild(childNoteId, branchId) {
|
addChild(childNoteId, branchId, sort = true) {
|
||||||
if (!this.children.includes(childNoteId)) {
|
if (!this.children.includes(childNoteId)) {
|
||||||
this.children.push(childNoteId);
|
this.children.push(childNoteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.childToBranch[childNoteId] = branchId;
|
this.childToBranch[childNoteId] = branchId;
|
||||||
|
|
||||||
this.sortChildren();
|
if (sort) {
|
||||||
|
this.sortChildren();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sortChildren() {
|
sortChildren() {
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ function linkContextMenu(e) {
|
|||||||
appContext.tabManager.openTabWithNote(notePath);
|
appContext.tabManager.openTabWithNote(notePath);
|
||||||
}
|
}
|
||||||
else if (command === 'openNoteInNewWindow') {
|
else if (command === 'openNoteInNewWindow') {
|
||||||
appContext.openInNewWindow(notePath);
|
appContext.triggerCommand('openInWindow', {notePath});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,8 +14,17 @@ function setupSplit(left, right) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const leftPaneWidth = options.getInt('leftPaneWidth');
|
let leftPaneWidth = options.getInt('leftPaneWidth');
|
||||||
const rightPaneWidth = options.getInt('rightPaneWidth');
|
if (!leftPaneWidth || leftPaneWidth < 5) {
|
||||||
|
leftPaneWidth = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rightPaneWidth = options.getInt('rightPaneWidth');
|
||||||
|
if (!rightPaneWidth || rightPaneWidth < 5) {
|
||||||
|
rightPaneWidth = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(leftPaneWidth, rightPaneWidth);
|
||||||
|
|
||||||
if (left && right) {
|
if (left && right) {
|
||||||
instance = Split(['#left-pane', '#center-pane', '#right-pane'], {
|
instance = Split(['#left-pane', '#center-pane', '#right-pane'], {
|
||||||
@@ -49,4 +58,4 @@ function setupSplit(left, right) {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
setupSplit
|
setupSplit
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ async function syncNow() {
|
|||||||
toastService.showMessage("Sync finished successfully.");
|
toastService.showMessage("Sync finished successfully.");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (result.message.length > 100) {
|
if (result.message.length > 200) {
|
||||||
result.message = result.message.substr(0, 100);
|
result.message = result.message.substr(0, 200) + "...";
|
||||||
}
|
}
|
||||||
|
|
||||||
toastService.showError("Sync failed: " + result.message);
|
toastService.showError("Sync failed: " + result.message);
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ class TreeCache {
|
|||||||
const branchRows = resp.branches;
|
const branchRows = resp.branches;
|
||||||
const attributeRows = resp.attributes;
|
const attributeRows = resp.attributes;
|
||||||
|
|
||||||
|
const noteIdsToSort = new Set();
|
||||||
|
|
||||||
for (const noteRow of noteRows) {
|
for (const noteRow of noteRows) {
|
||||||
const {noteId} = noteRow;
|
const {noteId} = noteRow;
|
||||||
|
|
||||||
@@ -153,7 +155,9 @@ class TreeCache {
|
|||||||
const parentNote = this.notes[branch.parentNoteId];
|
const parentNote = this.notes[branch.parentNoteId];
|
||||||
|
|
||||||
if (parentNote) {
|
if (parentNote) {
|
||||||
parentNote.addChild(branch.noteId, branch.branchId);
|
parentNote.addChild(branch.noteId, branch.branchId, false);
|
||||||
|
|
||||||
|
noteIdsToSort.add(parentNote.noteId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,6 +182,11 @@ class TreeCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sort all of them at once, this avoids repeated sorts (#1480)
|
||||||
|
for (const noteId of noteIdsToSort) {
|
||||||
|
this.notes[noteId].sortChildren();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async reloadNotes(noteIds) {
|
async reloadNotes(noteIds) {
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import treeService from './tree.js';
|
|||||||
import treeCache from "./tree_cache.js";
|
import treeCache from "./tree_cache.js";
|
||||||
import hoistedNoteService from './hoisted_note.js';
|
import hoistedNoteService from './hoisted_note.js';
|
||||||
import clipboard from './clipboard.js';
|
import clipboard from './clipboard.js';
|
||||||
import protectedSessionHolder from "./protected_session_holder.js";
|
|
||||||
import noteCreateService from "./note_create.js";
|
import noteCreateService from "./note_create.js";
|
||||||
import contextMenu from "./context_menu.js";
|
import contextMenu from "./context_menu.js";
|
||||||
|
import appContext from "./app_context.js";
|
||||||
|
|
||||||
class TreeContextMenu {
|
class TreeContextMenu {
|
||||||
/**
|
/**
|
||||||
@@ -95,7 +95,7 @@ class TreeContextMenu {
|
|||||||
enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes },
|
enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes },
|
||||||
{ title: 'Paste after', command: "pasteNotesAfterFromClipboard", uiIcon: "paste",
|
{ title: 'Paste after', command: "pasteNotesAfterFromClipboard", uiIcon: "paste",
|
||||||
enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes },
|
enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes },
|
||||||
{ title: "Duplicate subtree(s) here", command: "duplicateSubtree", uiIcon: "empty",
|
{ title: `Duplicate subtree <kbd data-command="duplicateSubtree">`, command: "duplicateSubtree", uiIcon: "empty",
|
||||||
enabled: parentNotSearch && isNotRoot && !isHoisted },
|
enabled: parentNotSearch && isNotRoot && !isHoisted },
|
||||||
{ title: "----" },
|
{ title: "----" },
|
||||||
{ title: "Export", command: "exportNote", uiIcon: "empty",
|
{ title: "Export", command: "exportNote", uiIcon: "empty",
|
||||||
@@ -110,14 +110,7 @@ class TreeContextMenu {
|
|||||||
const notePath = treeService.getNotePath(this.node);
|
const notePath = treeService.getNotePath(this.node);
|
||||||
|
|
||||||
if (command === 'openInTab') {
|
if (command === 'openInTab') {
|
||||||
|
appContext.tabManager.openTabWithNote(notePath);
|
||||||
const start = Date.now();
|
|
||||||
|
|
||||||
await this.node.load(true);
|
|
||||||
|
|
||||||
console.log("Reload took", Date.now() - start, "ms");
|
|
||||||
|
|
||||||
// appContext.tabManager.openTabWithNote(notePath);
|
|
||||||
}
|
}
|
||||||
else if (command === "insertNoteAfter") {
|
else if (command === "insertNoteAfter") {
|
||||||
const parentNoteId = this.node.data.parentNoteId;
|
const parentNoteId = this.node.data.parentNoteId;
|
||||||
|
|||||||
@@ -234,6 +234,8 @@ export default class AttributeDetailWidget extends TabAwareWidget {
|
|||||||
|
|
||||||
this.$inputName = this.$widget.find('.attr-input-name');
|
this.$inputName = this.$widget.find('.attr-input-name');
|
||||||
this.$inputName.on('keyup', () => this.userEditedAttribute());
|
this.$inputName.on('keyup', () => this.userEditedAttribute());
|
||||||
|
this.$inputName.on('change', () => this.userEditedAttribute());
|
||||||
|
this.$inputName.on('autocomplete:closed', () => this.userEditedAttribute());
|
||||||
|
|
||||||
this.$inputName.on('focus', () => {
|
this.$inputName.on('focus', () => {
|
||||||
attributeAutocompleteService.initAttributeNameAutocomplete({
|
attributeAutocompleteService.initAttributeNameAutocomplete({
|
||||||
|
|||||||
@@ -214,7 +214,8 @@ export default class AttributeListWidget extends TabAwareWidget {
|
|||||||
noteId: attribute.noteId,
|
noteId: attribute.noteId,
|
||||||
type: attribute.type,
|
type: attribute.type,
|
||||||
name: attribute.name,
|
name: attribute.name,
|
||||||
value: attribute.value
|
value: attribute.value,
|
||||||
|
isInheritable: attribute.isInheritable
|
||||||
},
|
},
|
||||||
isOwned: false,
|
isOwned: false,
|
||||||
x: e.pageX,
|
x: e.pageX,
|
||||||
|
|||||||
@@ -248,13 +248,22 @@ export default class NoteDetailWidget extends TabAwareWidget {
|
|||||||
|
|
||||||
this.$widget.find('.note-detail-printable:visible').printThis({
|
this.$widget.find('.note-detail-printable:visible').printThis({
|
||||||
header: $("<h2>").text(this.note && this.note.title).prop('outerHTML'),
|
header: $("<h2>").text(this.note && this.note.title).prop('outerHTML'),
|
||||||
footer: "<script>document.body.className += ' ck-content';</script>",
|
footer: `
|
||||||
|
<script src="libraries/katex/katex.min.js"></script>
|
||||||
|
<script src="libraries/katex/auto-render.min.js"></script>
|
||||||
|
<script>
|
||||||
|
document.body.className += ' ck-content printed-content';
|
||||||
|
|
||||||
|
renderMathInElement(document.body, {});
|
||||||
|
</script>
|
||||||
|
`,
|
||||||
importCSS: false,
|
importCSS: false,
|
||||||
loadCSS: [
|
loadCSS: [
|
||||||
"libraries/codemirror/codemirror.css",
|
"libraries/codemirror/codemirror.css",
|
||||||
"libraries/ckeditor/ckeditor-content.css",
|
"libraries/ckeditor/ckeditor-content.css",
|
||||||
"libraries/ckeditor/ckeditor-content.css",
|
"libraries/ckeditor/ckeditor-content.css",
|
||||||
"libraries/bootstrap/css/bootstrap.min.css",
|
"libraries/bootstrap/css/bootstrap.min.css",
|
||||||
|
"libraries/katex/katex.min.css",
|
||||||
"stylesheets/print.css",
|
"stylesheets/print.css",
|
||||||
"stylesheets/relation_map.css",
|
"stylesheets/relation_map.css",
|
||||||
"stylesheets/themes.css"
|
"stylesheets/themes.css"
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export default class NotePathsWidget extends TabAwareWidget {
|
|||||||
|
|
||||||
this.$currentPath = this.$widget.find('.current-path');
|
this.$currentPath = this.$widget.find('.current-path');
|
||||||
this.$dropdown = this.$widget.find(".dropdown");
|
this.$dropdown = this.$widget.find(".dropdown");
|
||||||
|
this.$dropdownToggle = this.$widget.find('.dropdown-toggle');
|
||||||
|
|
||||||
this.$notePathList = this.$dropdown.find(".note-path-list");
|
this.$notePathList = this.$dropdown.find(".note-path-list");
|
||||||
|
|
||||||
@@ -100,6 +101,8 @@ export default class NotePathsWidget extends TabAwareWidget {
|
|||||||
|
|
||||||
parentNoteId = noteId;
|
parentNoteId = noteId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.$dropdownToggle.dropdown('hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderDropdown() {
|
async renderDropdown() {
|
||||||
@@ -141,20 +144,20 @@ export default class NotePathsWidget extends TabAwareWidget {
|
|||||||
async addPath(notePath, isCurrent) {
|
async addPath(notePath, isCurrent) {
|
||||||
const title = await treeService.getNotePathTitle(notePath);
|
const title = await treeService.getNotePathTitle(notePath);
|
||||||
|
|
||||||
const noteLink = await linkService.createNoteLink(notePath, {title});
|
const $noteLink = await linkService.createNoteLink(notePath, {title});
|
||||||
|
|
||||||
noteLink
|
$noteLink
|
||||||
.addClass("dropdown-item");
|
.addClass("dropdown-item");
|
||||||
|
|
||||||
noteLink
|
$noteLink
|
||||||
.find('a')
|
.find('a')
|
||||||
.addClass("no-tooltip-preview");
|
.addClass("no-tooltip-preview");
|
||||||
|
|
||||||
if (isCurrent) {
|
if (isCurrent) {
|
||||||
noteLink.addClass("current");
|
$noteLink.addClass("current");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$notePathList.append(noteLink);
|
this.$notePathList.append($noteLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
entitiesReloadedEvent({loadResults}) {
|
entitiesReloadedEvent({loadResults}) {
|
||||||
|
|||||||
@@ -1198,7 +1198,25 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
this.clearSelectedNodes();
|
this.clearSelectedNodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canBeMovedUpOrDown(node) {
|
||||||
|
if (node.data.noteId === 'root') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentNote = treeCache.getNoteFromCache(node.getParent().data.noteId);
|
||||||
|
|
||||||
|
if (parentNote && parentNote.hasLabel('sorted')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
moveNoteUpCommand({node}) {
|
moveNoteUpCommand({node}) {
|
||||||
|
if (!this.canBeMovedUpOrDown(node)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const beforeNode = node.getPrevSibling();
|
const beforeNode = node.getPrevSibling();
|
||||||
|
|
||||||
if (beforeNode !== null) {
|
if (beforeNode !== null) {
|
||||||
@@ -1207,7 +1225,12 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
moveNoteDownCommand({node}) {
|
moveNoteDownCommand({node}) {
|
||||||
|
if (!this.canBeMovedUpOrDown(node)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const afterNode = node.getNextSibling();
|
const afterNode = node.getNextSibling();
|
||||||
|
|
||||||
if (afterNode !== null) {
|
if (afterNode !== null) {
|
||||||
branchService.moveAfterBranch([node.data.branchId], afterNode.data.branchId);
|
branchService.moveAfterBranch([node.data.branchId], afterNode.data.branchId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ export default class NoteTypeWidget extends TabAwareWidget {
|
|||||||
this.$noteTypeDropdown = this.$widget.find(".note-type-dropdown");
|
this.$noteTypeDropdown = this.$widget.find(".note-type-dropdown");
|
||||||
this.$noteTypeButton = this.$widget.find(".note-type-button");
|
this.$noteTypeButton = this.$widget.find(".note-type-button");
|
||||||
this.$noteTypeDesc = this.$widget.find(".note-type-desc");
|
this.$noteTypeDesc = this.$widget.find(".note-type-desc");
|
||||||
|
|
||||||
|
this.$widget.on('click', '.dropdown-item',
|
||||||
|
() => this.$widget.find('.dropdown-toggle').dropdown('toggle'));
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshWithNote(note) {
|
async refreshWithNote(note) {
|
||||||
@@ -64,8 +67,6 @@ export default class NoteTypeWidget extends TabAwareWidget {
|
|||||||
const noteType = NOTE_TYPES.find(nt => nt.type === type);
|
const noteType = NOTE_TYPES.find(nt => nt.type === type);
|
||||||
|
|
||||||
this.save(noteType.type, noteType.mime);
|
this.save(noteType.type, noteType.mime);
|
||||||
|
|
||||||
this.$widget.find('.dropdown-toggle').dropdown('toggle');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.note.type === noteType.type) {
|
if (this.note.type === noteType.type) {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const TPL = `
|
|||||||
cursor: text !important;
|
cursor: text !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-detail-editable-text *:first-child {
|
.note-detail-editable-text *:not(figure):first-child {
|
||||||
margin-top: 0 !important;
|
margin-top: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,11 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async doRefresh(note) {
|
async doRefresh(note) {
|
||||||
|
// we load CKEditor also for read only notes because they contain content styles required for correct rendering of even read only notes
|
||||||
|
// we could load just ckeditor-content.css but that causes CSS conflicts when both build CSS and this content CSS is loaded at the same time
|
||||||
|
// (see https://github.com/zadam/trilium/issues/1590 for example of such conflict)
|
||||||
|
await libraryLoader.requireLibrary(libraryLoader.CKEDITOR);
|
||||||
|
|
||||||
const noteComplement = await treeCache.getNoteComplement(note.noteId);
|
const noteComplement = await treeCache.getNoteComplement(note.noteId);
|
||||||
|
|
||||||
this.$content.html(noteComplement.content);
|
this.$content.html(noteComplement.content);
|
||||||
|
|||||||
@@ -703,6 +703,7 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background-color: var(--accented-background-color);
|
background-color: var(--accented-background-color);
|
||||||
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
.include-note.ck-placeholder::before { /* remove placeholder in otherwise empty note */
|
.include-note.ck-placeholder::before { /* remove placeholder in otherwise empty note */
|
||||||
|
|||||||
@@ -143,6 +143,11 @@ body {
|
|||||||
--ck-color-dropdown-panel-background: var(--accented-background-color);
|
--ck-color-dropdown-panel-background: var(--accented-background-color);
|
||||||
--ck-color-dropdown-panel-border: var(--main-border-color);
|
--ck-color-dropdown-panel-border: var(--main-border-color);
|
||||||
|
|
||||||
|
/* -- Overrides the default .ck-splitbutton class colors. ----------------------------------- */
|
||||||
|
|
||||||
|
--ck-color-split-button-hover-background: var(--ck-color-button-default-hover-background);
|
||||||
|
--ck-color-split-button-hover-border: var(--main-border-color);
|
||||||
|
|
||||||
/* -- Overrides the default .ck-input class colors. ----------------------------------------- */
|
/* -- Overrides the default .ck-input class colors. ----------------------------------------- */
|
||||||
|
|
||||||
--ck-color-input-background: var(--accented-background-color);
|
--ck-color-input-background: var(--accented-background-color);
|
||||||
@@ -199,6 +204,9 @@ body {
|
|||||||
--ck-color-engine-placeholder-text: var(--muted-text-color);
|
--ck-color-engine-placeholder-text: var(--muted-text-color);
|
||||||
|
|
||||||
--ck-z-modal: 10000;
|
--ck-z-modal: 10000;
|
||||||
|
|
||||||
|
--ck-color-widget-type-around-button: var(--main-border-color);
|
||||||
|
--ck-color-widget-type-around-button-hover: var(--main-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|||||||
@@ -23,11 +23,7 @@ function exportBranch(req, res) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (type === 'subtree' && (format === 'html' || format === 'markdown')) {
|
if (type === 'subtree' && (format === 'html' || format === 'markdown')) {
|
||||||
const start = Date.now();
|
|
||||||
|
|
||||||
zipExportService.exportToZip(taskContext, branch, format, res);
|
zipExportService.exportToZip(taskContext, branch, format, res);
|
||||||
|
|
||||||
console.log("Export took", Date.now() - start, "ms");
|
|
||||||
}
|
}
|
||||||
else if (type === 'single') {
|
else if (type === 'single') {
|
||||||
singleExportService.exportSingleNote(taskContext, branch, format, res);
|
singleExportService.exportSingleNote(taskContext, branch, format, res);
|
||||||
|
|||||||
@@ -3,15 +3,33 @@
|
|||||||
const sql = require('../../services/sql');
|
const sql = require('../../services/sql');
|
||||||
|
|
||||||
function getRelations(noteIds) {
|
function getRelations(noteIds) {
|
||||||
return (sql.getManyRows(`
|
noteIds = Array.from(noteIds);
|
||||||
SELECT noteId, name, value AS targetNoteId
|
|
||||||
FROM attributes
|
return [
|
||||||
WHERE (noteId IN (???) OR value IN (???))
|
// first read all non-image relations
|
||||||
AND type = 'relation'
|
...sql.getManyRows(`
|
||||||
AND isDeleted = 0
|
SELECT noteId, name, value AS targetNoteId
|
||||||
AND noteId != ''
|
FROM attributes
|
||||||
AND value != ''
|
WHERE (noteId IN (???) OR value IN (???))
|
||||||
`, Array.from(noteIds)));
|
AND type = 'relation'
|
||||||
|
AND name != 'imageLink'
|
||||||
|
AND isDeleted = 0
|
||||||
|
AND noteId != ''
|
||||||
|
AND value != ''`, noteIds),
|
||||||
|
// ... then read only imageLink relations which are not connecting parent and child
|
||||||
|
// this is done to not show image links in the trivial case where they are direct children of the note to which they are included. Same heuristic as in note tree
|
||||||
|
...sql.getManyRows(`
|
||||||
|
SELECT rel.noteId, rel.name, rel.value AS targetNoteId
|
||||||
|
FROM attributes AS rel
|
||||||
|
LEFT JOIN branches ON branches.parentNoteId = rel.noteId AND branches.noteId = rel.value AND branches.isDeleted = 0
|
||||||
|
WHERE (rel.noteId IN (???) OR rel.value IN (???))
|
||||||
|
AND rel.type = 'relation'
|
||||||
|
AND rel.name = 'imageLink'
|
||||||
|
AND rel.isDeleted = 0
|
||||||
|
AND rel.noteId != ''
|
||||||
|
AND rel.value != ''
|
||||||
|
AND branches.branchId IS NULL`, noteIds)
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLinkMap(req) {
|
function getLinkMap(req) {
|
||||||
|
|||||||
@@ -193,6 +193,10 @@ function duplicateSubtree(req) {
|
|||||||
return noteService.duplicateSubtree(noteId, parentNoteId);
|
return noteService.duplicateSubtree(noteId, parentNoteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function eraseDeletedNotesNow() {
|
||||||
|
noteService.eraseDeletedNotesNow();
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getNote,
|
getNote,
|
||||||
updateNote,
|
updateNote,
|
||||||
@@ -204,5 +208,6 @@ module.exports = {
|
|||||||
setNoteTypeMime,
|
setNoteTypeMime,
|
||||||
getRelationMap,
|
getRelationMap,
|
||||||
changeTitle,
|
changeTitle,
|
||||||
duplicateSubtree
|
duplicateSubtree,
|
||||||
|
eraseDeletedNotesNow
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,11 +54,21 @@ function getBundlesWithLabel(label, value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getStartupBundles() {
|
function getStartupBundles() {
|
||||||
return getBundlesWithLabel("run", "frontendStartup");
|
if (!process.env.TRILIUM_SAFE_MODE) {
|
||||||
|
return getBundlesWithLabel("run", "frontendStartup");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWidgetBundles() {
|
function getWidgetBundles() {
|
||||||
return getBundlesWithLabel("widget");
|
if (!process.env.TRILIUM_SAFE_MODE) {
|
||||||
|
return getBundlesWithLabel("widget");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRelationBundles(req) {
|
function getRelationBundles(req) {
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ function saveSyncSeed(req) {
|
|||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info("Saved sync seed.");
|
||||||
|
|
||||||
sqlInit.createDatabaseForSync(options);
|
sqlInit.createDatabaseForSync(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ function getTree(req) {
|
|||||||
const noteIds = sql.getColumn(`
|
const noteIds = sql.getColumn(`
|
||||||
WITH RECURSIVE
|
WITH RECURSIVE
|
||||||
treeWithDescendants(noteId, isExpanded) AS (
|
treeWithDescendants(noteId, isExpanded) AS (
|
||||||
SELECT noteId, 1 FROM branches WHERE parentNoteId = ? AND isDeleted = 0
|
SELECT noteId, isExpanded FROM branches WHERE parentNoteId = ? AND isDeleted = 0
|
||||||
UNION
|
UNION
|
||||||
SELECT branches.noteId, branches.isExpanded FROM branches
|
SELECT branches.noteId, branches.isExpanded FROM branches
|
||||||
JOIN treeWithDescendants ON branches.parentNoteId = treeWithDescendants.noteId
|
JOIN treeWithDescendants ON branches.parentNoteId = treeWithDescendants.noteId
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ function register(app) {
|
|||||||
route(GET, '/api/notes/:noteId/revisions/:noteRevisionId/download', [auth.checkApiAuthOrElectron], noteRevisionsApiRoute.downloadNoteRevision);
|
route(GET, '/api/notes/:noteId/revisions/:noteRevisionId/download', [auth.checkApiAuthOrElectron], noteRevisionsApiRoute.downloadNoteRevision);
|
||||||
apiRoute(PUT, '/api/notes/:noteId/restore-revision/:noteRevisionId', noteRevisionsApiRoute.restoreNoteRevision);
|
apiRoute(PUT, '/api/notes/:noteId/restore-revision/:noteRevisionId', noteRevisionsApiRoute.restoreNoteRevision);
|
||||||
apiRoute(POST, '/api/notes/relation-map', notesApiRoute.getRelationMap);
|
apiRoute(POST, '/api/notes/relation-map', notesApiRoute.getRelationMap);
|
||||||
|
apiRoute(POST, '/api/notes/erase-deleted-notes-now', notesApiRoute.eraseDeletedNotesNow);
|
||||||
apiRoute(PUT, '/api/notes/:noteId/change-title', notesApiRoute.changeTitle);
|
apiRoute(PUT, '/api/notes/:noteId/change-title', notesApiRoute.changeTitle);
|
||||||
apiRoute(POST, '/api/notes/:noteId/duplicate/:parentNoteId', notesApiRoute.duplicateSubtree);
|
apiRoute(POST, '/api/notes/:noteId/duplicate/:parentNoteId', notesApiRoute.duplicateSubtree);
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
module.exports = { buildDate:"2020-11-20T22:50:10+01:00", buildRevision: "e5fa1e0ed555c1c2cb4a14c426d7091d62b5beea" };
|
module.exports = { buildDate:"2021-02-11T23:05:48+01:00", buildRevision: "5b679930de787efc2f70dfe7961cd1677ec2fa0f" };
|
||||||
|
|||||||
@@ -650,7 +650,7 @@ class ConsistencyChecks {
|
|||||||
// root branch should always be expanded
|
// root branch should always be expanded
|
||||||
sql.execute("UPDATE branches SET isExpanded = 1 WHERE branchId = 'root'");
|
sql.execute("UPDATE branches SET isExpanded = 1 WHERE branchId = 'root'");
|
||||||
|
|
||||||
if (this.unrecoveredConsistencyErrors) {
|
if (!this.unrecoveredConsistencyErrors) {
|
||||||
// we run this only if basic checks passed since this assumes basic data consistency
|
// we run this only if basic checks passed since this assumes basic data consistency
|
||||||
|
|
||||||
this.checkTreeCycles();
|
this.checkTreeCycles();
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ function encrypt(key, plainText, ivLength = 13) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function decrypt(key, cipherText, ivLength = 13) {
|
function decrypt(key, cipherText, ivLength = 13) {
|
||||||
|
if (cipherText === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!key) {
|
if (!key) {
|
||||||
return "[protected]";
|
return "[protected]";
|
||||||
}
|
}
|
||||||
@@ -93,6 +97,10 @@ function decrypt(key, cipherText, ivLength = 13) {
|
|||||||
function decryptString(dataKey, cipherText) {
|
function decryptString(dataKey, cipherText) {
|
||||||
const buffer = decrypt(dataKey, cipherText);
|
const buffer = decrypt(dataKey, cipherText);
|
||||||
|
|
||||||
|
if (buffer === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const str = buffer.toString('utf-8');
|
const str = buffer.toString('utf-8');
|
||||||
|
|
||||||
if (str === 'false') {
|
if (str === 'false') {
|
||||||
@@ -108,4 +116,4 @@ module.exports = {
|
|||||||
encrypt,
|
encrypt,
|
||||||
decrypt,
|
decrypt,
|
||||||
decryptString
|
decryptString
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ function exportToZip(taskContext, branch, format, res) {
|
|||||||
const available = !note.isProtected || protectedSessionService.isProtectedSessionAvailable();
|
const available = !note.isProtected || protectedSessionService.isProtectedSessionAvailable();
|
||||||
|
|
||||||
// if it's a leaf then we'll export it even if it's empty
|
// if it's a leaf then we'll export it even if it's empty
|
||||||
if (available && ((note.getContent()).length > 0 || childBranches.length === 0)) {
|
if (available && (note.getContent().length > 0 || childBranches.length === 0)) {
|
||||||
meta.dataFileName = getDataFileName(note, baseFileName, existingFileNames);
|
meta.dataFileName = getDataFileName(note, baseFileName, existingFileNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +234,7 @@ function exportToZip(taskContext, branch, format, res) {
|
|||||||
<link rel="stylesheet" href="${cssUrl}">
|
<link rel="stylesheet" href="${cssUrl}">
|
||||||
<base target="_parent">
|
<base target="_parent">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="ck-content">
|
||||||
<h1>${utils.escapeHtml(title)}</h1>
|
<h1>${utils.escapeHtml(title)}</h1>
|
||||||
${content}
|
${content}
|
||||||
</body>
|
</body>
|
||||||
@@ -433,14 +433,13 @@ ${content}
|
|||||||
}
|
}
|
||||||
|
|
||||||
const note = branch.getNote();
|
const note = branch.getNote();
|
||||||
const zipFileName = (branch.prefix ? (branch.prefix + " - ") : "") + note.title + ".zip";
|
const zipFileName = (branch.prefix ? `${branch.prefix} - ` : "") + note.title + ".zip";
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', utils.getContentDisposition(zipFileName));
|
res.setHeader('Content-Disposition', utils.getContentDisposition(zipFileName));
|
||||||
res.setHeader('Content-Type', 'application/zip');
|
res.setHeader('Content-Type', 'application/zip');
|
||||||
|
|
||||||
zipFile.end();
|
|
||||||
|
|
||||||
zipFile.outputStream.pipe(res);
|
zipFile.outputStream.pipe(res);
|
||||||
|
zipFile.end();
|
||||||
|
|
||||||
taskContext.taskSucceeded();
|
taskContext.taskSucceeded();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const scriptService = require('./script');
|
|||||||
const treeService = require('./tree');
|
const treeService = require('./tree');
|
||||||
const noteService = require('./notes');
|
const noteService = require('./notes');
|
||||||
const repository = require('./repository');
|
const repository = require('./repository');
|
||||||
|
const noteCache = require('./note_cache/note_cache');
|
||||||
const Attribute = require('../entities/attribute');
|
const Attribute = require('../entities/attribute');
|
||||||
|
|
||||||
function runAttachedRelations(note, relationName, originEntity) {
|
function runAttachedRelations(note, relationName, originEntity) {
|
||||||
@@ -22,11 +23,15 @@ eventService.subscribe(eventService.NOTE_TITLE_CHANGED, note => {
|
|||||||
runAttachedRelations(note, 'runOnNoteTitleChange', note);
|
runAttachedRelations(note, 'runOnNoteTitleChange', note);
|
||||||
|
|
||||||
if (!note.isRoot()) {
|
if (!note.isRoot()) {
|
||||||
const parents = note.getParentNotes();
|
const noteFromCache = noteCache.notes[note.noteId];
|
||||||
|
|
||||||
for (const parent of parents) {
|
if (!noteFromCache) {
|
||||||
if (parent.hasOwnedLabel("sorted")) {
|
return;
|
||||||
treeService.sortNotesAlphabetically(parent.noteId);
|
}
|
||||||
|
|
||||||
|
for (const parentNote of noteFromCache.parents) {
|
||||||
|
if (parentNote.hasLabel("sorted")) {
|
||||||
|
treeService.sortNotesAlphabetically(parentNote.noteId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,12 +75,26 @@ eventService.subscribe(eventService.ENTITY_CREATED, ({ entityName, entity }) =>
|
|||||||
if (templateNoteContent) {
|
if (templateNoteContent) {
|
||||||
note.setContent(templateNoteContent);
|
note.setContent(templateNoteContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
note.type = templateNote.type;
|
||||||
|
note.mime = templateNote.mime;
|
||||||
|
note.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
noteService.duplicateSubtreeWithoutRoot(templateNote.noteId, note.noteId);
|
noteService.duplicateSubtreeWithoutRoot(templateNote.noteId, note.noteId);
|
||||||
}
|
}
|
||||||
else if (entity.type === 'label' && entity.name === 'sorted') {
|
else if (entity.type === 'label' && entity.name === 'sorted') {
|
||||||
treeService.sortNotesAlphabetically(entity.noteId);
|
treeService.sortNotesAlphabetically(entity.noteId);
|
||||||
|
|
||||||
|
if (entity.isInheritable) {
|
||||||
|
const note = noteCache.notes[entity.noteId];
|
||||||
|
|
||||||
|
if (note) {
|
||||||
|
for (const noteId of note.subtreeNoteIds) {
|
||||||
|
treeService.sortNotesAlphabetically(noteId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (entityName === 'notes') {
|
else if (entityName === 'notes') {
|
||||||
@@ -90,10 +109,10 @@ eventService.subscribe(eventService.CHILD_NOTE_CREATED, ({ parentNote, childNote
|
|||||||
function processInverseRelations(entityName, entity, handler) {
|
function processInverseRelations(entityName, entity, handler) {
|
||||||
if (entityName === 'attributes' && entity.type === 'relation') {
|
if (entityName === 'attributes' && entity.type === 'relation') {
|
||||||
const note = entity.getNote();
|
const note = entity.getNote();
|
||||||
const attributes = (note.getOwnedAttributes(entity.name)).filter(relation => relation.type === 'relation-definition');
|
const relDefinitions = note.getLabels('relation:' + entity.name);
|
||||||
|
|
||||||
for (const attribute of attributes) {
|
for (const relDefinition of relDefinitions) {
|
||||||
const definition = attribute.value;
|
const definition = relDefinition.getDefinition();
|
||||||
|
|
||||||
if (definition.inverseRelation && definition.inverseRelation.trim()) {
|
if (definition.inverseRelation && definition.inverseRelation.trim()) {
|
||||||
const targetNote = entity.getTargetNote();
|
const targetNote = entity.getTargetNote();
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (taskContext.data.safeImport && attributeService.isAttributeDangerous(attr.type, attr.name)) {
|
if (taskContext.data.safeImport && attributeService.isAttributeDangerous(attr.type, attr.name)) {
|
||||||
attr.name = 'disabled-' + attr.name;
|
attr.name = 'disabled:' + attr.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
attributes.push(attr);
|
attributes.push(attr);
|
||||||
|
|||||||
@@ -184,6 +184,12 @@ const DEFAULT_KEYBOARD_ACTIONS = [
|
|||||||
description: "Add note above to the selection",
|
description: "Add note above to the selection",
|
||||||
scope: "note-tree"
|
scope: "note-tree"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
actionName: "duplicateSubtree",
|
||||||
|
defaultShortcuts: [],
|
||||||
|
description: "Duplicate subtree",
|
||||||
|
scope: "note-tree"
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -307,6 +313,12 @@ const DEFAULT_KEYBOARD_ACTIONS = [
|
|||||||
description: "Cuts the selection from the current note and creates subnote with the selected text",
|
description: "Cuts the selection from the current note and creates subnote with the selected text",
|
||||||
scope: "text-detail"
|
scope: "text-detail"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
actionName: "addIncludeNoteToText",
|
||||||
|
defaultShortcuts: [],
|
||||||
|
description: "Opens the dialog to include a note",
|
||||||
|
scope: "text-detail"
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
separator: "Attributes (labels & relations)"
|
separator: "Attributes (labels & relations)"
|
||||||
|
|||||||
@@ -133,6 +133,14 @@ class Note {
|
|||||||
return !!this.attributes.find(attr => attr.type === type && attr.name === name);
|
return !!this.attributes.find(attr => attr.type === type && attr.name === name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasLabel(name) {
|
||||||
|
return this.hasAttribute('label', name);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasRelation(name) {
|
||||||
|
return this.hasAttribute('relation', name);
|
||||||
|
}
|
||||||
|
|
||||||
getLabelValue(name) {
|
getLabelValue(name) {
|
||||||
const label = this.attributes.find(attr => attr.type === 'label' && attr.name === name);
|
const label = this.attributes.find(attr => attr.type === 'label' && attr.name === name);
|
||||||
|
|
||||||
@@ -275,6 +283,11 @@ class Note {
|
|||||||
return arr.flat();
|
return arr.flat();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return {String[]} */
|
||||||
|
get subtreeNoteIds() {
|
||||||
|
return this.subtreeNotes.map(note => note.noteId);
|
||||||
|
}
|
||||||
|
|
||||||
get parentCount() {
|
get parentCount() {
|
||||||
return this.parents.length;
|
return this.parents.length;
|
||||||
}
|
}
|
||||||
@@ -338,7 +351,7 @@ class Note {
|
|||||||
|
|
||||||
decrypt() {
|
decrypt() {
|
||||||
if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) {
|
if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) {
|
||||||
this.title = protectedSessionService.decryptString(note.title);
|
this.title = protectedSessionService.decryptString(this.title);
|
||||||
|
|
||||||
this.isDecrypted = true;
|
this.isDecrypted = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const Note = require('./entities/note');
|
|
||||||
const Branch = require('./entities/branch');
|
|
||||||
const Attribute = require('./entities/attribute');
|
|
||||||
|
|
||||||
class NoteCache {
|
class NoteCache {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.reset();
|
this.reset();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const NoteRevision = require('../entities/note_revision');
|
const NoteRevision = require('../entities/note_revision');
|
||||||
const dateUtils = require('../services/date_utils');
|
const dateUtils = require('../services/date_utils');
|
||||||
|
const log = require('../services/log');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Note} note
|
* @param {Note} note
|
||||||
@@ -9,14 +10,21 @@ const dateUtils = require('../services/date_utils');
|
|||||||
function protectNoteRevisions(note) {
|
function protectNoteRevisions(note) {
|
||||||
for (const revision of note.getRevisions()) {
|
for (const revision of note.getRevisions()) {
|
||||||
if (note.isProtected !== revision.isProtected) {
|
if (note.isProtected !== revision.isProtected) {
|
||||||
const content = revision.getContent();
|
try {
|
||||||
|
const content = revision.getContent();
|
||||||
|
|
||||||
revision.isProtected = note.isProtected;
|
revision.isProtected = note.isProtected;
|
||||||
|
|
||||||
// this will force de/encryption
|
// this will force de/encryption
|
||||||
revision.setContent(content);
|
revision.setContent(content);
|
||||||
|
|
||||||
revision.save();
|
revision.save();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
log.error("Could not un/protect note revision ID = " + revision.noteRevisionId);
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,18 +185,25 @@ function protectNoteRecursively(note, protect, includingSubTree, taskContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function protectNote(note, protect) {
|
function protectNote(note, protect) {
|
||||||
if (protect !== note.isProtected) {
|
try {
|
||||||
const content = note.getContent();
|
if (protect !== note.isProtected) {
|
||||||
|
const content = note.getContent();
|
||||||
|
|
||||||
note.isProtected = protect;
|
note.isProtected = protect;
|
||||||
|
|
||||||
// this will force de/encryption
|
// this will force de/encryption
|
||||||
note.setContent(content);
|
note.setContent(content);
|
||||||
|
|
||||||
note.save();
|
note.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
noteRevisionService.protectNoteRevisions(note);
|
||||||
}
|
}
|
||||||
|
catch (e) {
|
||||||
|
log.error("Could not un/protect note ID = " + note.noteId);
|
||||||
|
|
||||||
noteRevisionService.protectNoteRevisions(note);
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function findImageLinks(content, foundLinks) {
|
function findImageLinks(content, foundLinks) {
|
||||||
@@ -668,8 +675,10 @@ function scanForLinks(note) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function eraseDeletedNotes() {
|
function eraseDeletedNotes(eraseNotesAfterTimeInSeconds = null) {
|
||||||
const eraseNotesAfterTimeInSeconds = optionService.getOptionInt('eraseNotesAfterTimeInSeconds');
|
if (eraseNotesAfterTimeInSeconds === null) {
|
||||||
|
eraseNotesAfterTimeInSeconds = optionService.getOptionInt('eraseNotesAfterTimeInSeconds');
|
||||||
|
}
|
||||||
|
|
||||||
const cutoffDate = new Date(Date.now() - eraseNotesAfterTimeInSeconds * 1000);
|
const cutoffDate = new Date(Date.now() - eraseNotesAfterTimeInSeconds * 1000);
|
||||||
|
|
||||||
@@ -719,6 +728,10 @@ function eraseDeletedNotes() {
|
|||||||
log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`);
|
log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function eraseDeletedNotesNow() {
|
||||||
|
eraseDeletedNotes(0);
|
||||||
|
}
|
||||||
|
|
||||||
// do a replace in str - all keys should be replaced by the corresponding values
|
// do a replace in str - all keys should be replaced by the corresponding values
|
||||||
function replaceByMap(str, mapObj) {
|
function replaceByMap(str, mapObj) {
|
||||||
const re = new RegExp(Object.keys(mapObj).join("|"),"g");
|
const re = new RegExp(Object.keys(mapObj).join("|"),"g");
|
||||||
@@ -739,7 +752,10 @@ function duplicateSubtree(origNoteId, newParentNoteId) {
|
|||||||
|
|
||||||
const res = duplicateSubtreeInner(origNote, origBranch, newParentNoteId, noteIdMapping);
|
const res = duplicateSubtreeInner(origNote, origBranch, newParentNoteId, noteIdMapping);
|
||||||
|
|
||||||
res.note.title += " (dup)";
|
if (!res.note.title.endsWith('(dup)')) {
|
||||||
|
res.note.title += " (dup)";
|
||||||
|
}
|
||||||
|
|
||||||
res.note.save();
|
res.note.save();
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
@@ -822,9 +838,9 @@ function getNoteIdMapping(origNote) {
|
|||||||
|
|
||||||
sqlInit.dbReady.then(() => {
|
sqlInit.dbReady.then(() => {
|
||||||
// first cleanup kickoff 5 minutes after startup
|
// first cleanup kickoff 5 minutes after startup
|
||||||
setTimeout(cls.wrap(eraseDeletedNotes), 5 * 60 * 1000);
|
setTimeout(cls.wrap(() => eraseDeletedNotes()), 5 * 60 * 1000);
|
||||||
|
|
||||||
setInterval(cls.wrap(eraseDeletedNotes), 4 * 3600 * 1000);
|
setInterval(cls.wrap(() => eraseDeletedNotes()), 4 * 3600 * 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@@ -838,5 +854,6 @@ module.exports = {
|
|||||||
duplicateSubtree,
|
duplicateSubtree,
|
||||||
duplicateSubtreeWithoutRoot,
|
duplicateSubtreeWithoutRoot,
|
||||||
getUndeletedParentBranches,
|
getUndeletedParentBranches,
|
||||||
triggerNoteTitleChanged
|
triggerNoteTitleChanged,
|
||||||
|
eraseDeletedNotesNow
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,10 +31,7 @@ function initNotSyncedOptions(initialized, startNotePath = 'root', opts = {}) {
|
|||||||
optionService.createOption('openTabs', JSON.stringify([
|
optionService.createOption('openTabs', JSON.stringify([
|
||||||
{
|
{
|
||||||
notePath: startNotePath,
|
notePath: startNotePath,
|
||||||
active: true,
|
active: true
|
||||||
sidebar: {
|
|
||||||
widgets: []
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]), false);
|
]), false);
|
||||||
|
|
||||||
@@ -103,6 +100,15 @@ function initStartupOptions() {
|
|||||||
log.info(`Created missing option "${name}" with default value "${value}"`);
|
log.info(`Created missing option "${name}" with default value "${value}"`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.TRILIUM_START_NOTE_ID || process.env.TRILIUM_SAFE_MODE) {
|
||||||
|
optionService.setOption('openTabs', JSON.stringify([
|
||||||
|
{
|
||||||
|
notePath: process.env.TRILIUM_START_NOTE_ID || 'root',
|
||||||
|
active: true
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getKeyboardDefaultOptions() {
|
function getKeyboardDefaultOptions() {
|
||||||
|
|||||||
@@ -43,10 +43,18 @@ function decryptNotes(notes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function encrypt(plainText) {
|
function encrypt(plainText) {
|
||||||
|
if (plainText === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return dataEncryptionService.encrypt(getDataKey(), plainText);
|
return dataEncryptionService.encrypt(getDataKey(), plainText);
|
||||||
}
|
}
|
||||||
|
|
||||||
function decrypt(cipherText) {
|
function decrypt(cipherText) {
|
||||||
|
if (cipherText === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return dataEncryptionService.decrypt(getDataKey(), cipherText);
|
return dataEncryptionService.decrypt(getDataKey(), cipherText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,11 @@ function runNotesWithLabel(runAttrValue) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sqlInit.dbReady.then(() => {
|
sqlInit.dbReady.then(() => {
|
||||||
setTimeout(cls.wrap(() => runNotesWithLabel('backendStartup')), 10 * 1000);
|
if (!process.env.TRILIUM_SAFE_MODE) {
|
||||||
|
setTimeout(cls.wrap(() => runNotesWithLabel('backendStartup')), 10 * 1000);
|
||||||
|
|
||||||
setInterval(cls.wrap(() => runNotesWithLabel('hourly')), 3600 * 1000);
|
setInterval(cls.wrap(() => runNotesWithLabel('hourly')), 3600 * 1000);
|
||||||
|
|
||||||
setInterval(cls.wrap(() => runNotesWithLabel('daily')), 24 * 3600 * 1000);
|
setInterval(cls.wrap(() => runNotesWithLabel('daily')), 24 * 3600 * 1000);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
const sql = require('./sql');
|
|
||||||
const ScriptContext = require('./script_context');
|
const ScriptContext = require('./script_context');
|
||||||
const repository = require('./repository');
|
const repository = require('./repository');
|
||||||
const cls = require('./cls');
|
const cls = require('./cls');
|
||||||
|
|||||||
@@ -32,26 +32,29 @@ class NoteContentProtectedFulltextExp extends Expression {
|
|||||||
FROM notes JOIN note_contents USING (noteId)
|
FROM notes JOIN note_contents USING (noteId)
|
||||||
WHERE type IN ('text', 'code') AND isDeleted = 0 AND isProtected = 1`)) {
|
WHERE type IN ('text', 'code') AND isDeleted = 0 AND isProtected = 1`)) {
|
||||||
|
|
||||||
|
if (!inputNoteSet.hasNoteId(noteId) || !(noteId in noteCache.notes)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
content = protectedSessionService.decryptString(content);
|
content = protectedSessionService.decryptString(content);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
log.info('Cannot decrypt content of note', noteId);
|
log.info(`Cannot decrypt content of note ${noteId}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
content = content.toLowerCase();
|
content = content.toLowerCase();
|
||||||
|
|
||||||
if (type === 'text' && mime === 'text/html') {
|
if (type === 'text' && mime === 'text/html') {
|
||||||
content = striptags(content);
|
if (content.length < 20000) { // striptags is slow for very large notes
|
||||||
|
content = striptags(content);
|
||||||
|
}
|
||||||
|
|
||||||
content = content.replace(/ /g, ' ');
|
content = content.replace(/ /g, ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.tokens.find(token => !content.includes(token))) {
|
if (!this.tokens.find(token => !content.includes(token))) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inputNoteSet.hasNoteId(noteId) && noteId in noteCache.notes) {
|
|
||||||
resultNoteSet.add(noteCache.notes[noteId]);
|
resultNoteSet.add(noteCache.notes[noteId]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,18 +26,21 @@ class NoteContentUnprotectedFulltextExp extends Expression {
|
|||||||
FROM notes JOIN note_contents USING (noteId)
|
FROM notes JOIN note_contents USING (noteId)
|
||||||
WHERE type IN ('text', 'code') AND isDeleted = 0 AND isProtected = 0`)) {
|
WHERE type IN ('text', 'code') AND isDeleted = 0 AND isProtected = 0`)) {
|
||||||
|
|
||||||
content = content.toString().toLowerCase();
|
if (!inputNoteSet.hasNoteId(noteId) || !(noteId in noteCache.notes)) {
|
||||||
|
|
||||||
if (type === 'text' && mime === 'text/html') {
|
|
||||||
content = striptags(content);
|
|
||||||
content = content.replace(/ /g, ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.tokens.find(token => !content.includes(token))) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputNoteSet.hasNoteId(noteId) && noteId in noteCache.notes) {
|
content = content.toString().toLowerCase();
|
||||||
|
|
||||||
|
if (type === 'text' && mime === 'text/html') {
|
||||||
|
if (content.length < 20000) { // striptags is slow for very large notes
|
||||||
|
content = striptags(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
content = content.replace(/ /g, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.tokens.find(token => !content.includes(token))) {
|
||||||
resultNoteSet.add(noteCache.notes[noteId]);
|
resultNoteSet.add(noteCache.notes[noteId]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const Option = require('../entities/option');
|
|||||||
const TaskContext = require('./task_context.js');
|
const TaskContext = require('./task_context.js');
|
||||||
const migrationService = require('./migration');
|
const migrationService = require('./migration');
|
||||||
const cls = require('./cls');
|
const cls = require('./cls');
|
||||||
|
const config = require('./config');
|
||||||
|
|
||||||
const dbReady = utils.deferred();
|
const dbReady = utils.deferred();
|
||||||
|
|
||||||
@@ -131,6 +132,12 @@ function setDbAsInitialized() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dbReady.then(() => {
|
dbReady.then(() => {
|
||||||
|
if (config.General && config.General.noBackup === true) {
|
||||||
|
log.info("Disabling scheduled backups.");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setInterval(() => require('./backup').regularBackup(), 4 * 60 * 60 * 1000);
|
setInterval(() => require('./backup').regularBackup(), 4 * 60 * 60 * 1000);
|
||||||
|
|
||||||
// kickoff first backup soon after start up
|
// kickoff first backup soon after start up
|
||||||
|
|||||||
@@ -63,8 +63,6 @@
|
|||||||
<link href="libraries/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
<link href="libraries/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<script src="libraries/bootstrap/js/bootstrap.bundle.min.js"></script>
|
<script src="libraries/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
<link href="libraries/ckeditor/ckeditor-content.css" rel="stylesheet">
|
|
||||||
|
|
||||||
<!-- Include Fancytree skin and library -->
|
<!-- Include Fancytree skin and library -->
|
||||||
<link href="libraries/fancytree/skin-win8/ui.fancytree.css" rel="stylesheet">
|
<link href="libraries/fancytree/skin-win8/ui.fancytree.css" rel="stylesheet">
|
||||||
<script src="libraries/fancytree/jquery.fancytree-all-deps.min.js"></script>
|
<script src="libraries/fancytree/jquery.fancytree-all-deps.min.js"></script>
|
||||||
|
|||||||
@@ -127,8 +127,6 @@
|
|||||||
<link href="libraries/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
<link href="libraries/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<script src="libraries/bootstrap/js/bootstrap.bundle.min.js"></script>
|
<script src="libraries/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
<link href="libraries/ckeditor/ckeditor-content.css" rel="stylesheet">
|
|
||||||
|
|
||||||
<script src="app/mobile.js" crossorigin type="module"></script>
|
<script src="app/mobile.js" crossorigin type="module"></script>
|
||||||
|
|
||||||
<link href="stylesheets/themes.css" rel="stylesheet">
|
<link href="stylesheets/themes.css" rel="stylesheet">
|
||||||
|
|||||||
Reference in New Issue
Block a user