diff --git a/db/demo.tar b/db/demo.tar
index 5ca9b7de2..bc8deb825 100644
Binary files a/db/demo.tar and b/db/demo.tar differ
diff --git a/docs/backend_api/entities_note.js.html b/docs/backend_api/entities_note.js.html
index 9b6acb3a0..491a86100 100644
--- a/docs/backend_api/entities_note.js.html
+++ b/docs/backend_api/entities_note.js.html
@@ -553,7 +553,7 @@ class Note extends Entity {
const attributes = await this.loadOwnedAttributesToCache();
for (const attribute of attributes) {
- if (attribute.type === type && (value === undefined || value === attribute.value)) {
+ if (attribute.type === type && attribute.name === name && (value === undefined || value === attribute.value)) {
attribute.isDeleted = true;
await attribute.save();
diff --git a/docs/backend_api/services_backend_script_api.js.html b/docs/backend_api/services_backend_script_api.js.html
index 8c76aa124..673fb043a 100644
--- a/docs/backend_api/services_backend_script_api.js.html
+++ b/docs/backend_api/services_backend_script_api.js.html
@@ -232,7 +232,7 @@ function BackendScriptApi(currentNote, apiParams) {
this.createDataNote = async (parentNoteId, title, content = {}) => await noteService.createNewNote({
parentNoteId,
title,
- content: JSON.stringify(content),
+ content: JSON.stringify(content, null, '\t'),
type: 'code',
mime: 'application/json'
});
diff --git a/docs/frontend_api/FrontendScriptApi.html b/docs/frontend_api/FrontendScriptApi.html
index bcb2edccb..4197e7611 100644
--- a/docs/frontend_api/FrontendScriptApi.html
+++ b/docs/frontend_api/FrontendScriptApi.html
@@ -807,7 +807,7 @@
- Activates newly created note. Compared to this.activateNote() also refreshes tree.
+ Activates newly created note. Compared to this.activateNote() also makes sure that frontend has been fully synced.
diff --git a/src/public/javascripts/services/frontend_script_api.js b/src/public/javascripts/services/frontend_script_api.js
index 06bc5e839..a141c8f19 100644
--- a/src/public/javascripts/services/frontend_script_api.js
+++ b/src/public/javascripts/services/frontend_script_api.js
@@ -58,13 +58,13 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
};
/**
- * Activates newly created note. Compared to this.activateNote() also refreshes tree.
+ * Activates newly created note. Compared to this.activateNote() also makes sure that frontend has been fully synced.
*
* @param {string} notePath (or noteId)
* @return {Promise}
*/
this.activateNewNote = async notePath => {
- await treeService.reload();
+ await ws.waitForMaxKnownSyncId();
await treeService.activateNote(notePath, noteDetailService.focusAndSelectTitle);
};
diff --git a/src/public/javascripts/services/note_detail_book.js b/src/public/javascripts/services/note_detail_book.js
index 6fad28878..2a6d9bea8 100644
--- a/src/public/javascripts/services/note_detail_book.js
+++ b/src/public/javascripts/services/note_detail_book.js
@@ -43,6 +43,7 @@ class NoteDetailBook {
this.$zoomInButton = this.$component.find('.book-zoom-in-button');
this.$zoomOutButton = this.$component.find('.book-zoom-out-button');
this.$expandChildrenButton = this.$component.find('.expand-children-button');
+ this.$help = this.$component.find('.note-detail-book-help');
this.$zoomInButton.on('click', () => this.setZoom(this.zoomLevel - 1));
this.$zoomOutButton.on('click', () => this.setZoom(this.zoomLevel + 1));
@@ -105,6 +106,7 @@ class NoteDetailBook {
async render() {
this.$content.empty();
+ this.$help.hide();
if (this.isAutoBook()) {
const $addTextLink = $('here').on('click', () => {
@@ -124,7 +126,9 @@ class NoteDetailBook {
}
async renderIntoElement(note, $container) {
- for (const childNote of await note.getChildNotes()) {
+ const childNotes = await note.getChildNotes();
+
+ for (const childNote of childNotes) {
const childNotePath = this.ctx.notePath + '/' + childNote.noteId;
const {type, renderedContent} = await noteContentRenderer.getRenderedContent(childNote);
@@ -152,6 +156,10 @@ class NoteDetailBook {
$container.append($card);
}
+
+ if (childNotes.length === 0) {
+ this.$help.show();
+ }
}
/** @return {boolean} true if this is "auto book" activated (empty text note) and not explicit book note */
diff --git a/src/public/javascripts/services/note_detail_text.js b/src/public/javascripts/services/note_detail_text.js
index 068cedd46..d81d5aed0 100644
--- a/src/public/javascripts/services/note_detail_text.js
+++ b/src/public/javascripts/services/note_detail_text.js
@@ -116,15 +116,19 @@ class NoteDetailText {
}
getContent() {
- let content = this.textEditor.getData();
+ const content = this.textEditor.getData();
// if content is only tags/whitespace (typically
), then just make it empty
// this is important when setting new note to code
- if (jQuery(content).text().trim() === '' && !content.includes("").html(html).text().trim().length === 0 && !html.toLowerCase().includes('").html(html).text().trim().length === 0
+ && !html.includes('
+
-
Note ID:
-
+
Note ID:
+
-
Created:
-
+
Created:
+
-
Modified:
-
+
Modified:
+
Type:
@@ -39,10 +39,19 @@ class NoteInfoWidget extends StandardWidget {
const note = this.ctx.note;
$noteId.text(note.noteId);
- $dateCreated.text(note.dateCreated);
- $dateModified.text(note.dateModified);
+ $dateCreated
+ .text(note.dateCreated)
+ .attr("title", note.dateCreated);
+
+ $dateModified
+ .text(note.dateModified)
+ .attr("title", note.dateCreated);
+
$type.text(note.type);
- $mime.text(note.mime).attr("title", note.mime);
+
+ $mime
+ .text(note.mime)
+ .attr("title", note.mime);
}
eventReceived(name, data) {
diff --git a/src/public/stylesheets/desktop.css b/src/public/stylesheets/desktop.css
index deae323c0..affa5d15a 100644
--- a/src/public/stylesheets/desktop.css
+++ b/src/public/stylesheets/desktop.css
@@ -407,7 +407,7 @@ body {
position: fixed;
bottom: 10px;
right: 10px;
- z-index: 100000;
+ z-index: 1000;
}
#right-pane {
diff --git a/src/public/stylesheets/style.css b/src/public/stylesheets/style.css
index 456f04f24..3deaeebf6 100644
--- a/src/public/stylesheets/style.css
+++ b/src/public/stylesheets/style.css
@@ -594,7 +594,7 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
padding: 10px;
}
-.note-detail-render-help {
+.note-detail-render-help, .note-detail-book-help {
margin: 50px;
padding: 20px;
}
@@ -967,4 +967,14 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
padding: 20px;
border-radius: 10px;
background-color: var(--accented-background-color);
+}
+
+.include-note.ck-placeholder::before { /* remove placeholder in otherwise empty note */
+ content: '' !important;
+}
+
+.alert-warning {
+ color: var(--main-text-color) !important;
+ background-color: var(--accented-background-color) !important;
+ border-color: var(--main-border-color) !important;
}
\ No newline at end of file
diff --git a/src/services/backend_script_api.js b/src/services/backend_script_api.js
index de0b29212..b5f9e72f1 100644
--- a/src/services/backend_script_api.js
+++ b/src/services/backend_script_api.js
@@ -204,7 +204,7 @@ function BackendScriptApi(currentNote, apiParams) {
this.createDataNote = async (parentNoteId, title, content = {}) => await noteService.createNewNote({
parentNoteId,
title,
- content: JSON.stringify(content),
+ content: JSON.stringify(content, null, '\t'),
type: 'code',
mime: 'application/json'
});
diff --git a/src/services/build.js b/src/services/build.js
index 79af4b055..a7fd163b3 100644
--- a/src/services/build.js
+++ b/src/services/build.js
@@ -1 +1 @@
-module.exports = { buildDate:"2020-01-02T10:43:41+01:00", buildRevision: "cb79f2c7eb51904537307f4ffc1135a7383da29f" };
+module.exports = { buildDate:"2020-01-04T22:01:20+01:00", buildRevision: "3b8b4da149fbc1b17d09253693823f5135a55f2e" };
diff --git a/src/services/build_search_query.js b/src/services/build_search_query.js
index 40c422f57..165ca3c42 100644
--- a/src/services/build_search_query.js
+++ b/src/services/build_search_query.js
@@ -31,7 +31,8 @@ module.exports = function(filters, selectedColumns = 'notes.*') {
// can match notes because @tag can be both "shopping" and "christmas"
const alias = "attr_" + property + "_" + attrFilterId++;
- joins[alias] = `LEFT JOIN attributes AS ${alias} `
+ // forcing to use particular index since SQLite query planner would often choose something pretty bad
+ joins[alias] = `LEFT JOIN attributes AS ${alias} INDEXED BY IDX_attributes_noteId_index `
+ `ON ${alias}.noteId = notes.noteId `
+ `AND ${alias}.name = '${property}' AND ${alias}.isDeleted = 0`;
diff --git a/src/services/image.js b/src/services/image.js
index f7b892b67..96fa6cca7 100644
--- a/src/services/image.js
+++ b/src/services/image.js
@@ -75,14 +75,17 @@ async function saveImage(parentNoteId, uploadBuffer, originalName, shrinkImageSw
}
async function shrinkImage(buffer, originalName) {
- const resizedImage = await resize(buffer);
+ // we do resizing with max (100) quality which will be trimmed during optimization step next
+ const resizedImage = await resize(buffer, 100);
let finalImageBuffer;
+ const jpegQuality = await optionService.getOptionInt('imageJpegQuality');
+
try {
- finalImageBuffer = await optimize(resizedImage);
+ finalImageBuffer = await optimize(resizedImage, jpegQuality);
} catch (e) {
log.error("Failed to optimize image '" + originalName + "'\nStack: " + e.stack);
- finalImageBuffer = resizedImage;
+ finalImageBuffer = await resize(buffer, jpegQuality);
}
// if resizing & shrinking did not help with size then save the original
@@ -94,7 +97,7 @@ async function shrinkImage(buffer, originalName) {
return finalImageBuffer;
}
-async function resize(buffer) {
+async function resize(buffer, quality) {
const imageMaxWidthHeight = await optionService.getOptionInt('imageMaxWidthHeight');
const image = await jimp.read(buffer);
@@ -106,8 +109,7 @@ async function resize(buffer) {
image.resize(jimp.AUTO, imageMaxWidthHeight);
}
- // we do resizing with max quality which will be trimmed during optimization step next
- image.quality(100);
+ image.quality(quality);
// when converting PNG to JPG we lose alpha channel, this is replaced by white to match Trilium white background
image.background(0xFFFFFFFF);
@@ -115,11 +117,11 @@ async function resize(buffer) {
return image.getBufferAsync(jimp.MIME_JPEG);
}
-async function optimize(buffer) {
+async function optimize(buffer, jpegQuality) {
return await imagemin.buffer(buffer, {
plugins: [
imageminMozJpeg({
- quality: await optionService.getOptionInt('imageJpegQuality')
+ quality: jpegQuality
}),
imageminPngQuant({
quality: [0, 0.7]
diff --git a/src/services/notes.js b/src/services/notes.js
index 3590dfe4f..715dc1b84 100644
--- a/src/services/notes.js
+++ b/src/services/notes.js
@@ -48,7 +48,7 @@ function deriveMime(type, mime) {
mime = 'text/plain';
} else if (['relation-map', 'search'].includes(type)) {
mime = 'application/json';
- } else if (type === 'render') {
+ } else if (['render', 'book'].includes(type)) {
mime = '';
}
diff --git a/src/services/task_context.js b/src/services/task_context.js
index 4731fed44..9c4c354b0 100644
--- a/src/services/task_context.js
+++ b/src/services/task_context.js
@@ -12,8 +12,14 @@ class TaskContext {
this.data = data;
// progressCount is meant to represent just some progress - to indicate the task is not stuck
- this.progressCount = 0;
- this.lastSentCountTs = Date.now();
+ this.progressCount = -1; // we're incrementing immediatelly
+ this.lastSentCountTs = 0; // 0 will guarantee first message will be sent
+
+ // just the fact this has been initialized is a progress which should be sent to clients
+ // this is esp. important when importing big files/images which take long time to upload/process
+ // which means that first "real" increaseProgressCount() will be called quite late and user is without
+ // feedback until then
+ this.increaseProgressCount();
}
/** @return {TaskContext} */
diff --git a/src/views/details/book.ejs b/src/views/details/book.ejs
index 2b843bacd..60085228f 100644
--- a/src/views/details/book.ejs
+++ b/src/views/details/book.ejs
@@ -13,5 +13,9 @@
title="Zoom Out">
+
+ This note of type Book doesn't have any child notes so there's nothing to display. See wiki for details.
+
+
\ No newline at end of file
diff --git a/src/views/sidebar.ejs b/src/views/sidebar.ejs
index 55378a980..82f4f9ecf 100644
--- a/src/views/sidebar.ejs
+++ b/src/views/sidebar.ejs
@@ -1,5 +1,5 @@
-
+