mirror of
https://github.com/zadam/trilium.git
synced 2025-10-29 17:26:38 +01:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -1,10 +1,13 @@
|
||||
[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=
|
||||
|
||||
# set to true to allow using Trilium without authentication (makes sense for server build only, desktop build doesn't need password)
|
||||
noAuthentication=false
|
||||
|
||||
# set to true to disable backups (e.g. because of limited space on server)
|
||||
noBackup=false
|
||||
|
||||
# Disable automatically generating desktop icon
|
||||
# noDesktopIcon=true
|
||||
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "trilium",
|
||||
"version": "0.45.5",
|
||||
"version": "0.45.6",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.45.6",
|
||||
"version": "0.45.9",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
|
||||
@@ -51,6 +51,12 @@ const TPL = `
|
||||
<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">
|
||||
</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>
|
||||
@@ -117,6 +123,13 @@ export default class ProtectedSessionOptions {
|
||||
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.on('change', () => {
|
||||
|
||||
@@ -75,14 +75,16 @@ class NoteShort {
|
||||
this.parentToBranch[parentNoteId] = branchId;
|
||||
}
|
||||
|
||||
addChild(childNoteId, branchId) {
|
||||
addChild(childNoteId, branchId, sort = true) {
|
||||
if (!this.children.includes(childNoteId)) {
|
||||
this.children.push(childNoteId);
|
||||
}
|
||||
|
||||
this.childToBranch[childNoteId] = branchId;
|
||||
|
||||
this.sortChildren();
|
||||
if (sort) {
|
||||
this.sortChildren();
|
||||
}
|
||||
}
|
||||
|
||||
sortChildren() {
|
||||
|
||||
@@ -8,8 +8,8 @@ async function syncNow() {
|
||||
toastService.showMessage("Sync finished successfully.");
|
||||
}
|
||||
else {
|
||||
if (result.message.length > 100) {
|
||||
result.message = result.message.substr(0, 100);
|
||||
if (result.message.length > 200) {
|
||||
result.message = result.message.substr(0, 200) + "...";
|
||||
}
|
||||
|
||||
toastService.showError("Sync failed: " + result.message);
|
||||
|
||||
@@ -87,6 +87,8 @@ class TreeCache {
|
||||
const branchRows = resp.branches;
|
||||
const attributeRows = resp.attributes;
|
||||
|
||||
const noteIdsToSort = new Set();
|
||||
|
||||
for (const noteRow of noteRows) {
|
||||
const {noteId} = noteRow;
|
||||
|
||||
@@ -153,7 +155,9 @@ class TreeCache {
|
||||
const parentNote = this.notes[branch.parentNoteId];
|
||||
|
||||
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) {
|
||||
|
||||
@@ -234,6 +234,8 @@ export default class AttributeDetailWidget extends TabAwareWidget {
|
||||
|
||||
this.$inputName = this.$widget.find('.attr-input-name');
|
||||
this.$inputName.on('keyup', () => this.userEditedAttribute());
|
||||
this.$inputName.on('change', () => this.userEditedAttribute());
|
||||
this.$inputName.on('autocomplete:closed', () => this.userEditedAttribute());
|
||||
|
||||
this.$inputName.on('focus', () => {
|
||||
attributeAutocompleteService.initAttributeNameAutocomplete({
|
||||
|
||||
@@ -248,13 +248,22 @@ export default class NoteDetailWidget extends TabAwareWidget {
|
||||
|
||||
this.$widget.find('.note-detail-printable:visible').printThis({
|
||||
header: $("<h2>").text(this.note && this.note.title).prop('outerHTML'),
|
||||
footer: "<script>document.body.className += ' ck-content printed-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,
|
||||
loadCSS: [
|
||||
"libraries/codemirror/codemirror.css",
|
||||
"libraries/ckeditor/ckeditor-content.css",
|
||||
"libraries/ckeditor/ckeditor-content.css",
|
||||
"libraries/bootstrap/css/bootstrap.min.css",
|
||||
"libraries/katex/katex.min.css",
|
||||
"stylesheets/print.css",
|
||||
"stylesheets/relation_map.css",
|
||||
"stylesheets/themes.css"
|
||||
|
||||
@@ -59,6 +59,7 @@ export default class NotePathsWidget extends TabAwareWidget {
|
||||
|
||||
this.$currentPath = this.$widget.find('.current-path');
|
||||
this.$dropdown = this.$widget.find(".dropdown");
|
||||
this.$dropdownToggle = this.$widget.find('.dropdown-toggle');
|
||||
|
||||
this.$notePathList = this.$dropdown.find(".note-path-list");
|
||||
|
||||
@@ -100,6 +101,8 @@ export default class NotePathsWidget extends TabAwareWidget {
|
||||
|
||||
parentNoteId = noteId;
|
||||
}
|
||||
|
||||
this.$dropdownToggle.dropdown('hide');
|
||||
}
|
||||
|
||||
async renderDropdown() {
|
||||
@@ -141,20 +144,20 @@ export default class NotePathsWidget extends TabAwareWidget {
|
||||
async addPath(notePath, isCurrent) {
|
||||
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");
|
||||
|
||||
noteLink
|
||||
$noteLink
|
||||
.find('a')
|
||||
.addClass("no-tooltip-preview");
|
||||
|
||||
if (isCurrent) {
|
||||
noteLink.addClass("current");
|
||||
$noteLink.addClass("current");
|
||||
}
|
||||
|
||||
this.$notePathList.append(noteLink);
|
||||
this.$notePathList.append($noteLink);
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({loadResults}) {
|
||||
|
||||
@@ -1198,7 +1198,25 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
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}) {
|
||||
if (!this.canBeMovedUpOrDown(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const beforeNode = node.getPrevSibling();
|
||||
|
||||
if (beforeNode !== null) {
|
||||
@@ -1207,7 +1225,12 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
}
|
||||
|
||||
moveNoteDownCommand({node}) {
|
||||
if (!this.canBeMovedUpOrDown(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const afterNode = node.getNextSibling();
|
||||
|
||||
if (afterNode !== null) {
|
||||
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.$noteTypeButton = this.$widget.find(".note-type-button");
|
||||
this.$noteTypeDesc = this.$widget.find(".note-type-desc");
|
||||
|
||||
this.$widget.on('click', '.dropdown-item',
|
||||
() => this.$widget.find('.dropdown-toggle').dropdown('toggle'));
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
@@ -64,8 +67,6 @@ export default class NoteTypeWidget extends TabAwareWidget {
|
||||
const noteType = NOTE_TYPES.find(nt => nt.type === type);
|
||||
|
||||
this.save(noteType.type, noteType.mime);
|
||||
|
||||
this.$widget.find('.dropdown-toggle').dropdown('toggle');
|
||||
});
|
||||
|
||||
if (this.note.type === noteType.type) {
|
||||
|
||||
@@ -38,7 +38,7 @@ const TPL = `
|
||||
cursor: text !important;
|
||||
}
|
||||
|
||||
.note-detail-editable-text *:first-child {
|
||||
.note-detail-editable-text *:not(figure):first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,11 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
border-radius: 10px;
|
||||
background-color: var(--accented-background-color);
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.include-note.ck-placeholder::before { /* remove placeholder in otherwise empty note */
|
||||
|
||||
@@ -23,11 +23,7 @@ function exportBranch(req, res) {
|
||||
|
||||
try {
|
||||
if (type === 'subtree' && (format === 'html' || format === 'markdown')) {
|
||||
const start = Date.now();
|
||||
|
||||
zipExportService.exportToZip(taskContext, branch, format, res);
|
||||
|
||||
console.log("Export took", Date.now() - start, "ms");
|
||||
}
|
||||
else if (type === 'single') {
|
||||
singleExportService.exportSingleNote(taskContext, branch, format, res);
|
||||
|
||||
@@ -193,6 +193,10 @@ function duplicateSubtree(req) {
|
||||
return noteService.duplicateSubtree(noteId, parentNoteId);
|
||||
}
|
||||
|
||||
function eraseDeletedNotesNow() {
|
||||
noteService.eraseDeletedNotesNow();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getNote,
|
||||
updateNote,
|
||||
@@ -204,5 +208,6 @@ module.exports = {
|
||||
setNoteTypeMime,
|
||||
getRelationMap,
|
||||
changeTitle,
|
||||
duplicateSubtree
|
||||
duplicateSubtree,
|
||||
eraseDeletedNotesNow
|
||||
};
|
||||
|
||||
@@ -54,11 +54,21 @@ function getBundlesWithLabel(label, value) {
|
||||
}
|
||||
|
||||
function getStartupBundles() {
|
||||
return getBundlesWithLabel("run", "frontendStartup");
|
||||
if (!process.env.TRILIUM_SAFE_MODE) {
|
||||
return getBundlesWithLabel("run", "frontendStartup");
|
||||
}
|
||||
else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function getWidgetBundles() {
|
||||
return getBundlesWithLabel("widget");
|
||||
if (!process.env.TRILIUM_SAFE_MODE) {
|
||||
return getBundlesWithLabel("widget");
|
||||
}
|
||||
else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function getRelationBundles(req) {
|
||||
|
||||
@@ -38,6 +38,8 @@ function saveSyncSeed(req) {
|
||||
}]
|
||||
}
|
||||
|
||||
log.info("Saved sync seed.");
|
||||
|
||||
sqlInit.createDatabaseForSync(options);
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ function getTree(req) {
|
||||
const noteIds = sql.getColumn(`
|
||||
WITH RECURSIVE
|
||||
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
|
||||
SELECT branches.noteId, branches.isExpanded FROM branches
|
||||
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);
|
||||
apiRoute(PUT, '/api/notes/:noteId/restore-revision/:noteRevisionId', noteRevisionsApiRoute.restoreNoteRevision);
|
||||
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(POST, '/api/notes/:noteId/duplicate/:parentNoteId', notesApiRoute.duplicateSubtree);
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
module.exports = { buildDate:"2020-12-04T22:08:24+01:00", buildRevision: "b7b1324dd0b2af6abc8cd5b98f620ca227582d4d" };
|
||||
module.exports = { buildDate:"2021-02-05T21:38:32+01:00", buildRevision: "9d7d79ef94bc7734393ae7f89993e76bbc7d97e3" };
|
||||
|
||||
@@ -650,7 +650,7 @@ class ConsistencyChecks {
|
||||
// root branch should always be expanded
|
||||
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
|
||||
|
||||
this.checkTreeCycles();
|
||||
|
||||
@@ -52,6 +52,10 @@ function encrypt(key, plainText, ivLength = 13) {
|
||||
}
|
||||
|
||||
function decrypt(key, cipherText, ivLength = 13) {
|
||||
if (cipherText === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!key) {
|
||||
return "[protected]";
|
||||
}
|
||||
@@ -93,6 +97,10 @@ function decrypt(key, cipherText, ivLength = 13) {
|
||||
function decryptString(dataKey, cipherText) {
|
||||
const buffer = decrypt(dataKey, cipherText);
|
||||
|
||||
if (buffer === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const str = buffer.toString('utf-8');
|
||||
|
||||
if (str === 'false') {
|
||||
@@ -108,4 +116,4 @@ module.exports = {
|
||||
encrypt,
|
||||
decrypt,
|
||||
decryptString
|
||||
};
|
||||
};
|
||||
|
||||
@@ -143,7 +143,7 @@ function exportToZip(taskContext, branch, format, res) {
|
||||
const available = !note.isProtected || protectedSessionService.isProtectedSessionAvailable();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ function exportToZip(taskContext, branch, format, res) {
|
||||
<link rel="stylesheet" href="${cssUrl}">
|
||||
<base target="_parent">
|
||||
</head>
|
||||
<body>
|
||||
<body class="ck-content">
|
||||
<h1>${utils.escapeHtml(title)}</h1>
|
||||
${content}
|
||||
</body>
|
||||
@@ -433,14 +433,13 @@ ${content}
|
||||
}
|
||||
|
||||
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-Type', 'application/zip');
|
||||
|
||||
zipFile.end();
|
||||
|
||||
zipFile.outputStream.pipe(res);
|
||||
zipFile.end();
|
||||
|
||||
taskContext.taskSucceeded();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ const scriptService = require('./script');
|
||||
const treeService = require('./tree');
|
||||
const noteService = require('./notes');
|
||||
const repository = require('./repository');
|
||||
const noteCache = require('./note_cache/note_cache');
|
||||
const Attribute = require('../entities/attribute');
|
||||
|
||||
function runAttachedRelations(note, relationName, originEntity) {
|
||||
@@ -22,11 +23,15 @@ eventService.subscribe(eventService.NOTE_TITLE_CHANGED, note => {
|
||||
runAttachedRelations(note, 'runOnNoteTitleChange', note);
|
||||
|
||||
if (!note.isRoot()) {
|
||||
const parents = note.getParentNotes();
|
||||
const noteFromCache = noteCache.notes[note.noteId];
|
||||
|
||||
for (const parent of parents) {
|
||||
if (parent.hasOwnedLabel("sorted")) {
|
||||
treeService.sortNotesAlphabetically(parent.noteId);
|
||||
if (!noteFromCache) {
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
note.setContent(templateNoteContent);
|
||||
}
|
||||
|
||||
note.type = templateNote.type;
|
||||
note.mime = templateNote.mime;
|
||||
note.save();
|
||||
}
|
||||
|
||||
noteService.duplicateSubtreeWithoutRoot(templateNote.noteId, note.noteId);
|
||||
}
|
||||
else if (entity.type === 'label' && entity.name === 'sorted') {
|
||||
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') {
|
||||
@@ -90,10 +109,10 @@ eventService.subscribe(eventService.CHILD_NOTE_CREATED, ({ parentNote, childNote
|
||||
function processInverseRelations(entityName, entity, handler) {
|
||||
if (entityName === 'attributes' && entity.type === 'relation') {
|
||||
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) {
|
||||
const definition = attribute.value;
|
||||
for (const relDefinition of relDefinitions) {
|
||||
const definition = relDefinition.getDefinition();
|
||||
|
||||
if (definition.inverseRelation && definition.inverseRelation.trim()) {
|
||||
const targetNote = entity.getTargetNote();
|
||||
|
||||
@@ -133,6 +133,14 @@ class Note {
|
||||
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) {
|
||||
const label = this.attributes.find(attr => attr.type === 'label' && attr.name === name);
|
||||
|
||||
@@ -275,6 +283,11 @@ class Note {
|
||||
return arr.flat();
|
||||
}
|
||||
|
||||
/** @return {String[]} */
|
||||
get subtreeNoteIds() {
|
||||
return this.subtreeNotes.map(note => note.noteId);
|
||||
}
|
||||
|
||||
get parentCount() {
|
||||
return this.parents.length;
|
||||
}
|
||||
@@ -338,7 +351,7 @@ class Note {
|
||||
|
||||
decrypt() {
|
||||
if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) {
|
||||
this.title = protectedSessionService.decryptString(note.title);
|
||||
this.title = protectedSessionService.decryptString(this.title);
|
||||
|
||||
this.isDecrypted = true;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
const Note = require('./entities/note');
|
||||
const Branch = require('./entities/branch');
|
||||
const Attribute = require('./entities/attribute');
|
||||
|
||||
class NoteCache {
|
||||
constructor() {
|
||||
this.reset();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
const NoteRevision = require('../entities/note_revision');
|
||||
const dateUtils = require('../services/date_utils');
|
||||
const log = require('../services/log');
|
||||
|
||||
/**
|
||||
* @param {Note} note
|
||||
@@ -9,14 +10,21 @@ const dateUtils = require('../services/date_utils');
|
||||
function protectNoteRevisions(note) {
|
||||
for (const revision of note.getRevisions()) {
|
||||
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
|
||||
revision.setContent(content);
|
||||
// this will force de/encryption
|
||||
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) {
|
||||
if (protect !== note.isProtected) {
|
||||
const content = note.getContent();
|
||||
try {
|
||||
if (protect !== note.isProtected) {
|
||||
const content = note.getContent();
|
||||
|
||||
note.isProtected = protect;
|
||||
note.isProtected = protect;
|
||||
|
||||
// this will force de/encryption
|
||||
note.setContent(content);
|
||||
// this will force de/encryption
|
||||
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) {
|
||||
@@ -668,8 +675,10 @@ function scanForLinks(note) {
|
||||
}
|
||||
}
|
||||
|
||||
function eraseDeletedNotes() {
|
||||
const eraseNotesAfterTimeInSeconds = optionService.getOptionInt('eraseNotesAfterTimeInSeconds');
|
||||
function eraseDeletedNotes(eraseNotesAfterTimeInSeconds = null) {
|
||||
if (eraseNotesAfterTimeInSeconds === null) {
|
||||
eraseNotesAfterTimeInSeconds = optionService.getOptionInt('eraseNotesAfterTimeInSeconds');
|
||||
}
|
||||
|
||||
const cutoffDate = new Date(Date.now() - eraseNotesAfterTimeInSeconds * 1000);
|
||||
|
||||
@@ -719,6 +728,10 @@ function eraseDeletedNotes() {
|
||||
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
|
||||
function replaceByMap(str, mapObj) {
|
||||
const re = new RegExp(Object.keys(mapObj).join("|"),"g");
|
||||
@@ -825,9 +838,9 @@ function getNoteIdMapping(origNote) {
|
||||
|
||||
sqlInit.dbReady.then(() => {
|
||||
// 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 = {
|
||||
@@ -841,5 +854,6 @@ module.exports = {
|
||||
duplicateSubtree,
|
||||
duplicateSubtreeWithoutRoot,
|
||||
getUndeletedParentBranches,
|
||||
triggerNoteTitleChanged
|
||||
triggerNoteTitleChanged,
|
||||
eraseDeletedNotesNow
|
||||
};
|
||||
|
||||
@@ -31,10 +31,7 @@ function initNotSyncedOptions(initialized, startNotePath = 'root', opts = {}) {
|
||||
optionService.createOption('openTabs', JSON.stringify([
|
||||
{
|
||||
notePath: startNotePath,
|
||||
active: true,
|
||||
sidebar: {
|
||||
widgets: []
|
||||
}
|
||||
active: true
|
||||
}
|
||||
]), false);
|
||||
|
||||
@@ -103,6 +100,15 @@ function initStartupOptions() {
|
||||
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() {
|
||||
|
||||
@@ -43,10 +43,18 @@ function decryptNotes(notes) {
|
||||
}
|
||||
|
||||
function encrypt(plainText) {
|
||||
if (plainText === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return dataEncryptionService.encrypt(getDataKey(), plainText);
|
||||
}
|
||||
|
||||
function decrypt(cipherText) {
|
||||
if (cipherText === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return dataEncryptionService.decrypt(getDataKey(), cipherText);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,9 +22,11 @@ function runNotesWithLabel(runAttrValue) {
|
||||
}
|
||||
|
||||
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 repository = require('./repository');
|
||||
const cls = require('./cls');
|
||||
|
||||
@@ -32,26 +32,29 @@ class NoteContentProtectedFulltextExp extends Expression {
|
||||
FROM notes JOIN note_contents USING (noteId)
|
||||
WHERE type IN ('text', 'code') AND isDeleted = 0 AND isProtected = 1`)) {
|
||||
|
||||
if (!inputNoteSet.hasNoteId(noteId) || !(noteId in noteCache.notes)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
content = protectedSessionService.decryptString(content);
|
||||
}
|
||||
catch (e) {
|
||||
log.info('Cannot decrypt content of note', noteId);
|
||||
log.info(`Cannot decrypt content of note ${noteId}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
content = content.toLowerCase();
|
||||
|
||||
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, ' ');
|
||||
}
|
||||
|
||||
if (this.tokens.find(token => !content.includes(token))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inputNoteSet.hasNoteId(noteId) && noteId in noteCache.notes) {
|
||||
if (!this.tokens.find(token => !content.includes(token))) {
|
||||
resultNoteSet.add(noteCache.notes[noteId]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,18 +26,21 @@ class NoteContentUnprotectedFulltextExp extends Expression {
|
||||
FROM notes JOIN note_contents USING (noteId)
|
||||
WHERE type IN ('text', 'code') AND isDeleted = 0 AND isProtected = 0`)) {
|
||||
|
||||
content = content.toString().toLowerCase();
|
||||
|
||||
if (type === 'text' && mime === 'text/html') {
|
||||
content = striptags(content);
|
||||
content = content.replace(/ /g, ' ');
|
||||
}
|
||||
|
||||
if (this.tokens.find(token => !content.includes(token))) {
|
||||
if (!inputNoteSet.hasNoteId(noteId) || !(noteId in noteCache.notes)) {
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ const Option = require('../entities/option');
|
||||
const TaskContext = require('./task_context.js');
|
||||
const migrationService = require('./migration');
|
||||
const cls = require('./cls');
|
||||
const config = require('./config');
|
||||
|
||||
const dbReady = utils.deferred();
|
||||
|
||||
@@ -131,6 +132,12 @@ function setDbAsInitialized() {
|
||||
}
|
||||
|
||||
dbReady.then(() => {
|
||||
if (config.General && config.General.noBackup === true) {
|
||||
log.info("Disabling scheduled backups.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setInterval(() => require('./backup').regularBackup(), 4 * 60 * 60 * 1000);
|
||||
|
||||
// kickoff first backup soon after start up
|
||||
|
||||
@@ -63,8 +63,6 @@
|
||||
<link href="libraries/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="libraries/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<link href="libraries/ckeditor/ckeditor-content.css" rel="stylesheet">
|
||||
|
||||
<!-- Include Fancytree skin and library -->
|
||||
<link href="libraries/fancytree/skin-win8/ui.fancytree.css" rel="stylesheet">
|
||||
<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">
|
||||
<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>
|
||||
|
||||
<link href="stylesheets/themes.css" rel="stylesheet">
|
||||
|
||||
Reference in New Issue
Block a user