Compare commits

...

20 Commits

Author SHA1 Message Date
zadam
fb3d5f25ac release 0.45.9 2021-02-05 21:38:32 +01:00
zadam
9d7d79ef94 avoid errors from missing note cache during startup 2021-01-30 22:25:40 +01:00
zadam
ba33a0d330 fix conflict between CKEditor build content style and externally provided content style (which are not needed when build CSS is available), #1590 2021-01-29 20:27:16 +01:00
zadam
aea81c9872 fix include note rendering over floating elements, #1590 2021-01-29 20:09:03 +01:00
zadam
806ab22fa8 hide note paths dropdown on changed note, #1572 2021-01-27 20:44:58 +01:00
zadam
9c2b98915e fix glitch in switching between different types of notes 2021-01-23 21:52:39 +01:00
zadam
f2ca9276d6 fix/improve behavior of "sorted" attribute 2021-01-23 21:00:59 +01:00
zadam
48b697f408 select attr name in attr detail dialog did not trigger update, closes #1557 2021-01-21 20:18:33 +01:00
zadam
e1e185e5db added "safe mode" environment variable switch which disables startup scripts and resets tabs 2021-01-15 20:12:14 +01:00
zadam
e06c5703ee config value to disable backups, closes #1533 2021-01-12 21:50:05 +01:00
zadam
fe3bb2c5f6 release 0.45.8 2021-01-11 22:47:11 +01:00
zadam
6afc299efb release 0.45.8 2021-01-11 22:29:31 +01:00
zadam
369274ead7 new env variable to specify start note, #1532 2021-01-11 22:29:02 +01:00
zadam
04e6431c09 use correct class in exported HTMLs so that content style is applied, #1504 2020-12-30 23:20:12 +01:00
zadam
e89057a771 search note content only if not excluded by other expressions 2020-12-25 20:46:04 +01:00
zadam
4f27254e64 fix top margin of images and tables which can obscure their handles 2020-12-25 13:01:35 +01:00
zadam
577dc95ab8 convert   into whitespace also for large notes 2020-12-25 12:55:16 +01:00
zadam
a266d6a3d5 don't strip tags for very large text notes, #1500 2020-12-24 23:37:21 +01:00
zadam
749b6cb57e don't strip tags for very large text notes, #1500 2020-12-24 23:33:42 +01:00
zadam
b0b2951ff6 cleanup 2020-12-22 22:30:04 +01:00
24 changed files with 140 additions and 57 deletions

View File

@@ -5,6 +5,9 @@ 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

View File

@@ -2,7 +2,7 @@
"name": "trilium", "name": "trilium",
"productName": "Trilium Notes", "productName": "Trilium Notes",
"description": "Trilium Notes", "description": "Trilium Notes",
"version": "0.45.7", "version": "0.45.9",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"main": "electron.js", "main": "electron.js",
"bin": { "bin": {

View File

@@ -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({

View File

@@ -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}) {

View File

@@ -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);
} }

View File

@@ -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) {

View File

@@ -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;
} }

View File

@@ -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);

View File

@@ -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 */

View File

@@ -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);

View File

@@ -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) {

View File

@@ -1 +1 @@
module.exports = { buildDate:"2020-12-22T20:21:15+01:00", buildRevision: "bdfd760b9d428dc29c45a0e016d12a25af220043" }; module.exports = { buildDate:"2021-02-05T21:38:32+01:00", buildRevision: "9d7d79ef94bc7734393ae7f89993e76bbc7d97e3" };

View File

@@ -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();
} }

View File

@@ -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);
} }
} }
} }
@@ -80,6 +85,16 @@ eventService.subscribe(eventService.ENTITY_CREATED, ({ entityName, entity }) =>
} }
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') {

View File

@@ -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;
} }

View File

@@ -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();

View File

@@ -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() {

View File

@@ -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);
}
}); });

View File

@@ -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');

View File

@@ -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(/&nbsp;/g, ' '); content = content.replace(/&nbsp;/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]);
} }
} }

View File

@@ -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(/&nbsp;/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(/&nbsp;/g, ' ');
}
if (!this.tokens.find(token => !content.includes(token))) {
resultNoteSet.add(noteCache.notes[noteId]); resultNoteSet.add(noteCache.notes[noteId]);
} }
} }

View File

@@ -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

View File

@@ -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>

View File

@@ -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">