From 1b68adf3e4d05e0b621aecf5f75963c945263d64 Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 15 Jun 2023 21:34:02 +0200 Subject: [PATCH 1/9] compatibility with online excalidraw tool --- src/public/app/widgets/type_widgets/canvas.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/public/app/widgets/type_widgets/canvas.js b/src/public/app/widgets/type_widgets/canvas.js index b4c92c4f0..ae4088d6c 100644 --- a/src/public/app/widgets/type_widgets/canvas.js +++ b/src/public/app/widgets/type_widgets/canvas.js @@ -285,6 +285,8 @@ export default class ExcalidrawTypeWidget extends TypeWidget { }) const content = { + type: "excalidraw", + version: 2, _meta: "This note has type `canvas`. It uses excalidraw and stores an exported svg alongside.", elements, // excalidraw appState, // excalidraw From 74400dad9712a884adcb1bb6c17656db5e217d34 Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 15 Jun 2023 21:51:41 +0200 Subject: [PATCH 2/9] fix race condition between script execution and saving, closes #4028 --- src/public/app/widgets/note_detail.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/public/app/widgets/note_detail.js b/src/public/app/widgets/note_detail.js index aaf2c568d..2346c041e 100644 --- a/src/public/app/widgets/note_detail.js +++ b/src/public/app/widgets/note_detail.js @@ -230,6 +230,15 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { } } + async runActiveNoteCommand(params) { + if (this.isNoteContext(params.ntxId)) { + // make sure that script is saved before running it #4028 + await this.spacedUpdate.updateNowIfNecessary(); + } + + return await this.parent.triggerCommand('runActiveNote', params); + } + async printActiveNoteEvent() { if (!this.noteContext.isActive()) { return; From 3223e767875e5379c99ff58a562cb9c1a2641bdf Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 15 Jun 2023 23:21:40 +0200 Subject: [PATCH 3/9] etapi ZIP import --- src/etapi/etapi.openapi.yaml | 38 +++++++++++++++++++++++++++++------- src/etapi/notes.js | 15 ++++++++++++-- src/services/task_context.js | 2 +- test-etapi/import-zip.http | 12 ++++++++++++ 4 files changed, 57 insertions(+), 10 deletions(-) create mode 100644 test-etapi/import-zip.http diff --git a/src/etapi/etapi.openapi.yaml b/src/etapi/etapi.openapi.yaml index 754fb05b3..c510fa0d2 100644 --- a/src/etapi/etapi.openapi.yaml +++ b/src/etapi/etapi.openapi.yaml @@ -33,13 +33,7 @@ paths: content: application/json; charset=utf-8: schema: - properties: - note: - $ref: '#/components/schemas/Note' - description: Created note - branch: - $ref: '#/components/schemas/Branch' - description: Created branch + $ref: '#/components/schemas/NoteWithBranch' default: description: unexpected error content: @@ -291,6 +285,29 @@ paths: application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' + /notes/{noteId}/import: + parameters: + - name: noteId + in: path + required: true + schema: + $ref: '#/components/schemas/EntityId' + post: + description: Imports ZIP file into a given note. + operationId: importZip + responses: + '201': + description: note created + content: + application/json; charset=utf-8: + schema: + $ref: '#/components/schemas/NoteWithBranch' + default: + description: unexpected error + content: + application/json; charset=utf-8: + schema: + $ref: '#/components/schemas/Error' /notes/{noteId}/note-revision: parameters: - name: noteId @@ -852,6 +869,13 @@ components: utcDateModified: $ref: '#/components/schemas/UtcDateTime' readOnly: true + NoteWithBranch: + type: object + properties: + note: + $ref: '#/components/schemas/Note' + branch: + $ref: '#/components/schemas/Branch' Attribute: type: object description: Attribute (Label, Relation) is a key-value record attached to a note. diff --git a/src/etapi/notes.js b/src/etapi/notes.js index 683544fff..9d56cb966 100644 --- a/src/etapi/notes.js +++ b/src/etapi/notes.js @@ -8,6 +8,7 @@ const v = require("./validators"); const searchService = require("../services/search/services/search"); const SearchContext = require("../services/search/search_context"); const zipExportService = require("../services/export/zip"); +const zipImportService = require("../services/import/zip"); function register(router) { eu.route(router, 'get', '/etapi/notes', (req, res, next) => { @@ -141,11 +142,21 @@ function register(router) { // (e.g. branchIds are not seen in UI), that we export "note export" instead. const branch = note.getParentBranches()[0]; - console.log(note.getParentBranches()); - zipExportService.exportToZip(taskContext, branch, format, res); }); + eu.route(router, 'post' ,'/etapi/notes/:noteId/import', (req, res, next) => { + const note = eu.getAndCheckNote(req.params.noteId); + const taskContext = new TaskContext('no-progress-reporting'); + + zipImportService.importZip(taskContext, req.body, note).then(importedNote => { + res.status(201).json({ + note: mappers.mapNoteToPojo(importedNote), + branch: mappers.mapBranchToPojo(importedNote.getBranches()[0]), + }); + }); // we need better error handling here, async errors won't be properly processed. + }); + eu.route(router, 'post' ,'/etapi/notes/:noteId/note-revision', (req, res, next) => { const note = eu.getAndCheckNote(req.params.noteId); diff --git a/src/services/task_context.js b/src/services/task_context.js index 9aab9d43e..363bd3035 100644 --- a/src/services/task_context.js +++ b/src/services/task_context.js @@ -6,7 +6,7 @@ const ws = require('./ws'); const taskContexts = {}; class TaskContext { - constructor(taskId, taskType = null, data = null) { + constructor(taskId, taskType = null, data = {}) { this.taskId = taskId; this.taskType = taskType; this.data = data; diff --git a/test-etapi/import-zip.http b/test-etapi/import-zip.http new file mode 100644 index 000000000..e831a050a --- /dev/null +++ b/test-etapi/import-zip.http @@ -0,0 +1,12 @@ +POST {{triliumHost}}/etapi/notes/root/import +Authorization: {{authToken}} +Content-Type: application/octet-stream +Content-Transfer-Encoding: binary + +< ../db/demo.zip + +> {% + client.assert(response.status === 201); + client.assert(response.body.note.title == "Trilium Demo"); + client.assert(response.body.branch.parentNoteId == "root"); +%} From e22f77eae7ba6f6bbc9b89254db48fd03e3b81a2 Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 15 Jun 2023 23:23:37 +0200 Subject: [PATCH 4/9] release 0.60.3 --- package.json | 2 +- src/services/build.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 43b1241f2..090d13924 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "trilium", "productName": "Trilium Notes", "description": "Trilium Notes", - "version": "0.60.2-beta", + "version": "0.60.3", "license": "AGPL-3.0-only", "main": "electron.js", "bin": { diff --git a/src/services/build.js b/src/services/build.js index c539d9bd7..2386ee8af 100644 --- a/src/services/build.js +++ b/src/services/build.js @@ -1 +1 @@ -module.exports = { buildDate:"2023-06-08T22:46:52+02:00", buildRevision: "6e69cafe5419e8efcc6f652647f9227dbcfa1e18" }; +module.exports = { buildDate:"2023-06-15T23:23:37+02:00", buildRevision: "3223e767875e5379c99ff58a562cb9c1a2641bdf" }; From 691fccb769611a9e2e051cd2cc904f9ba7ad2050 Mon Sep 17 00:00:00 2001 From: zadam Date: Sun, 18 Jun 2023 23:45:48 +0200 Subject: [PATCH 5/9] fix keyboard navigation in the note tree, fixes #4036 --- src/public/app/widgets/type_widgets/editable_text.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/public/app/widgets/type_widgets/editable_text.js b/src/public/app/widgets/type_widgets/editable_text.js index 10004e736..06178c3d7 100644 --- a/src/public/app/widgets/type_widgets/editable_text.js +++ b/src/public/app/widgets/type_widgets/editable_text.js @@ -185,14 +185,8 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { async doRefresh(note) { const noteComplement = await froca.getNoteComplement(note.noteId); - await this.spacedUpdate.allowUpdateWithoutChange(() => { - // https://github.com/zadam/trilium/issues/3914 - // todo: quite hacky, but it works. remove it if ckeditor has fixed it. - this.$editor.trigger('focus'); - this.$editor.trigger('blur') - - this.watchdog.editor.setData(noteComplement.content || ""); - }); + await this.spacedUpdate.allowUpdateWithoutChange(() => + this.watchdog.editor.setData(noteComplement.content || "")); } getData() { From 5905950c17791ce0eb278e010c2c8b3450fdb447 Mon Sep 17 00:00:00 2001 From: zadam Date: Mon, 19 Jun 2023 00:29:36 +0200 Subject: [PATCH 6/9] fix notePosition assignment for new children of root --- db/migrations/0214__fix_root_children_ordering.sql | 1 + src/services/app_info.js | 2 +- src/services/notes.js | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 db/migrations/0214__fix_root_children_ordering.sql diff --git a/db/migrations/0214__fix_root_children_ordering.sql b/db/migrations/0214__fix_root_children_ordering.sql new file mode 100644 index 000000000..056a76336 --- /dev/null +++ b/db/migrations/0214__fix_root_children_ordering.sql @@ -0,0 +1 @@ +UPDATE branches SET notePosition = notePosition - 999899999 WHERE parentNoteId = 'root' AND notePosition > 999999999; diff --git a/src/services/app_info.js b/src/services/app_info.js index 3f5f02f3b..88338defe 100644 --- a/src/services/app_info.js +++ b/src/services/app_info.js @@ -4,7 +4,7 @@ const build = require('./build'); const packageJson = require('../../package'); const {TRILIUM_DATA_DIR} = require('./data_dir'); -const APP_DB_VERSION = 213; +const APP_DB_VERSION = 214; const SYNC_VERSION = 29; const CLIPPER_PROTOCOL_VERSION = "1.0"; diff --git a/src/services/notes.js b/src/services/notes.js index c9343bc4e..1ec012186 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -26,11 +26,13 @@ const fs = require("fs"); function getNewNotePosition(parentNote) { if (parentNote.isLabelTruthy('newNotesOnTop')) { const minNotePos = parentNote.getChildBranches() + .filter(branch => branch.noteId !== '_hidden') // has "always last" note position .reduce((min, note) => Math.min(min, note.notePosition), 0); return minNotePos - 10; } else { const maxNotePos = parentNote.getChildBranches() + .filter(branch => branch.noteId !== '_hidden') // has "always last" note position .reduce((max, note) => Math.max(max, note.notePosition), 0); return maxNotePos + 10; From defd99742460422491632cd358b6dea4450020de Mon Sep 17 00:00:00 2001 From: zadam Date: Mon, 19 Jun 2023 23:26:50 +0200 Subject: [PATCH 7/9] release 0.60.4 --- package.json | 2 +- src/services/build.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 090d13924..f55889ddb 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "trilium", "productName": "Trilium Notes", "description": "Trilium Notes", - "version": "0.60.3", + "version": "0.60.4", "license": "AGPL-3.0-only", "main": "electron.js", "bin": { diff --git a/src/services/build.js b/src/services/build.js index 2386ee8af..04a133b33 100644 --- a/src/services/build.js +++ b/src/services/build.js @@ -1 +1 @@ -module.exports = { buildDate:"2023-06-15T23:23:37+02:00", buildRevision: "3223e767875e5379c99ff58a562cb9c1a2641bdf" }; +module.exports = { buildDate:"2023-06-19T23:26:50+02:00", buildRevision: "5905950c17791ce0eb278e010c2c8b3450fdb447" }; From 6cfd18b29b38ad2ccbde5bf72f5b74c1e458687a Mon Sep 17 00:00:00 2001 From: zadam Date: Tue, 20 Jun 2023 21:19:56 +0200 Subject: [PATCH 8/9] read filter values in icon selector only after async events, #4044 --- src/public/app/widgets/note_icon.js | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/public/app/widgets/note_icon.js b/src/public/app/widgets/note_icon.js index ba2bbb683..bfd7754ca 100644 --- a/src/public/app/widgets/note_icon.js +++ b/src/public/app/widgets/note_icon.js @@ -93,11 +93,11 @@ export default class NoteIconWidget extends NoteContextAwareWidget { }); this.$iconCategory = this.$widget.find("select[name='icon-category']"); - this.$iconCategory.on('change', () => this.renderFilteredDropdown()); + this.$iconCategory.on('change', () => this.renderDropdown()); this.$iconCategory.on('click', e => e.stopPropagation()); this.$iconSearch = this.$widget.find("input[name='icon-search']"); - this.$iconSearch.on('input', () => this.renderFilteredDropdown()); + this.$iconSearch.on('input', () => this.renderDropdown()); this.$notePathList = this.$widget.find(".note-path-list"); this.$widget.on('show.bs.dropdown', async () => { @@ -140,14 +140,7 @@ export default class NoteIconWidget extends NoteContextAwareWidget { } } - renderFilteredDropdown() { - const categoryId = parseInt(this.$iconCategory.find('option:selected').val()); - const search = this.$iconSearch.val(); - - this.renderDropdown(categoryId, search); - } - - async renderDropdown(categoryId, search) { + async renderDropdown() { const iconToCountPromise = this.getIconToCountMap(); this.$iconList.empty(); @@ -165,8 +158,10 @@ export default class NoteIconWidget extends NoteContextAwareWidget { } const {icons} = (await import('./icon_list.js')).default; + const iconToCount = await iconToCountPromise; - search = search?.trim()?.toLowerCase(); + const categoryId = parseInt(this.$iconCategory.find('option:selected').val()); + const search = this.$iconSearch.val().trim().toLowerCase(); const filteredIcons = icons.filter(icon => { if (categoryId && icon.category_id !== categoryId) { @@ -182,8 +177,6 @@ export default class NoteIconWidget extends NoteContextAwareWidget { return true; }); - const iconToCount = await iconToCountPromise; - filteredIcons.sort((a, b) => { const countA = iconToCount[a.className] || 0; const countB = iconToCount[b.className] || 0; From 8095c77b9188b7e40bd00a331912114b68b56893 Mon Sep 17 00:00:00 2001 From: zadam Date: Tue, 20 Jun 2023 21:31:25 +0200 Subject: [PATCH 9/9] cache icon count to make filtering more responsive on slower connections, #4044 --- src/public/app/widgets/note_icon.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/public/app/widgets/note_icon.js b/src/public/app/widgets/note_icon.js index bfd7754ca..da3582d8f 100644 --- a/src/public/app/widgets/note_icon.js +++ b/src/public/app/widgets/note_icon.js @@ -141,7 +141,8 @@ export default class NoteIconWidget extends NoteContextAwareWidget { } async renderDropdown() { - const iconToCountPromise = this.getIconToCountMap(); + const iconToCount = await this.getIconToCountMap(); + const {icons} = (await import('./icon_list.js')).default; this.$iconList.empty(); @@ -157,9 +158,6 @@ export default class NoteIconWidget extends NoteContextAwareWidget { ); } - const {icons} = (await import('./icon_list.js')).default; - const iconToCount = await iconToCountPromise; - const categoryId = parseInt(this.$iconCategory.find('option:selected').val()); const search = this.$iconSearch.val().trim().toLowerCase(); @@ -192,9 +190,12 @@ export default class NoteIconWidget extends NoteContextAwareWidget { } async getIconToCountMap() { - const {iconClassToCountMap} = await server.get('other/icon-usage'); + if (!this.iconToCountCache) { + this.iconToCountCache = server.get('other/icon-usage'); + setTimeout(() => this.iconToCountCache = null, 20000); // invalidate cache after 20 seconds + } - return iconClassToCountMap; + return (await this.iconToCountCache).iconClassToCountMap; } renderIcon(icon) {