mirror of
https://github.com/zadam/trilium.git
synced 2025-10-30 18:05:55 +01:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f3d73b9fd | ||
|
|
bdfd760b9d | ||
|
|
7133e60267 | ||
|
|
fc4edf4aa7 | ||
|
|
eaf93a70cd | ||
|
|
b093569ec5 | ||
|
|
4633c68a0c | ||
|
|
33571e0ef3 | ||
|
|
31876d2cf9 | ||
|
|
81c6043cb6 | ||
|
|
1982d054ef | ||
|
|
e56979c482 | ||
|
|
58555b3660 | ||
|
|
b7b1324dd0 | ||
|
|
e318acc977 | ||
|
|
8ae82f5b69 | ||
|
|
26442f418a | ||
|
|
23a432e7d8 | ||
|
|
984ecaf99c | ||
|
|
21b73a86b2 | ||
|
|
7d8277699c | ||
|
|
928ed7a034 |
@@ -1,5 +1,5 @@
|
|||||||
[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)
|
||||||
|
|||||||
@@ -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.7",
|
||||||
"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});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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:"2020-12-22T20:21:15+01:00", buildRevision: "bdfd760b9d428dc29c45a0e016d12a25af220043" };
|
||||||
|
|||||||
@@ -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') {
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ 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);
|
||||||
@@ -90,10 +94,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)"
|
||||||
|
|||||||
@@ -338,7 +338,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user