From ce7489049f9dc2110f99f2d63e18aeea1f6243db Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Tue, 23 May 2023 10:32:24 +0800 Subject: [PATCH 01/40] put window on top --- src/public/app/widgets/title_bar_buttons.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/public/app/widgets/title_bar_buttons.js b/src/public/app/widgets/title_bar_buttons.js index e125e555b..832ddfef1 100644 --- a/src/public/app/widgets/title_bar_buttons.js +++ b/src/public/app/widgets/title_bar_buttons.js @@ -33,6 +33,7 @@ const TPL = ` +
@@ -47,10 +48,24 @@ export default class TitleBarButtonsWidget extends BasicWidget { this.$widget = $(TPL); this.contentSized(); + const $topBtn = this.$widget.find(".top-btn"); const $minimizeBtn = this.$widget.find(".minimize-btn"); const $maximizeBtn = this.$widget.find(".maximize-btn"); const $closeBtn = this.$widget.find(".close-btn"); + $topBtn.on('click', () => { + $topBtn.trigger('blur'); + const remote = utils.dynamicRequire('@electron/remote'); + const focusedWindow = remote.BrowserWindow.getFocusedWindow(); + const isAlwaysOnTop = focusedWindow.isAlwaysOnTop() + if (isAlwaysOnTop) { + focusedWindow.setAlwaysOnTop(false) + $topBtn.css("background-color", ""); + } else { + focusedWindow.setAlwaysOnTop(true); + $topBtn.css("background-color", "var(--accented-background-color)"); + } + }); $minimizeBtn.on('click', () => { $minimizeBtn.trigger('blur'); const remote = utils.dynamicRequire('@electron/remote'); From 63be25f5bb12679b62577e0333ea52d4bd61404a Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Tue, 23 May 2023 11:14:23 +0800 Subject: [PATCH 02/40] put window on top --- src/public/app/widgets/title_bar_buttons.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/public/app/widgets/title_bar_buttons.js b/src/public/app/widgets/title_bar_buttons.js index 832ddfef1..beb7aa1e7 100644 --- a/src/public/app/widgets/title_bar_buttons.js +++ b/src/public/app/widgets/title_bar_buttons.js @@ -52,7 +52,14 @@ export default class TitleBarButtonsWidget extends BasicWidget { const $minimizeBtn = this.$widget.find(".minimize-btn"); const $maximizeBtn = this.$widget.find(".maximize-btn"); const $closeBtn = this.$widget.find(".close-btn"); - + + //When the window is restarted, the window will not be reset when it is set to the top, so get the window status and set the icon background + (function() { + const remote = utils.dynamicRequire('@electron/remote'); + if (remote.BrowserWindow.getFocusedWindow().isAlwaysOnTop()) { + $topBtn.css("background-color", "var(--accented-background-color)"); + } + }()); $topBtn.on('click', () => { $topBtn.trigger('blur'); const remote = utils.dynamicRequire('@electron/remote'); @@ -66,6 +73,7 @@ export default class TitleBarButtonsWidget extends BasicWidget { $topBtn.css("background-color", "var(--accented-background-color)"); } }); + $minimizeBtn.on('click', () => { $minimizeBtn.trigger('blur'); const remote = utils.dynamicRequire('@electron/remote'); From 039959a48b46131de15a65685a2bff2a62b4ec2e Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Tue, 23 May 2023 15:44:26 +0800 Subject: [PATCH 03/40] Put this window top. --- src/public/app/widgets/title_bar_buttons.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/public/app/widgets/title_bar_buttons.js b/src/public/app/widgets/title_bar_buttons.js index beb7aa1e7..0ba59ffb7 100644 --- a/src/public/app/widgets/title_bar_buttons.js +++ b/src/public/app/widgets/title_bar_buttons.js @@ -33,7 +33,7 @@ const TPL = ` -
+
@@ -58,6 +58,7 @@ export default class TitleBarButtonsWidget extends BasicWidget { const remote = utils.dynamicRequire('@electron/remote'); if (remote.BrowserWindow.getFocusedWindow().isAlwaysOnTop()) { $topBtn.css("background-color", "var(--accented-background-color)"); + $topBtn.attr("title","The window has been pinned. "); } }()); $topBtn.on('click', () => { @@ -68,9 +69,11 @@ export default class TitleBarButtonsWidget extends BasicWidget { if (isAlwaysOnTop) { focusedWindow.setAlwaysOnTop(false) $topBtn.css("background-color", ""); + $topBtn.attr("title","Put this window top. "); } else { focusedWindow.setAlwaysOnTop(true); $topBtn.css("background-color", "var(--accented-background-color)"); + $topBtn.attr("title","The window has been pinned. "); } }); From dc4983a013015ca52439e65e785bc28e026ed3fa Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Tue, 23 May 2023 15:49:03 +0800 Subject: [PATCH 04/40] Bring this window to the top --- src/public/app/widgets/title_bar_buttons.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/public/app/widgets/title_bar_buttons.js b/src/public/app/widgets/title_bar_buttons.js index 0ba59ffb7..fe09669e3 100644 --- a/src/public/app/widgets/title_bar_buttons.js +++ b/src/public/app/widgets/title_bar_buttons.js @@ -33,7 +33,7 @@ const TPL = ` -
+
@@ -58,7 +58,7 @@ export default class TitleBarButtonsWidget extends BasicWidget { const remote = utils.dynamicRequire('@electron/remote'); if (remote.BrowserWindow.getFocusedWindow().isAlwaysOnTop()) { $topBtn.css("background-color", "var(--accented-background-color)"); - $topBtn.attr("title","The window has been pinned. "); + $topBtn.attr("title","The window is already on top. "); } }()); $topBtn.on('click', () => { @@ -69,11 +69,11 @@ export default class TitleBarButtonsWidget extends BasicWidget { if (isAlwaysOnTop) { focusedWindow.setAlwaysOnTop(false) $topBtn.css("background-color", ""); - $topBtn.attr("title","Put this window top. "); + $topBtn.attr("title","Bring this window to the top. "); } else { focusedWindow.setAlwaysOnTop(true); $topBtn.css("background-color", "var(--accented-background-color)"); - $topBtn.attr("title","The window has been pinned. "); + $topBtn.attr("title","The window is already on top. "); } }); From 66c25e7f2966c7a47b6f9469b587c75f13b5e075 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Tue, 23 May 2023 15:55:35 +0800 Subject: [PATCH 05/40] Bring this window to the top --- src/public/app/widgets/title_bar_buttons.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/public/app/widgets/title_bar_buttons.js b/src/public/app/widgets/title_bar_buttons.js index fe09669e3..a2f35937b 100644 --- a/src/public/app/widgets/title_bar_buttons.js +++ b/src/public/app/widgets/title_bar_buttons.js @@ -58,7 +58,6 @@ export default class TitleBarButtonsWidget extends BasicWidget { const remote = utils.dynamicRequire('@electron/remote'); if (remote.BrowserWindow.getFocusedWindow().isAlwaysOnTop()) { $topBtn.css("background-color", "var(--accented-background-color)"); - $topBtn.attr("title","The window is already on top. "); } }()); $topBtn.on('click', () => { @@ -69,11 +68,9 @@ export default class TitleBarButtonsWidget extends BasicWidget { if (isAlwaysOnTop) { focusedWindow.setAlwaysOnTop(false) $topBtn.css("background-color", ""); - $topBtn.attr("title","Bring this window to the top. "); } else { focusedWindow.setAlwaysOnTop(true); $topBtn.css("background-color", "var(--accented-background-color)"); - $topBtn.attr("title","The window is already on top. "); } }); From 79dca12274b31d3dc681742b5ed0ea792bedbfc2 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Tue, 23 May 2023 20:50:03 +0800 Subject: [PATCH 06/40] top --- .vscode/settings.json | 2 +- src/public/app/widgets/title_bar_buttons.js | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 0e4b77b5c..b1cee4342 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "[javascript]": { - "editor.defaultFormatter": "dbaeumer.vscode-eslint" + "editor.defaultFormatter": "vscode.typescript-language-features" }, "[json]": { "editor.defaultFormatter": "dbaeumer.vscode-eslint" diff --git a/src/public/app/widgets/title_bar_buttons.js b/src/public/app/widgets/title_bar_buttons.js index a2f35937b..24188d9ce 100644 --- a/src/public/app/widgets/title_bar_buttons.js +++ b/src/public/app/widgets/title_bar_buttons.js @@ -30,6 +30,9 @@ const TPL = ` height: 40px; width: 40px; } + .title-bar-buttons .top-btn.active{ + background-color:var(--accented-background-color); + } @@ -52,12 +55,12 @@ export default class TitleBarButtonsWidget extends BasicWidget { const $minimizeBtn = this.$widget.find(".minimize-btn"); const $maximizeBtn = this.$widget.find(".maximize-btn"); const $closeBtn = this.$widget.find(".close-btn"); - + //When the window is restarted, the window will not be reset when it is set to the top, so get the window status and set the icon background - (function() { + (function () { const remote = utils.dynamicRequire('@electron/remote'); if (remote.BrowserWindow.getFocusedWindow().isAlwaysOnTop()) { - $topBtn.css("background-color", "var(--accented-background-color)"); + $topBtn.addClass('active'); } }()); $topBtn.on('click', () => { @@ -67,13 +70,13 @@ export default class TitleBarButtonsWidget extends BasicWidget { const isAlwaysOnTop = focusedWindow.isAlwaysOnTop() if (isAlwaysOnTop) { focusedWindow.setAlwaysOnTop(false) - $topBtn.css("background-color", ""); + $topBtn.removeClass('active'); } else { focusedWindow.setAlwaysOnTop(true); - $topBtn.css("background-color", "var(--accented-background-color)"); + $topBtn.addClass('active'); } }); - + $minimizeBtn.on('click', () => { $minimizeBtn.trigger('blur'); const remote = utils.dynamicRequire('@electron/remote'); From 01b2887b36c44f2ca519c299dd1b72f6e9aadcd3 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Sat, 27 May 2023 14:18:26 +0800 Subject: [PATCH 07/40] Keep this window on top. --- src/public/app/widgets/title_bar_buttons.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/app/widgets/title_bar_buttons.js b/src/public/app/widgets/title_bar_buttons.js index 24188d9ce..6a4f73ad5 100644 --- a/src/public/app/widgets/title_bar_buttons.js +++ b/src/public/app/widgets/title_bar_buttons.js @@ -36,7 +36,7 @@ const TPL = ` -
+
From 1a2beb941e8f77b7bf59586289bfa10bf1327e14 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Sat, 27 May 2023 14:23:50 +0800 Subject: [PATCH 08/40] keep this window on top --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index b1cee4342..0e4b77b5c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "[javascript]": { - "editor.defaultFormatter": "vscode.typescript-language-features" + "editor.defaultFormatter": "dbaeumer.vscode-eslint" }, "[json]": { "editor.defaultFormatter": "dbaeumer.vscode-eslint" From e1b67e20ec66b0d295fe567d21baeb68ad23c72a Mon Sep 17 00:00:00 2001 From: dymani Date: Tue, 30 May 2023 02:24:56 +0800 Subject: [PATCH 09/40] Add buttons to move split panes --- src/public/app/layouts/desktop_layout.js | 3 + .../app/widgets/buttons/move_pane_button.js | 55 +++++++++++++++++++ .../containers/split_note_container.js | 38 +++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 src/public/app/widgets/buttons/move_pane_button.js diff --git a/src/public/app/layouts/desktop_layout.js b/src/public/app/layouts/desktop_layout.js index 17dcc99a7..62e6b3ed1 100644 --- a/src/public/app/layouts/desktop_layout.js +++ b/src/public/app/layouts/desktop_layout.js @@ -75,6 +75,7 @@ import CodeButtonsWidget from "../widgets/floating_buttons/code_buttons.js"; import ApiLogWidget from "../widgets/api_log.js"; import HideFloatingButtonsButton from "../widgets/floating_buttons/hide_floating_buttons_button.js"; import ScriptExecutorWidget from "../widgets/ribbon_widgets/script_executor.js"; +import MovePaneButton from "../widgets/buttons/move_pane_button.js"; export default class DesktopLayout { constructor(customWidgets) { @@ -123,6 +124,8 @@ export default class DesktopLayout { .child(new NoteIconWidget()) .child(new NoteTitleWidget()) .child(new SpacerWidget(0, 1)) + .child(new MovePaneButton(true)) + .child(new MovePaneButton(false)) .child(new ClosePaneButton()) .child(new CreatePaneButton()) ) diff --git a/src/public/app/widgets/buttons/move_pane_button.js b/src/public/app/widgets/buttons/move_pane_button.js new file mode 100644 index 000000000..9ea4dbe61 --- /dev/null +++ b/src/public/app/widgets/buttons/move_pane_button.js @@ -0,0 +1,55 @@ +import OnClickButtonWidget from "./onclick_button.js"; +import appContext from "../../components/app_context.js"; + +export default class MovePaneButton extends OnClickButtonWidget { + isEnabled() { + if (!super.isEnabled()) + return false; + + if (this.isMovingLeft) { + // movable if the current context is not a main context, i.e. non-null mainNtxId + return !!this.noteContext?.mainNtxId; + } else { + const currentIndex = appContext.tabManager.noteContexts.findIndex(c => c.ntxId === this.ntxId); + const nextContext = appContext.tabManager.noteContexts[currentIndex + 1]; + // movable if the next context is not null and not a main context, i.e. non-null mainNtxId + return !!nextContext?.mainNtxId; + } + } + + initialRenderCompleteEvent() { + this.refresh(); + super.initialRenderCompleteEvent(); + } + + async noteContextRemovedEvent({ntxIds}) { + this.refresh(); + } + + async newNoteContextCreatedEvent({noteContext}) { + this.refresh(); + } + + async noteContextSwitchEvent() { + this.refresh(); + } + + async noteContextReorderEvent({ntxIdsInOrder}) { + this.refresh(); + } + + constructor(isMovingLeft) { + super(); + + this.isMovingLeft = isMovingLeft; + + this.icon(isMovingLeft ? "bx-chevron-left" : "bx-chevron-right") + .title(isMovingLeft ? "Move left" : "Move right") + .titlePlacement("bottom") + .onClick(async (widget, e) => { + e.stopPropagation(); + widget.triggerCommand("moveThisNoteSplit", {ntxId: widget.getClosestNtxId(), isMovingLeft: this.isMovingLeft}); + }) + .class("icon-action"); + } +} diff --git a/src/public/app/widgets/containers/split_note_container.js b/src/public/app/widgets/containers/split_note_container.js index 66356164d..854ccfd23 100644 --- a/src/public/app/widgets/containers/split_note_container.js +++ b/src/public/app/widgets/containers/split_note_container.js @@ -1,5 +1,6 @@ import FlexContainer from "./flex_container.js"; import appContext from "../../components/app_context.js"; +import NoteContext from "../../components/note_context.js"; export default class SplitNoteContainer extends FlexContainer { constructor(widgetFactory) { @@ -74,6 +75,43 @@ export default class SplitNoteContainer extends FlexContainer { appContext.tabManager.removeNoteContext(ntxId); } + async moveThisNoteSplitCommand({ntxId, isMovingLeft}) { + if (!ntxId) { + logError("empty ntxId!"); + return; + } + + const contexts = appContext.tabManager.noteContexts; + + const currentIndex = contexts.findIndex(c => c.ntxId === ntxId); + const otherIndex = currentIndex + (isMovingLeft ? -1 : 1); + + if (currentIndex === -1 || otherIndex < 0 || otherIndex >= contexts.length) { + logError("invalid context!"); + return; + } + + if (contexts[currentIndex].isEmpty() && contexts[otherIndex].isEmpty()) + // no op + return; + + const currentId = contexts[currentIndex].ntxId; + const currentPath = contexts[currentIndex].notePath; + + const otherId = contexts[otherIndex].ntxId; + const otherPath = contexts[otherIndex].notePath; + + if (!!currentPath) + await appContext.tabManager.switchToNoteContext(otherId, currentPath); + if (!!otherPath) + await appContext.tabManager.switchToNoteContext(currentId, otherPath); + + // activate context that now contains the original note + await appContext.tabManager.activateNoteContext(otherId); + + this.triggerEvent('noteContextSwitch'); + } + activeContextChangedEvent() { this.refresh(); } From 2a399069936bb3e0c094ca52ebbad493ed529122 Mon Sep 17 00:00:00 2001 From: dymani Date: Tue, 30 May 2023 04:22:51 +0800 Subject: [PATCH 10/40] Cleanup --- src/public/app/widgets/containers/split_note_container.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/public/app/widgets/containers/split_note_container.js b/src/public/app/widgets/containers/split_note_container.js index 854ccfd23..3a17b7d20 100644 --- a/src/public/app/widgets/containers/split_note_container.js +++ b/src/public/app/widgets/containers/split_note_container.js @@ -1,6 +1,5 @@ import FlexContainer from "./flex_container.js"; import appContext from "../../components/app_context.js"; -import NoteContext from "../../components/note_context.js"; export default class SplitNoteContainer extends FlexContainer { constructor(widgetFactory) { From 735852b3c1dc9d642bfc01f603b1f84aec9e9e1b Mon Sep 17 00:00:00 2001 From: dymani Date: Wed, 31 May 2023 01:53:55 +0800 Subject: [PATCH 11/40] Use noteContextReorder event instead --- src/public/app/components/tab_manager.js | 13 ++++--- .../app/widgets/buttons/close_pane_button.js | 4 +++ .../containers/split_note_container.js | 36 ++++++++++--------- src/public/app/widgets/tab_row.js | 16 +++++++++ 4 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/public/app/components/tab_manager.js b/src/public/app/components/tab_manager.js index e5ca32c45..ace30105f 100644 --- a/src/public/app/components/tab_manager.js +++ b/src/public/app/components/tab_manager.js @@ -451,16 +451,15 @@ export default class TabManager extends Component { this.tabsUpdate.scheduleUpdate(); } - noteContextReorderEvent({ntxIdsInOrder}) { - const order = {}; - let i = 0; - - for (const ntxId of ntxIdsInOrder) { - order[ntxId] = i++; - } + noteContextReorderEvent({ntxIdsInOrder, mainNtxIdsInOrder}) { + const order = Object.fromEntries(ntxIdsInOrder.map((v, i) => [v, i])); this.children.sort((a, b) => order[a.ntxId] < order[b.ntxId] ? -1 : 1); + if (!!mainNtxIdsInOrder && mainNtxIdsInOrder.length === this.children.length) { + this.children.forEach((c, i) => c.mainNtxId = mainNtxIdsInOrder[i]); + } + this.tabsUpdate.scheduleUpdate(); } diff --git a/src/public/app/widgets/buttons/close_pane_button.js b/src/public/app/widgets/buttons/close_pane_button.js index 690dcac6f..220fd2cca 100644 --- a/src/public/app/widgets/buttons/close_pane_button.js +++ b/src/public/app/widgets/buttons/close_pane_button.js @@ -7,6 +7,10 @@ export default class ClosePaneButton extends OnClickButtonWidget { && this.noteContext && !!this.noteContext.mainNtxId; } + async noteContextReorderEvent({ntxIdsInOrder}) { + this.refresh(); + } + constructor() { super(); diff --git a/src/public/app/widgets/containers/split_note_container.js b/src/public/app/widgets/containers/split_note_container.js index 3a17b7d20..988c537b1 100644 --- a/src/public/app/widgets/containers/split_note_container.js +++ b/src/public/app/widgets/containers/split_note_container.js @@ -83,32 +83,36 @@ export default class SplitNoteContainer extends FlexContainer { const contexts = appContext.tabManager.noteContexts; const currentIndex = contexts.findIndex(c => c.ntxId === ntxId); - const otherIndex = currentIndex + (isMovingLeft ? -1 : 1); + const leftIndex = isMovingLeft ? currentIndex - 1 : currentIndex; - if (currentIndex === -1 || otherIndex < 0 || otherIndex >= contexts.length) { + if (currentIndex === -1 || leftIndex < 0 || leftIndex + 1 >= contexts.length) { logError("invalid context!"); return; } - if (contexts[currentIndex].isEmpty() && contexts[otherIndex].isEmpty()) + if (contexts[leftIndex].isEmpty() && contexts[leftIndex + 1].isEmpty()) // no op return; - const currentId = contexts[currentIndex].ntxId; - const currentPath = contexts[currentIndex].notePath; - - const otherId = contexts[otherIndex].ntxId; - const otherPath = contexts[otherIndex].notePath; - - if (!!currentPath) - await appContext.tabManager.switchToNoteContext(otherId, currentPath); - if (!!otherPath) - await appContext.tabManager.switchToNoteContext(currentId, otherPath); + const ntxIds = contexts.map(c => c.ntxId); + const mainNtxIds = contexts.map(c => c.mainNtxId); + + this.triggerCommand("noteContextReorder", { + ntxIdsInOrder: [ + ...ntxIds.slice(0, leftIndex), + ntxIds[leftIndex + 1], + ntxIds[leftIndex], + ...ntxIds.slice(leftIndex + 2), + ], + oldNtxIdsInOrder: ntxIds, + mainNtxIdsInOrder: mainNtxIds.map(id => id === ntxIds[leftIndex] ? ntxIds[leftIndex + 1] : id) + }); + + this.$widget.find(`[data-ntx-id="${ntxIds[leftIndex]}"]`) + .insertAfter(this.$widget.find(`[data-ntx-id="${ntxIds[leftIndex + 1]}"]`)); // activate context that now contains the original note - await appContext.tabManager.activateNoteContext(otherId); - - this.triggerEvent('noteContextSwitch'); + await appContext.tabManager.activateNoteContext(isMovingLeft ? ntxIds[leftIndex + 1] : ntxIds[leftIndex]); } activeContextChangedEvent() { diff --git a/src/public/app/widgets/tab_row.js b/src/public/app/widgets/tab_row.js index 55a5da24c..6e15f3458 100644 --- a/src/public/app/widgets/tab_row.js +++ b/src/public/app/widgets/tab_row.js @@ -609,6 +609,22 @@ export default class TabRowWidget extends BasicWidget { this.updateTabById(noteContext.mainNtxId || noteContext.ntxId); } + noteContextReorderEvent({ntxIdsInOrder, oldNtxIdsInOrder, mainNtxIdsInOrder}) { + if (!oldNtxIdsInOrder || !mainNtxIdsInOrder + || ntxIdsInOrder.length !== oldNtxIdsInOrder.length + || ntxIdsInOrder.length !== mainNtxIdsInOrder.length + ) + return; + + ntxIdsInOrder.forEach((id, i) => { + // update main context that is not a tab + if (!mainNtxIdsInOrder[i] && this.getTabById(id).length === 0) { + this.getTabById(oldNtxIdsInOrder[i]).attr("data-ntx-id", id); + this.updateTabById(id); + } + }); + } + updateTabById(ntxId) { const $tab = this.getTabById(ntxId); From 64aed9b4620df36277556bb6d7c8778bc3dd1a97 Mon Sep 17 00:00:00 2001 From: zadam Date: Tue, 30 May 2023 22:52:48 +0200 Subject: [PATCH 12/40] display icon for non-supported note types in content renderer --- package-lock.json | 4 ++-- src/public/app/services/note_content_renderer.js | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 74aabd229..6055a636e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "trilium", - "version": "0.60.0-beta", + "version": "0.60.1-beta", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "trilium", - "version": "0.60.0-beta", + "version": "0.60.1-beta", "hasInstallScript": true, "license": "AGPL-3.0-only", "dependencies": { diff --git a/src/public/app/services/note_content_renderer.js b/src/public/app/services/note_content_renderer.js index 2fb805799..6bb47d9f3 100644 --- a/src/public/app/services/note_content_renderer.js +++ b/src/public/app/services/note_content_renderer.js @@ -157,9 +157,6 @@ async function getRenderedContent(note, options = {}) { $renderedContent.append($("
").text("Error parsing content. Please check console.error() for more details.")); } } - else if (type === 'book') { - // nothing, book doesn't have its own content - } else if (!options.tooltip && type === 'protectedSession') { const $button = $(``) .on('click', protectedSessionService.enterProtectedSession); @@ -172,7 +169,12 @@ async function getRenderedContent(note, options = {}) { ); } else { - $renderedContent.append($("

Content of this note cannot be displayed in the book format

")); + $renderedContent.append( + $("
") + .css("text-align", "center") + .css("font-size", "500%") + .append($("").addClass(note.getIcon())) + ); } $renderedContent.addClass(note.getCssClass()); From 375dba3264751fe30cb33fd82f210c3d21c11f63 Mon Sep 17 00:00:00 2001 From: zadam Date: Tue, 30 May 2023 23:54:11 +0200 Subject: [PATCH 13/40] fixed infinite recursion with search notes --- src/public/app/entities/fnote.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/app/entities/fnote.js b/src/public/app/entities/fnote.js index 6af816a44..eff96df81 100644 --- a/src/public/app/entities/fnote.js +++ b/src/public/app/entities/fnote.js @@ -392,7 +392,7 @@ class FNote { for (const parentNote of this.getParentNotes()) { if (parentNote.noteId === 'root') { return false; - } else if (parentNote.noteId === '_hidden') { + } else if (parentNote.noteId === '_hidden' || parentNote.type === 'search') { continue; } From d26a0fae175d1abf6d24fdab5ca03d4c257f57ae Mon Sep 17 00:00:00 2001 From: zadam Date: Wed, 31 May 2023 00:03:20 +0200 Subject: [PATCH 14/40] if a note context has sub contexts, then it has to be saved even if empty, fixes #3985 --- src/public/app/components/note_context.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/public/app/components/note_context.js b/src/public/app/components/note_context.js index 3df6b206e..c22a87ac6 100644 --- a/src/public/app/components/note_context.js +++ b/src/public/app/components/note_context.js @@ -176,9 +176,12 @@ class NoteContext extends Component { } getTabState() { - if (!this.notePath && this.hoistedNoteId === 'root') { + if (this.hoistedNoteId !== 'root') { // keeping empty hoisted tab is esp. important for mobile (e.g. opened launcher config) - return null; + + if (!this.notePath && this.getSubContexts().length === 0) { + return null; + } } return { From 86861f6ec36b6015c20dd0b4529b627fc8f92667 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Wed, 31 May 2023 18:32:33 +0800 Subject: [PATCH 15/40] Show highlighted text in the left pane --- src/public/app/layouts/desktop_layout.js | 2 + src/public/app/widgets/highlighted_text.js | 267 ++++++++++++++++++ .../widgets/type_widgets/content_widget.js | 2 + .../options/text_notes/highlighted_text.js | 90 ++++++ src/routes/api/options.js | 2 + src/services/options_init.js | 2 + 6 files changed, 365 insertions(+) create mode 100644 src/public/app/widgets/highlighted_text.js create mode 100644 src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js diff --git a/src/public/app/layouts/desktop_layout.js b/src/public/app/layouts/desktop_layout.js index 17dcc99a7..3a9ec2930 100644 --- a/src/public/app/layouts/desktop_layout.js +++ b/src/public/app/layouts/desktop_layout.js @@ -44,6 +44,7 @@ import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js"; import SharedInfoWidget from "../widgets/shared_info.js"; import FindWidget from "../widgets/find.js"; import TocWidget from "../widgets/toc.js"; +import HltWidget from "../widgets/highlighted_text.js"; import BulkActionsDialog from "../widgets/dialogs/bulk_actions.js"; import AboutDialog from "../widgets/dialogs/about.js"; import HelpDialog from "../widgets/dialogs/help.js"; @@ -181,6 +182,7 @@ export default class DesktopLayout { ) .child(new RightPaneContainer() .child(new TocWidget()) + .child(new HltWidget()) .child(...this.customWidgets.get('right-pane')) ) ) diff --git a/src/public/app/widgets/highlighted_text.js b/src/public/app/widgets/highlighted_text.js new file mode 100644 index 000000000..3dbfc9b5c --- /dev/null +++ b/src/public/app/widgets/highlighted_text.js @@ -0,0 +1,267 @@ +/** + * Widget: Show highlighted text in the right pane + * + * By design there's no support for nonsensical or malformed constructs: + * - For example, if there is a formula in the middle of the highlighted text, the two ends of the formula will be regarded as two entries + */ + +import attributeService from "../services/attributes.js"; +import RightPanelWidget from "./right_panel_widget.js"; +import options from "../services/options.js"; +import OnClickButtonWidget from "./buttons/onclick_button.js"; + +const TPL = `
+ + + +
`; + +export default class HltWidget extends RightPanelWidget { + constructor() { + super(); + + this.closeHltButton = new CloseHltButton(); + this.child(this.closeHltButton); + } + + get widgetTitle() { + return "Highlighted Text"; + } + + isEnabled() { + return super.isEnabled() + && this.note.type === 'text' + && !this.noteContext.viewScope.hltTemporarilyHidden + && this.noteContext.viewScope.viewMode === 'default'; + } + + async doRenderBody() { + this.$body.empty().append($(TPL)); + this.$hlt = this.$body.find('.hlt'); + this.$body.find('.hlt-widget').append(this.closeHltButton.render()); + } + + async refreshWithNote(note) { + const hltLabel = note.getLabel('hlt'); + + if (hltLabel?.value === 'hide') { + this.toggleInt(false); + this.triggerCommand("reEvaluateRightPaneVisibility"); + return; + } + + let $hlt = "", hltColors = [], hltBgColors = []; + + let optionsHltColors = JSON.parse(options.get('highlightedTextColors')); + let optionsHltBgColors = JSON.parse(options.get('highlightedTextBgColors')); + // Check for type text unconditionally in case alwaysShowWidget is set + if (this.note.type === 'text') { + const { content } = await note.getNoteComplement(); + //hltColors/hltBgColors are the colors/background-color that appear in notes and in options + ({ $hlt, hltColors, hltBgColors } = await this.getHlt(content, optionsHltColors, optionsHltBgColors)); + } + this.$hlt.html($hlt); + this.toggleInt( + ["", "show"].includes(hltLabel?.value) + || hltColors!="" + || hltBgColors!="" + ); + + this.triggerCommand("reEvaluateRightPaneVisibility"); + } + //Converts color values in RGB, RGBA, or HSL format to hexadecimal format, removing transparency + colorToHex(color) { + function rgbToHex(rgb) { + // Converts color values in RGB or RGBA format to hexadecimal format + var rgba = rgb.match(/\d+/g); + var r = parseInt(rgba[0]); + var g = parseInt(rgba[1]); + var b = parseInt(rgba[2]); + var hex = "#"; + hex += (r < 16 ? "0" : "") + r.toString(16); + hex += (g < 16 ? "0" : "") + g.toString(16); + hex += (b < 16 ? "0" : "") + b.toString(16); + return hex; + } + + function hslToHex(hsl) { + // Convert color values in HSL format to RGB format and then to hexadecimal format + var hslValues = hsl.match(/\d+(\.\d+)?/g); + var h = parseFloat(hslValues[0]) / 360; + var s = parseFloat(hslValues[1]) / 100; + var l = parseFloat(hslValues[2]) / 100; + var r, g, b; + + if (s === 0) { + r = g = b = l; // achromatic + } else { + function hueToRgb(p, q, t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + } + + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hueToRgb(p, q, h + 1 / 3); + g = hueToRgb(p, q, h); + b = hueToRgb(p, q, h - 1 / 3); + } + + var hex = "#"; + hex += (Math.round(r * 255) < 16 ? "0" : "") + Math.round(r * 255).toString(16); + hex += (Math.round(g * 255) < 16 ? "0" : "") + Math.round(g * 255).toString(16); + hex += (Math.round(b * 255) < 16 ? "0" : "") + Math.round(b * 255).toString(16); + return hex; + } + if (color.indexOf("rgb") !== -1) { + return rgbToHex(color); + } else if (color.indexOf("hsl") !== -1) { + return hslToHex(color); + } else { + return ""; + } + } + // Determine whether the highlighted color is in the options, avoid errors caused by errors in color conversion, + // and the error of each value is acceptable within 2 + hexIsInOptionHexs(targetColor, optionColors){ + for (let i = 0; i < optionColors.length; i++) { + if (Math.abs(parseInt(optionColors[i].slice(1, 3), 16) - parseInt(targetColor.slice(1, 3), 16)) > 2) { continue; } + if (Math.abs(parseInt(optionColors[i].slice(3, 5), 16) - parseInt(targetColor.slice(3, 5), 16)) > 2) { continue; } + if (Math.abs(parseInt(optionColors[i].slice(5, 7), 16) - parseInt(targetColor.slice(5, 7), 16)) > 2) { continue; } + return true; + } + return false; + } + /** + * Builds a jquery table of helight text. + */ + getHlt(html, optionsHltColors, optionsHltBgColors) { + const hltBCs = $(html).find(`span[style*="background-color"],span[style*="color"]`) + const $hlt = $("
    "); + let hltColors = []; + let hltBgColors = []; + for (let hltIndex = 0; hltIndex'); + + if (color != "") { + var hexColor = this.colorToHex(color); + if (this.hexIsInOptionHexs(hexColor,optionsHltColors)) { + $li.html(hltText) + hltColors.push(hexColor); + liDisplay=true; + } + } + if (bgColor != "") { + var hexBgColor = this.colorToHex(bgColor); + if (this.hexIsInOptionHexs(hexBgColor,optionsHltBgColors)) { + //When you need to add a background color, in order to make the display more comfortable, change the background color to transparent + $li.html(hltText.css("background-color", hexBgColor+"80")) + hltBgColors.push(hexBgColor); + liDisplay=true; + } + } + if(!liDisplay){ + $li.css("display","none"); + } + //The font color and background color may be nested or adjacent to each other. At this time, connect the front and back li to avoid interruption + if(hltIndex!=0 && hltBCs[hltIndex-1].nextSibling ===hltBCs[hltIndex] && $hlt.children().last().css("display")!="none"){ + $hlt.children().last().append($li.html()); + }else{ + $li.on("click", () => this.jumpToHlt(hltIndex)); + $hlt.append($li); + } + + }; + return { + $hlt, + hltColors, + hltBgColors + }; + } + + async jumpToHlt(hltIndex) { + const isReadOnly = await this.noteContext.isReadOnly(); + if (isReadOnly) { + const $container = await this.noteContext.getContentElement(); + const hltElement = $container.find(`span[style*="background-color"],span[style*="color"]`)[hltIndex]; + + if (hltElement != null) { + hltElement.scrollIntoView({ behavior: "smooth", block: "center" }); + } + } else { + const textEditor = await this.noteContext.getTextEditor(); + $(textEditor.editing.view.domRoots.values().next().value).find(`span[style*="background-color"],span[style*="color"]`)[hltIndex].scrollIntoView({ + behavior: "smooth", block: "center" + }); + } + } + + async closeHltCommand() { + this.noteContext.viewScope.hltTemporarilyHidden = true; + await this.refresh(); + this.triggerCommand('reEvaluateRightPaneVisibility'); + } + + async entitiesReloadedEvent({ loadResults }) { + if (loadResults.isNoteContentReloaded(this.noteId)) { + await this.refresh(); + } else if (loadResults.getAttributes().find(attr => attr.type === 'label' + && (attr.name.toLowerCase().includes('readonly') || attr.name === 'hlt') + && attributeService.isAffecting(attr, this.note))) { + await this.refresh(); + } + } +} + + +class CloseHltButton extends OnClickButtonWidget { + constructor() { + super(); + + this.icon("bx-x") + .title("Close HLT") + .titlePlacement("bottom") + .onClick((widget, e) => { + e.stopPropagation(); + + widget.triggerCommand("closeHlt"); + }) + .class("icon-action close-hlt"); + } +} diff --git a/src/public/app/widgets/type_widgets/content_widget.js b/src/public/app/widgets/type_widgets/content_widget.js index 967c996e5..f7a4846bd 100644 --- a/src/public/app/widgets/type_widgets/content_widget.js +++ b/src/public/app/widgets/type_widgets/content_widget.js @@ -7,6 +7,7 @@ import MaxContentWidthOptions from "./options/appearance/max_content_width.js"; import KeyboardShortcutsOptions from "./options/shortcuts.js"; import HeadingStyleOptions from "./options/text_notes/heading_style.js"; import TableOfContentsOptions from "./options/text_notes/table_of_contents.js"; +import HighlightedTextOptions from "./options/text_notes/highlighted_text.js"; import TextAutoReadOnlySizeOptions from "./options/text_notes/text_auto_read_only_size.js"; import VimKeyBindingsOptions from "./options/code_notes/vim_key_bindings.js"; import WrapLinesOptions from "./options/code_notes/wrap_lines.js"; @@ -61,6 +62,7 @@ const CONTENT_WIDGETS = { _optionsTextNotes: [ HeadingStyleOptions, TableOfContentsOptions, + HighlightedTextOptions, TextAutoReadOnlySizeOptions ], _optionsCodeNotes: [ diff --git a/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js b/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js new file mode 100644 index 000000000..7fc2ce825 --- /dev/null +++ b/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js @@ -0,0 +1,90 @@ +import OptionsWidget from "../options_widget.js"; + +const TPL = ` +
    + +

    Highlighted Text

    + + Displays highlighted text in the left pane. You can customize the highlighted text displayed in the left pane: +
    Text color:
    + + + + + + + + + + + + + + + +
    Background color:
    + + + + + + + + + + + + + + + +
    `; + +export default class HighlightedTextOptions extends OptionsWidget { + doRender() { + this.$widget = $(TPL); + this.$hltColors = this.$widget.find(".hlt-color"); + this.$hltColors.on('change', () => { + const hltColorVals=this.$widget.find('input.hlt-color[type="checkbox"]:checked').map(function() { + return this.value; + }).get(); + this.updateOption('highlightedTextColors', JSON.stringify(hltColorVals)); + + }); + this.$hltBgColors = this.$widget.find(".hlt-background-color"); + this.$hltBgColors.on('change', () =>{ + const hltBgColorVals=this.$widget.find('input.hlt-background-color[type="checkbox"]:checked').map(function() { + return this.value; + }).get(); + this.updateOption('highlightedTextBgColors', JSON.stringify(hltBgColorVals)); + }); + + } + + async optionsLoaded(options) { + const hltColorVals=JSON.parse(options.highlightedTextColors); + const hltBgColorVals=JSON.parse(options.highlightedTextBgColors); + this.$widget.find('input.hlt-color[type="checkbox"]').each(function () { + if ($.inArray($(this).val(), hltColorVals) !== -1) { + $(this).prop("checked", true); + } else { + $(this).prop("checked", false); + } + }); + this.$widget.find('input.hlt-background-color[type="checkbox"]').each(function () { + if ($.inArray($(this).val(), hltBgColorVals) !== -1) { + $(this).prop("checked", true); + } else { + $(this).prop("checked", false); + } + }); + } +} diff --git a/src/routes/api/options.js b/src/routes/api/options.js index e98b1795f..48f2590b9 100644 --- a/src/routes/api/options.js +++ b/src/routes/api/options.js @@ -60,6 +60,8 @@ const ALLOWED_OPTIONS = new Set([ 'compressImages', 'downloadImagesAutomatically', 'minTocHeadings', + 'highlightedTextColors', + 'highlightedTextBgColors', 'checkForUpdates', 'disableTray', 'customSearchEngineName', diff --git a/src/services/options_init.js b/src/services/options_init.js index 1ce5bbd5d..ba34fba03 100644 --- a/src/services/options_init.js +++ b/src/services/options_init.js @@ -87,6 +87,8 @@ const defaultOptions = [ { name: 'compressImages', value: 'true', isSynced: true }, { name: 'downloadImagesAutomatically', value: 'true', isSynced: true }, { name: 'minTocHeadings', value: '5', isSynced: true }, + { name: 'highlightedTextColors', value: '["#e64c4c","#e6994c","#e6e64c","#99e64c","#4ce64c","#4ce699","#4ce6e6","#4c99e6","#4c4ce6","#994ce6"]', isSynced: true }, + { name: 'highlightedTextBgColors', value: '["#e64c4c","#e6994c","#e6e64c","#99e64c","#4ce64c","#4ce699","#4ce6e6","#4c99e6","#4c4ce6","#994ce6"]', isSynced: true }, { name: 'checkForUpdates', value: 'true', isSynced: true }, { name: 'disableTray', value: 'false', isSynced: false }, { name: 'customSearchEngineName', value: 'Duckduckgo', isSynced: false }, From a9dae7823faf75cd860e1687f37ef1dd0c8bbe8d Mon Sep 17 00:00:00 2001 From: zadam Date: Wed, 31 May 2023 14:00:37 +0200 Subject: [PATCH 16/40] script api docs --- docs/backend_api/AbstractBeccaEntity.html | 2 +- docs/backend_api/BAttribute.html | 2 +- docs/backend_api/BBranch.html | 2 +- docs/backend_api/BEtapiToken.html | 2 +- docs/backend_api/BNote.html | 792 ++++++++++++++++-- docs/backend_api/BNoteRevision.html | 2 +- docs/backend_api/BOption.html | 10 +- docs/backend_api/BRecentNote.html | 2 +- docs/backend_api/BackendScriptApi.html | 8 +- ...cca_entities_abstract_becca_entity.js.html | 2 +- .../becca_entities_battribute.js.html | 4 +- .../becca_entities_bbranch.js.html | 2 +- .../becca_entities_betapi_token.js.html | 2 +- docs/backend_api/becca_entities_bnote.js.html | 112 ++- .../becca_entities_bnote_revision.js.html | 4 +- .../becca_entities_boption.js.html | 9 +- .../becca_entities_brecent_note.js.html | 2 +- docs/backend_api/index.html | 2 +- docs/backend_api/module-sql.html | 2 +- .../services_backend_script_api.js.html | 12 +- docs/backend_api/services_sql.js.html | 6 +- package-lock.json | 1 - src/becca/entities/bnote.js | 2 +- src/public/app/entities/fnote.js | 2 +- 24 files changed, 881 insertions(+), 105 deletions(-) diff --git a/docs/backend_api/AbstractBeccaEntity.html b/docs/backend_api/AbstractBeccaEntity.html index e13c799bc..328d08f0c 100644 --- a/docs/backend_api/AbstractBeccaEntity.html +++ b/docs/backend_api/AbstractBeccaEntity.html @@ -991,7 +991,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    diff --git a/docs/backend_api/BAttribute.html b/docs/backend_api/BAttribute.html index 8f6af88c6..29eb98f90 100644 --- a/docs/backend_api/BAttribute.html +++ b/docs/backend_api/BAttribute.html @@ -1904,7 +1904,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    diff --git a/docs/backend_api/BBranch.html b/docs/backend_api/BBranch.html index ae6e4e014..f75738ee7 100644 --- a/docs/backend_api/BBranch.html +++ b/docs/backend_api/BBranch.html @@ -1916,7 +1916,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    diff --git a/docs/backend_api/BEtapiToken.html b/docs/backend_api/BEtapiToken.html index 1fab94869..2c6eb8218 100644 --- a/docs/backend_api/BEtapiToken.html +++ b/docs/backend_api/BEtapiToken.html @@ -1461,7 +1461,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    diff --git a/docs/backend_api/BNote.html b/docs/backend_api/BNote.html index d38f35c0f..b1d3e2b60 100644 --- a/docs/backend_api/BNote.html +++ b/docs/backend_api/BNote.html @@ -1318,7 +1318,7 @@ See addLabel, addRelation for more specific methods.
    Source:
    @@ -1654,7 +1654,7 @@ See addLabel, addRelation for more specific methods.
    Source:
    @@ -1900,7 +1900,7 @@ returned.
    Source:
    @@ -2135,7 +2135,7 @@ returned.
    Source:
    @@ -2335,7 +2335,7 @@ returned.
    Source:
    @@ -2556,6 +2556,10 @@ returned. +
    + Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles) +
    + @@ -2597,7 +2601,7 @@ returned.
    Source:
    @@ -2703,7 +2707,7 @@ returned.
    Source:
    @@ -2877,7 +2881,7 @@ returned.
    Source:
    @@ -3055,7 +3059,7 @@ returned.
    Source:
    @@ -3316,6 +3320,364 @@ returned. +

    getBestNotePath(hoistedNoteIdopt) → {Array.<string>}

    + + + + + + +
    + Returns note path considered to be the "best" +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    hoistedNoteId + + +string + + + + + + <optional>
    + + + + + +
    + + 'root' + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + +
    + array of noteIds constituting the particular note path +
    + + + +
    +
    + Type +
    +
    + +Array.<string> + + +
    +
    + + + + + + + + + + + + + +

    getBestNotePathString(hoistedNoteIdopt) → {string}

    + + + + + + +
    + Returns note path considered to be the "best" +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    hoistedNoteId + + +string + + + + + + <optional>
    + + + + + +
    + + 'root' + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + +
    + serialized note path (e.g. 'root/a1h315/js725h') +
    + + + +
    +
    + Type +
    +
    + +string + + +
    +
    + + + + + + + + + + + + +

    getBranches() → {Array.<BBranch>}

    @@ -3878,7 +4240,7 @@ returned.
    Source:
    @@ -3968,7 +4330,7 @@ returned.
    Source:
    @@ -4074,7 +4436,7 @@ returned.
    Source:
    @@ -4332,7 +4694,7 @@ returned.
    Source:
    @@ -4490,7 +4852,7 @@ returned.
    Source:
    @@ -4660,7 +5022,7 @@ returned.
    Source:
    @@ -4827,7 +5189,7 @@ returned.
    Source:
    @@ -4933,7 +5295,7 @@ returned.
    Source:
    @@ -5035,7 +5397,7 @@ returned.
    Source:
    @@ -5215,7 +5577,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -5480,7 +5842,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -5635,7 +5997,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -5793,7 +6155,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -5963,7 +6325,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -6130,7 +6492,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -6285,7 +6647,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -6443,7 +6805,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -6613,7 +6975,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -7061,7 +7423,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -7219,7 +7581,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -7389,7 +7751,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -7604,7 +7966,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -7658,6 +8020,177 @@ This method can be significantly faster than the getAttribute() +

    getSortedNotePathRecords(hoistedNoteIdopt) → {Array.<{isArchived: boolean, isInHoistedSubTree: boolean, notePath: Array.<string>, isHidden: boolean}>}

    + + + + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    hoistedNoteId + + +string + + + + + + <optional>
    + + + + + +
    + + 'root' + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + + + +
    +
    + Type +
    +
    + +Array.<{isArchived: boolean, isInHoistedSubTree: boolean, notePath: Array.<string>, isHidden: boolean}> + + +
    +
    + + + + + + + + + + + + +

    getStrongParentBranches() → {Array.<BBranch>}

    @@ -7812,7 +8345,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -7914,7 +8447,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -8020,7 +8553,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -8211,7 +8744,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -8962,7 +9495,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -9160,7 +9693,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -9358,7 +9891,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -9556,7 +10089,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -9706,7 +10239,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -9812,7 +10345,7 @@ This method can be significantly faster than the getAttribute()
    Source:
    @@ -10282,6 +10815,161 @@ This method can be significantly faster than the getAttribute() +

    isLabelTruthy(name) → {boolean}

    + + + + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    name + + +string + + + + label name
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    Returns:
    + + +
    + true if label exists (including inherited) and does not have "false" value. +
    + + + +
    +
    + Type +
    +
    + +boolean + + +
    +
    + + + + + + + + + + + + +

    isRoot() → {boolean}

    @@ -10828,7 +11516,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    Source:
    @@ -11008,7 +11696,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    Source:
    @@ -11188,7 +11876,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    Source:
    @@ -11383,7 +12071,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    Source:
    @@ -11615,7 +12303,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    Source:
    @@ -11795,7 +12483,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    Source:
    @@ -11955,7 +12643,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    Source:
    @@ -12197,7 +12885,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    Source:
    @@ -12408,7 +13096,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    Source:
    @@ -12619,7 +13307,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    Source:
    @@ -12671,7 +13359,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    diff --git a/docs/backend_api/BNoteRevision.html b/docs/backend_api/BNoteRevision.html index 0951fa14d..3a9ccf0d3 100644 --- a/docs/backend_api/BNoteRevision.html +++ b/docs/backend_api/BNoteRevision.html @@ -2174,7 +2174,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    diff --git a/docs/backend_api/BOption.html b/docs/backend_api/BOption.html index b5990cb92..849051190 100644 --- a/docs/backend_api/BOption.html +++ b/docs/backend_api/BOption.html @@ -267,7 +267,7 @@
    Source:
    @@ -335,7 +335,7 @@
    Source:
    @@ -403,7 +403,7 @@
    Source:
    @@ -471,7 +471,7 @@
    Source:
    @@ -1319,7 +1319,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    diff --git a/docs/backend_api/BRecentNote.html b/docs/backend_api/BRecentNote.html index b36b38298..cdda77c51 100644 --- a/docs/backend_api/BRecentNote.html +++ b/docs/backend_api/BRecentNote.html @@ -1251,7 +1251,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
    diff --git a/docs/backend_api/BackendScriptApi.html b/docs/backend_api/BackendScriptApi.html index 12800f161..ba6ffbc2b 100644 --- a/docs/backend_api/BackendScriptApi.html +++ b/docs/backend_api/BackendScriptApi.html @@ -3254,7 +3254,7 @@ JSON MIME type. See also createNewNote() for more options. -

    ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) → {void}

    +

    ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) → {Object}

    @@ -3262,7 +3262,7 @@ JSON MIME type. See also createNewNote() for more options.
    - If there's no branch between note and parent note, create one. Otherwise, do nothing. + If there's no branch between note and parent note, create one. Otherwise, do nothing. Returns the new or existing branch.
    @@ -3437,7 +3437,7 @@ JSON MIME type. See also createNewNote() for more options.
    -void +Object
    @@ -7889,7 +7889,7 @@ exists, then we'll use that transaction.
    diff --git a/docs/backend_api/becca_entities_abstract_becca_entity.js.html b/docs/backend_api/becca_entities_abstract_becca_entity.js.html index 4036de3f6..b64387b2d 100644 --- a/docs/backend_api/becca_entities_abstract_becca_entity.js.html +++ b/docs/backend_api/becca_entities_abstract_becca_entity.js.html @@ -212,7 +212,7 @@ module.exports = AbstractBeccaEntity;
    diff --git a/docs/backend_api/becca_entities_battribute.js.html b/docs/backend_api/becca_entities_battribute.js.html index 83fff96e5..d9715c60b 100644 --- a/docs/backend_api/becca_entities_battribute.js.html +++ b/docs/backend_api/becca_entities_battribute.js.html @@ -124,7 +124,7 @@ class BAttribute extends AbstractBeccaEntity { } if (this.type === 'relation' && !(this.value in this.becca.notes)) { - throw new Error(`Cannot save relation '${this.name}' of note '${this.noteId}' since it target not existing note '${this.value}'.`); + throw new Error(`Cannot save relation '${this.name}' of note '${this.noteId}' since it targets not existing note '${this.value}'.`); } } @@ -276,7 +276,7 @@ module.exports = BAttribute;
    diff --git a/docs/backend_api/becca_entities_bbranch.js.html b/docs/backend_api/becca_entities_bbranch.js.html index b4fa7f567..b2261711d 100644 --- a/docs/backend_api/becca_entities_bbranch.js.html +++ b/docs/backend_api/becca_entities_bbranch.js.html @@ -319,7 +319,7 @@ module.exports = BBranch;
    diff --git a/docs/backend_api/becca_entities_betapi_token.js.html b/docs/backend_api/becca_entities_betapi_token.js.html index 92ab19328..329843522 100644 --- a/docs/backend_api/becca_entities_betapi_token.js.html +++ b/docs/backend_api/becca_entities_betapi_token.js.html @@ -120,7 +120,7 @@ module.exports = BEtapiToken;
    diff --git a/docs/backend_api/becca_entities_bnote.js.html b/docs/backend_api/becca_entities_bnote.js.html index 97889071a..0c729d838 100644 --- a/docs/backend_api/becca_entities_bnote.js.html +++ b/docs/backend_api/becca_entities_bnote.js.html @@ -125,7 +125,7 @@ class BNote extends AbstractBeccaEntity { * @private */ this.parents = []; /** @type {BNote[]} - * @private*/ + * @private */ this.children = []; /** @type {BAttribute[]} * @private */ @@ -135,11 +135,11 @@ class BNote extends AbstractBeccaEntity { * @private */ this.__attributeCache = null; /** @type {BAttribute[]|null} - * @private*/ + * @private */ this.inheritableAttributeCache = null; /** @type {BAttribute[]} - * @private*/ + * @private */ this.targetRelations = []; this.becca.addNote(this.noteId, this); @@ -560,6 +560,20 @@ class BNote extends AbstractBeccaEntity { */ hasLabel(name, value) { return this.hasAttribute(LABEL, name, value); } + /** + * @param {string} name - label name + * @returns {boolean} true if label exists (including inherited) and does not have "false" value. + */ + isLabelTruthy(name) { + const label = this.getLabel(name); + + if (!label) { + return false; + } + + return label && label.value !== 'false'; + } + /** * @param {string} name - label name * @param {string} [value] - label value @@ -761,6 +775,21 @@ class BNote extends AbstractBeccaEntity { return this.hasAttribute('label', 'archived'); } + areAllNotePathsArchived() { + // there's a slight difference between note being itself archived and all its note paths being archived + // - note is archived when it itself has an archived label or inherits it + // - note does not have or inherit archived label, but each note paths contains a note with (non-inheritable) + // archived label + + const bestNotePathRecord = this.getSortedNotePathRecords()[0]; + + if (!bestNotePathRecord) { + throw new Error(`No note path available for note '${this.noteId}'`); + } + + return bestNotePathRecord.isArchived; + } + hasInheritableArchivedLabel() { for (const attr of this.getAttributes()) { if (attr.name === 'archived' && attr.type === LABEL && attr.isInheritable) { @@ -1164,6 +1193,8 @@ class BNote extends AbstractBeccaEntity { } /** + * Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles) + * * @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path) */ getAllNotePaths() { @@ -1171,18 +1202,73 @@ class BNote extends AbstractBeccaEntity { return [['root']]; } - const notePaths = []; + const parentNotes = this.getParentNotes(); + let notePaths = []; - for (const parentNote of this.getParentNotes()) { - for (const parentPath of parentNote.getAllNotePaths()) { - parentPath.push(this.noteId); - notePaths.push(parentPath); - } + if (parentNotes.length === 1) { // optimization for most common case + notePaths = parentNotes[0].getAllNotePaths(); + } else { + notePaths = parentNotes.flatMap(parentNote => parentNote.getAllNotePaths()); + } + + for (const notePath of notePaths) { + notePath.push(this.noteId); } return notePaths; } + /** + * @param {string} [hoistedNoteId='root'] + * @return {Array<{isArchived: boolean, isInHoistedSubTree: boolean, notePath: Array<string>, isHidden: boolean}>} + */ + getSortedNotePathRecords(hoistedNoteId = 'root') { + const isHoistedRoot = hoistedNoteId === 'root'; + + const notePaths = this.getAllNotePaths().map(path => ({ + notePath: path, + isInHoistedSubTree: isHoistedRoot || path.includes(hoistedNoteId), + isArchived: path.some(noteId => this.becca.notes[noteId].isArchived), + isHidden: path.includes('_hidden') + })); + + notePaths.sort((a, b) => { + if (a.isInHoistedSubTree !== b.isInHoistedSubTree) { + return a.isInHoistedSubTree ? -1 : 1; + } else if (a.isArchived !== b.isArchived) { + return a.isArchived ? 1 : -1; + } else if (a.isHidden !== b.isHidden) { + return a.isHidden ? 1 : -1; + } else { + return a.notePath.length - b.notePath.length; + } + }); + + return notePaths; + } + + /** + * Returns note path considered to be the "best" + * + * @param {string} [hoistedNoteId='root'] + * @return {string[]} array of noteIds constituting the particular note path + */ + getBestNotePath(hoistedNoteId = 'root') { + return this.getSortedNotePathRecords(hoistedNoteId)[0]?.notePath; + } + + /** + * Returns note path considered to be the "best" + * + * @param {string} [hoistedNoteId='root'] + * @return {string} serialized note path (e.g. 'root/a1h315/js725h') + */ + getBestNotePathString(hoistedNoteId = 'root') { + const notePath = this.getBestNotePath(hoistedNoteId); + + return notePath?.join("/"); + } + /** * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree */ @@ -1196,9 +1282,7 @@ class BNote extends AbstractBeccaEntity { return false; } else if (parentNote.noteId === '_hidden') { continue; - } - - if (!parentNote.isHiddenCompletely()) { + } else if (!parentNote.isHiddenCompletely()) { return false; } } @@ -1392,7 +1476,7 @@ class BNote extends AbstractBeccaEntity { /** * @param parentNoteId - * @returns {{success: boolean, message: string}} + * @returns {{success: boolean, message: string, branchId: string, notePath: string}} */ cloneTo(parentNoteId) { const cloningService = require("../../services/cloning"); @@ -1550,7 +1634,7 @@ module.exports = BNote;
    diff --git a/docs/backend_api/becca_entities_bnote_revision.js.html b/docs/backend_api/becca_entities_bnote_revision.js.html index c140f1d10..ea663e51f 100644 --- a/docs/backend_api/becca_entities_bnote_revision.js.html +++ b/docs/backend_api/becca_entities_bnote_revision.js.html @@ -194,12 +194,14 @@ class BNoteRevision extends AbstractBeccaEntity { utcDateLastEdited: this.utcDateLastEdited, utcDateCreated: this.utcDateCreated, utcDateModified: this.utcDateModified, + content: this.content, // used when retrieving full note revision to frontend contentLength: this.contentLength }; } getPojoToSave() { const pojo = this.getPojo(); + delete pojo.content; // not getting persisted delete pojo.contentLength; // not getting persisted if (pojo.isProtected) { @@ -233,7 +235,7 @@ module.exports = BNoteRevision;
    diff --git a/docs/backend_api/becca_entities_boption.js.html b/docs/backend_api/becca_entities_boption.js.html index ab2c22f51..0f2856fdc 100644 --- a/docs/backend_api/becca_entities_boption.js.html +++ b/docs/backend_api/becca_entities_boption.js.html @@ -44,6 +44,11 @@ class BOption extends AbstractBeccaEntity { constructor(row) { super(); + this.updateFromRow(row); + this.becca.options[this.name] = this; + } + + updateFromRow(row) { /** @type {string} */ this.name = row.name; /** @type {string} */ @@ -52,8 +57,6 @@ class BOption extends AbstractBeccaEntity { this.isSynced = !!row.isSynced; /** @type {string} */ this.utcDateModified = row.utcDateModified; - - this.becca.options[this.name] = this; } beforeSaving() { @@ -89,7 +92,7 @@ module.exports = BOption;
    diff --git a/docs/backend_api/becca_entities_brecent_note.js.html b/docs/backend_api/becca_entities_brecent_note.js.html index 2e2f5ccb1..02f6858fb 100644 --- a/docs/backend_api/becca_entities_brecent_note.js.html +++ b/docs/backend_api/becca_entities_brecent_note.js.html @@ -77,7 +77,7 @@ module.exports = BRecentNote;
    diff --git a/docs/backend_api/index.html b/docs/backend_api/index.html index 55d778a7e..f9f7919b5 100644 --- a/docs/backend_api/index.html +++ b/docs/backend_api/index.html @@ -56,7 +56,7 @@
    diff --git a/docs/backend_api/module-sql.html b/docs/backend_api/module-sql.html index 6517bc0d4..2605d6530 100644 --- a/docs/backend_api/module-sql.html +++ b/docs/backend_api/module-sql.html @@ -1300,7 +1300,7 @@
    diff --git a/docs/backend_api/services_backend_script_api.js.html b/docs/backend_api/services_backend_script_api.js.html index 44f40bed6..bffce4bcd 100644 --- a/docs/backend_api/services_backend_script_api.js.html +++ b/docs/backend_api/services_backend_script_api.js.html @@ -165,13 +165,13 @@ function BackendScriptApi(currentNote, apiParams) { this.getNoteWithLabel = attributeService.getNoteWithLabel; /** - * If there's no branch between note and parent note, create one. Otherwise, do nothing. + * If there's no branch between note and parent note, create one. Otherwise, do nothing. Returns the new or existing branch. * * @method * @param {string} noteId * @param {string} parentNoteId * @param {string} prefix - if branch will be created between note and parent note, set this prefix - * @returns {void} + * @returns {{branch: BBranch|null}} */ this.ensureNoteIsPresentInParent = cloningService.ensureNoteIsPresentInParent; @@ -499,11 +499,11 @@ function BackendScriptApi(currentNote, apiParams) { if (opts.type === 'script' && !opts.scriptNoteId) { throw new Error("scriptNoteId is mandatory for launchers of type 'script'"); } if (opts.type === 'customWidget' && !opts.widgetNoteId) { throw new Error("widgetNoteId is mandatory for launchers of type 'customWidget'"); } - const parentNoteId = !!opts.isVisible ? '_lbVisibleLaunchers' : '_lbAvailableLaunchers'; + const parentNoteId = opts.isVisible ? '_lbVisibleLaunchers' : '_lbAvailableLaunchers'; const noteId = 'al_' + opts.id; const launcherNote = - becca.getNote(opts.id) || + becca.getNote(noteId) || specialNotesService.createLauncher({ noteId: noteId, parentNoteId: parentNoteId, @@ -542,7 +542,7 @@ function BackendScriptApi(currentNote, apiParams) { if (opts.icon) { launcherNote.setLabel('iconClass', `bx ${opts.icon}`); } else { - launcherNote.removeLabel('keyboardShortcut'); + launcherNote.removeLabel('iconClass'); } return {note: launcherNote}; @@ -584,7 +584,7 @@ module.exports = BackendScriptApi;
    diff --git a/docs/backend_api/services_sql.js.html b/docs/backend_api/services_sql.js.html index ec2ac56e6..783bf8301 100644 --- a/docs/backend_api/services_sql.js.html +++ b/docs/backend_api/services_sql.js.html @@ -245,7 +245,7 @@ function wrap(query, func) { // in these cases error should be simply ignored. console.log(e.message); - return null + return null; } throw e; @@ -309,7 +309,7 @@ function fillParamList(paramIds, truncate = true) { } // doing it manually to avoid this showing up on the sloq query list - const s = stmt(`INSERT INTO param_list VALUES ${paramIds.map(paramId => `(?)`).join(',')}`, paramIds); + const s = stmt(`INSERT INTO param_list VALUES ${paramIds.map(paramId => `(?)`).join(',')}`); s.run(paramIds); } @@ -413,7 +413,7 @@ module.exports = {
    diff --git a/package-lock.json b/package-lock.json index 6055a636e..682c4f5a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,6 @@ "requires": true, "packages": { "": { - "name": "trilium", "version": "0.60.1-beta", "hasInstallScript": true, "license": "AGPL-3.0-only", diff --git a/src/becca/entities/bnote.js b/src/becca/entities/bnote.js index b8e58c740..0be9a3ab4 100644 --- a/src/becca/entities/bnote.js +++ b/src/becca/entities/bnote.js @@ -1192,7 +1192,7 @@ class BNote extends AbstractBeccaEntity { /** * @param {string} [hoistedNoteId='root'] - * @return {{isArchived: boolean, isInHoistedSubTree: boolean, notePath: string[], isHidden: boolean}[]} + * @return {Array<{isArchived: boolean, isInHoistedSubTree: boolean, notePath: Array, isHidden: boolean}>} */ getSortedNotePathRecords(hoistedNoteId = 'root') { const isHoistedRoot = hoistedNoteId === 'root'; diff --git a/src/public/app/entities/fnote.js b/src/public/app/entities/fnote.js index eff96df81..d73f14de2 100644 --- a/src/public/app/entities/fnote.js +++ b/src/public/app/entities/fnote.js @@ -332,7 +332,7 @@ class FNote { /** * @param {string} [hoistedNoteId='root'] - * @return {{isArchived: boolean, isInHoistedSubTree: boolean, notePath: string[], isHidden: boolean}[]} + * @return {Array<{isArchived: boolean, isInHoistedSubTree: boolean, notePath: Array, isHidden: boolean}>} */ getSortedNotePathRecords(hoistedNoteId = 'root') { const isHoistedRoot = hoistedNoteId === 'root'; From a6ade790c64ad9ee68f63d53743e0f90c7898672 Mon Sep 17 00:00:00 2001 From: zadam Date: Wed, 31 May 2023 14:35:08 +0200 Subject: [PATCH 17/40] script api docs --- docs/frontend_api/FAttribute.html | 2 +- docs/frontend_api/FBranch.html | 2 +- docs/frontend_api/FNote.html | 888 +++++++++++++++++- docs/frontend_api/FNoteComplement.html | 2 +- docs/frontend_api/FrontendScriptApi.html | 520 +--------- docs/frontend_api/entities_fattribute.js.html | 2 +- docs/frontend_api/entities_fbranch.js.html | 2 +- docs/frontend_api/entities_fnote.js.html | 139 ++- .../entities_fnote_complement.js.html | 2 +- docs/frontend_api/index.html | 2 +- .../services_frontend_script_api.js.html | 34 +- 11 files changed, 990 insertions(+), 605 deletions(-) diff --git a/docs/frontend_api/FAttribute.html b/docs/frontend_api/FAttribute.html index 641c3a737..901dc598c 100644 --- a/docs/frontend_api/FAttribute.html +++ b/docs/frontend_api/FAttribute.html @@ -850,7 +850,7 @@ and relation (representing named relationship between source and target note) diff --git a/docs/frontend_api/FBranch.html b/docs/frontend_api/FBranch.html index 284bc72d5..4ec45ea97 100644 --- a/docs/frontend_api/FBranch.html +++ b/docs/frontend_api/FBranch.html @@ -1062,7 +1062,7 @@ parents.

diff --git a/docs/frontend_api/FNote.html b/docs/frontend_api/FNote.html index cff8d1e8f..cf7e9782c 100644 --- a/docs/frontend_api/FNote.html +++ b/docs/frontend_api/FNote.html @@ -977,6 +977,116 @@ +

getAllNotePaths() → {Array.<Array.<string>>}

+ + + + + + +
+ Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles) +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ - array of notePaths (each represented by array of noteIds constituting the particular note path) +
+ + + +
+
+ Type +
+
+ +Array.<Array.<string>> + + +
+
+ + + + + + + + + + + + +

getAttribute(type, name) → {FAttribute}

@@ -1097,7 +1207,7 @@
Source:
@@ -1275,7 +1385,7 @@
Source:
@@ -1475,7 +1585,7 @@
Source:
@@ -1533,6 +1643,364 @@ +

getBestNotePath(hoistedNoteIdopt) → {Array.<string>}

+ + + + + + +
+ Returns note path considered to be the "best" +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
hoistedNoteId + + +string + + + + + + <optional>
+ + + + + +
+ + 'root' + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ array of noteIds constituting the particular note path +
+ + + +
+
+ Type +
+
+ +Array.<string> + + +
+
+ + + + + + + + + + + + + +

getBestNotePathString(hoistedNoteIdopt) → {string}

+ + + + + + +
+ Returns note path considered to be the "best" +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
hoistedNoteId + + +string + + + + + + <optional>
+ + + + + +
+ + 'root' + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ serialized note path (e.g. 'root/a1h315/js725h') +
+ + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + + + +

getBranchIds() → {Array.<string>}

@@ -1583,7 +2051,7 @@
Source:
@@ -1687,7 +2155,7 @@
Source:
@@ -1789,7 +2257,7 @@
Source:
@@ -1891,7 +2359,7 @@
Source:
@@ -1993,7 +2461,7 @@
Source:
@@ -2144,7 +2612,7 @@
Source:
@@ -2299,7 +2767,7 @@
Source:
@@ -2466,7 +2934,7 @@
Source:
@@ -2576,7 +3044,7 @@
Source:
@@ -2678,7 +3146,7 @@
Source:
@@ -2852,7 +3320,7 @@
Source:
@@ -3030,7 +3498,7 @@
Source:
@@ -3230,7 +3698,7 @@
Source:
@@ -3385,7 +3853,7 @@
Source:
@@ -3540,7 +4008,7 @@
Source:
@@ -3707,7 +4175,7 @@
Source:
@@ -3862,7 +4330,7 @@
Source:
@@ -4017,7 +4485,7 @@
Source:
@@ -4184,7 +4652,7 @@
Source:
@@ -4290,7 +4758,7 @@
Source:
@@ -4392,7 +4860,7 @@
Source:
@@ -4494,7 +4962,7 @@
Source:
@@ -4596,7 +5064,7 @@
Source:
@@ -4747,7 +5215,7 @@
Source:
@@ -4902,7 +5370,7 @@
Source:
@@ -5072,7 +5540,7 @@
Source:
@@ -5223,7 +5691,7 @@
Source:
@@ -5390,7 +5858,7 @@
Source:
@@ -5496,7 +5964,7 @@
Source:
@@ -5557,6 +6025,177 @@ +

getSortedNotePathRecords(hoistedNoteIdopt) → {Array.<{isArchived: boolean, isInHoistedSubTree: boolean, notePath: Array.<string>, isHidden: boolean}>}

+ + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
hoistedNoteId + + +string + + + + + + <optional>
+ + + + + +
+ + 'root' + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +Array.<{isArchived: boolean, isInHoistedSubTree: boolean, notePath: Array.<string>, isHidden: boolean}> + + +
+
+ + + + + + + + + + + + +

(async) getTargetRelationSourceNotes() → {Array.<FNote>}

@@ -5609,7 +6248,7 @@
Source:
@@ -5715,7 +6354,7 @@
Source:
@@ -5889,7 +6528,7 @@
Source:
@@ -5995,7 +6634,7 @@
Source:
@@ -6146,7 +6785,7 @@
Source:
@@ -6324,7 +6963,7 @@
Source:
@@ -6479,7 +7118,7 @@
Source:
@@ -6634,7 +7273,7 @@
Source:
@@ -6789,7 +7428,7 @@
Source:
@@ -6897,7 +7536,7 @@
Source:
@@ -6981,7 +7620,7 @@
Source:
@@ -7075,7 +7714,7 @@
Source:
@@ -7181,7 +7820,7 @@
Source:
@@ -7287,7 +7926,7 @@
Source:
@@ -7336,6 +7975,161 @@ + + + + + +

isLabelTruthy(name) → {boolean}

+ + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + + +string + + + + label name
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ true if label exists (including inherited) and does not have "false" value. +
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + @@ -7357,7 +8151,7 @@
diff --git a/docs/frontend_api/FNoteComplement.html b/docs/frontend_api/FNoteComplement.html index 8c1bb3d94..1cac29fba 100644 --- a/docs/frontend_api/FNoteComplement.html +++ b/docs/frontend_api/FNoteComplement.html @@ -781,7 +781,7 @@
diff --git a/docs/frontend_api/FrontendScriptApi.html b/docs/frontend_api/FrontendScriptApi.html index e866a8473..5e94a32c2 100644 --- a/docs/frontend_api/FrontendScriptApi.html +++ b/docs/frontend_api/FrontendScriptApi.html @@ -342,115 +342,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
- - - - - - - - - - - - - - - - -

CollapsibleWidget

- - - - - - - - - - -
Properties:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDescription
- - -RightPanelWidget - - - -
- - - - -
- - - - - - - - - - - - - - - - -
Deprecated:
  • use api.RightPanelWidget instead
- - - - - - - - - - - -
Source:
-
@@ -556,115 +448,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
- - - - - - - -
- - - - - - - - -

NoteContextCachingWidget

- - - - - - - - - - -
Properties:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDescription
- - -NoteContextAwareWidget - - - -
- - - - -
- - - - - - - - - - - - - - - - -
Deprecated:
  • use NoteContextAwareWidget instead
- - - - - - - - - - - -
Source:
-
@@ -770,223 +554,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
- - - - - - - -
- - - - - - - - -

TabAwareWidget

- - - - - - - - - - -
Properties:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDescription
- - -NoteContextAwareWidget - - - -
- - - - -
- - - - - - - - - - - - - - - - -
Deprecated:
  • use NoteContextAwareWidget instead
- - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - -

TabCachingWidget

- - - - - - - - - - -
Properties:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDescription
- - -NoteContextAwareWidget - - - -
- - - - -
- - - - - - - - - - - - - - - - -
Deprecated:
  • use NoteContextAwareWidget instead
- - - - - - - - - - - -
Source:
-
@@ -1558,7 +1126,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -1713,7 +1281,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -2054,7 +1622,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -2191,7 +1759,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -2399,7 +1967,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -2781,7 +2349,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -2914,7 +2482,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -3024,7 +2592,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -3130,7 +2698,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -3236,7 +2804,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -3346,7 +2914,7 @@ available in the JS frontend notes. You can use e.g. api.showMessage(api.s
Source:
@@ -3457,7 +3025,7 @@ implementation of actual widget type.
Source:
@@ -3612,7 +3180,7 @@ implementation of actual widget type.
Source:
@@ -3767,7 +3335,7 @@ implementation of actual widget type.
Source:
@@ -3874,7 +3442,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -4029,7 +3597,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -4185,7 +3753,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -4386,7 +3954,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -4492,7 +4060,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -4647,7 +4215,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -4802,7 +4370,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -4952,7 +4520,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -5130,7 +4698,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -5308,7 +4876,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -5459,7 +5027,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -5637,7 +5205,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -5811,7 +5379,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -5966,7 +5534,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -6120,7 +5688,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -6275,7 +5843,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -6436,7 +6004,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -6596,7 +6164,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -6752,7 +6320,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -6907,7 +6475,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -7058,7 +6626,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -7213,7 +6781,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -7350,7 +6918,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -7510,7 +7078,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -7670,7 +7238,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -7762,7 +7330,7 @@ Typical use case is when new note has been created, we should wait until it is s
Source:
@@ -7832,7 +7400,7 @@ Typical use case is when new note has been created, we should wait until it is s
diff --git a/docs/frontend_api/entities_fattribute.js.html b/docs/frontend_api/entities_fattribute.js.html index dffe972bc..a9cb49a5f 100644 --- a/docs/frontend_api/entities_fattribute.js.html +++ b/docs/frontend_api/entities_fattribute.js.html @@ -121,7 +121,7 @@ export default FAttribute;
diff --git a/docs/frontend_api/entities_fbranch.js.html b/docs/frontend_api/entities_fbranch.js.html index 2b7ced98d..deae06846 100644 --- a/docs/frontend_api/entities_fbranch.js.html +++ b/docs/frontend_api/entities_fbranch.js.html @@ -105,7 +105,7 @@ export default FBranch;
diff --git a/docs/frontend_api/entities_fnote.js.html b/docs/frontend_api/entities_fnote.js.html index a31f615a2..84616e2cb 100644 --- a/docs/frontend_api/entities_fnote.js.html +++ b/docs/frontend_api/entities_fnote.js.html @@ -101,7 +101,7 @@ class FNote { this.mime = row.mime; } - addParent(parentNoteId, branchId) { + addParent(parentNoteId, branchId, sort = true) { if (parentNoteId === 'none') { return; } @@ -111,6 +111,10 @@ class FNote { } this.parentToBranch[parentNoteId] = branchId; + + if (sort) { + this.sortParents(); + } } addChild(childNoteId, branchId, sort = true) { @@ -217,7 +221,7 @@ class FNote { // will sort the parents so that non-search & non-archived are first and archived at the end // this is done so that non-search & non-archived paths are always explored as first when looking for note path - resortParents() { + sortParents() { this.parents.sort((aNoteId, bNoteId) => { const aBranchId = this.parentToBranch[aNoteId]; @@ -225,7 +229,7 @@ class FNote { return 1; } - const aNote = this.froca.getNoteFromCache([aNoteId]); + const aNote = this.froca.getNoteFromCache(aNoteId); if (aNote.isArchived || aNote.isHiddenCompletely()) { return 1; @@ -271,6 +275,11 @@ class FNote { return this.__filterAttrs(this.__getCachedAttributes([]), type, name); } + /** + * @param {string[]} path + * @return {FAttribute[]} + * @private + */ __getCachedAttributes(path) { // notes/clones cannot form tree cycles, it is possible to create attribute inheritance cycle via templates // when template instance is a parent of template itself @@ -323,63 +332,49 @@ class FNote { return this.noteId === 'root'; } - getAllNotePaths(encounteredNoteIds = null) { + /** + * Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles) + * + * @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path) + */ + getAllNotePaths() { if (this.noteId === 'root') { return [['root']]; } - if (!encounteredNoteIds) { - encounteredNoteIds = new Set(); + const parentNotes = this.getParentNotes().filter(note => note.type !== 'search'); + let notePaths = []; + + if (parentNotes.length === 1) { // optimization for most common case + notePaths = parentNotes[0].getAllNotePaths(); + } else { + notePaths = parentNotes.flatMap(parentNote => parentNote.getAllNotePaths()); } - encounteredNoteIds.add(this.noteId); - - const parentNotes = this.getParentNotes(); - let paths; - - if (parentNotes.length === 1) { // optimization for the most common case - if (encounteredNoteIds.has(parentNotes[0].noteId)) { - return []; - } - else { - paths = parentNotes[0].getAllNotePaths(encounteredNoteIds); - } - } - else { - paths = []; - - for (const parentNote of parentNotes) { - if (encounteredNoteIds.has(parentNote.noteId)) { - continue; - } - - const newSet = new Set(encounteredNoteIds); - - paths.push(...parentNote.getAllNotePaths(newSet)); - } + for (const notePath of notePaths) { + notePath.push(this.noteId); } - for (const path of paths) { - path.push(this.noteId); - } - - return paths; + return notePaths; } - getSortedNotePaths(hoistedNotePath = 'root') { + /** + * @param {string} [hoistedNoteId='root'] + * @return {Array<{isArchived: boolean, isInHoistedSubTree: boolean, notePath: Array<string>, isHidden: boolean}>} + */ + getSortedNotePathRecords(hoistedNoteId = 'root') { + const isHoistedRoot = hoistedNoteId === 'root'; + const notePaths = this.getAllNotePaths().map(path => ({ notePath: path, - isInHoistedSubTree: path.includes(hoistedNotePath), - isArchived: path.find(noteId => froca.notes[noteId].isArchived), - isSearch: path.find(noteId => froca.notes[noteId].type === 'search'), + isInHoistedSubTree: isHoistedRoot || path.includes(hoistedNoteId), + isArchived: path.some(noteId => froca.notes[noteId].isArchived), isHidden: path.includes('_hidden') })); notePaths.sort((a, b) => { if (a.isInHoistedSubTree !== b.isInHoistedSubTree) { return a.isInHoistedSubTree ? -1 : 1; - } else if (a.isSearch !== b.isSearch) { - return a.isSearch ? 1 : -1; } else if (a.isArchived !== b.isArchived) { return a.isArchived ? 1 : -1; } else if (a.isHidden !== b.isHidden) { @@ -392,6 +387,28 @@ class FNote { return notePaths; } + /** + * Returns note path considered to be the "best" + * + * @param {string} [hoistedNoteId='root'] + * @return {string[]} array of noteIds constituting the particular note path + */ + getBestNotePath(hoistedNoteId = 'root') { + return this.getSortedNotePathRecords(hoistedNoteId)[0]?.notePath; + } + + /** + * Returns note path considered to be the "best" + * + * @param {string} [hoistedNoteId='root'] + * @return {string} serialized note path (e.g. 'root/a1h315/js725h') + */ + getBestNotePathString(hoistedNoteId = 'root') { + const notePath = this.getBestNotePath(hoistedNoteId); + + return notePath?.join("/"); + } + /** * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree */ @@ -403,7 +420,7 @@ class FNote { for (const parentNote of this.getParentNotes()) { if (parentNote.noteId === 'root') { return false; - } else if (parentNote.noteId === '_hidden') { + } else if (parentNote.noteId === '_hidden' || parentNote.type === 'search') { continue; } @@ -415,6 +432,13 @@ class FNote { return true; } + /** + * @param {FAttribute[]} attributes + * @param {string} type + * @param {string} name + * @return {FAttribute[]} + * @private + */ __filterAttrs(attributes, type, name) { this.__validateTypeName(type, name); @@ -551,7 +575,9 @@ class FNote { * @returns {boolean} true if note has an attribute with given type and name (including inherited) */ hasAttribute(type, name) { - return !!this.getAttribute(type, name); + const attributes = this.getAttributes(); + + return attributes.some(attr => attr.name === name && attr.type === type); } /** @@ -619,6 +645,20 @@ class FNote { */ hasLabel(name) { return this.hasAttribute(LABEL, name); } + /** + * @param {string} name - label name + * @returns {boolean} true if label exists (including inherited) and does not have "false" value. + */ + isLabelTruthy(name) { + const label = this.getLabel(name); + + if (!label) { + return false; + } + + return label && label.value !== 'false'; + } + /** * @param {string} name - relation name * @returns {boolean} true if relation exists (excluding inherited) @@ -730,7 +770,14 @@ class FNote { }); // attrs are not resorted if position changes after initial load - promotedAttrs.sort((a, b) => a.position < b.position ? -1 : 1); + promotedAttrs.sort((a, b) => { + if (a.noteId === b.noteId) { + return a.position < b.position ? -1 : 1; + } else { + // inherited promoted attributes should stay grouped: https://github.com/zadam/trilium/issues/3761 + return a.noteId < b.noteId ? -1 : 1; + } + }); return promotedAttrs; } @@ -930,7 +977,7 @@ export default FNote;
diff --git a/docs/frontend_api/entities_fnote_complement.js.html b/docs/frontend_api/entities_fnote_complement.js.html index 6ea35e95a..252ea6a2f 100644 --- a/docs/frontend_api/entities_fnote_complement.js.html +++ b/docs/frontend_api/entities_fnote_complement.js.html @@ -82,7 +82,7 @@ export default FNoteComplement;
diff --git a/docs/frontend_api/index.html b/docs/frontend_api/index.html index 08a8eb159..6465f193a 100644 --- a/docs/frontend_api/index.html +++ b/docs/frontend_api/index.html @@ -56,7 +56,7 @@
diff --git a/docs/frontend_api/services_frontend_script_api.js.html b/docs/frontend_api/services_frontend_script_api.js.html index a326810c3..679f2e5eb 100644 --- a/docs/frontend_api/services_frontend_script_api.js.html +++ b/docs/frontend_api/services_frontend_script_api.js.html @@ -63,36 +63,12 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain /** @property {dayjs} day.js library for date manipulation. See {@link https://day.js.org} for documentation */ this.dayjs = dayjs; - /** - * @property {RightPanelWidget} - * @deprecated use api.RightPanelWidget instead - */ - this.CollapsibleWidget = RightPanelWidget; - /** @property {RightPanelWidget} */ this.RightPanelWidget = RightPanelWidget; /** @property {NoteContextAwareWidget} */ this.NoteContextAwareWidget = NoteContextAwareWidget; - /** - * @property {NoteContextAwareWidget} - * @deprecated use NoteContextAwareWidget instead - */ - this.TabAwareWidget = NoteContextAwareWidget; - - /** - * @property {NoteContextAwareWidget} - * @deprecated use NoteContextAwareWidget instead - */ - this.TabCachingWidget = NoteContextAwareWidget; - - /** - * @property {NoteContextAwareWidget} - * @deprecated use NoteContextAwareWidget instead - */ - this.NoteContextCachingWidget = NoteContextAwareWidget; - /** @property {BasicWidget} */ this.BasicWidget = BasicWidget; @@ -117,7 +93,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain await ws.waitForMaxKnownEntityChangeId(); await appContext.tabManager.getActiveContext().setNote(notePath); - appContext.triggerEvent('focusAndSelectTitle'); + await appContext.triggerEvent('focusAndSelectTitle'); }; /** @@ -134,7 +110,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain await appContext.tabManager.openContextWithNote(notePath, { activate }); if (activate) { - appContext.triggerEvent('focusAndSelectTitle'); + await appContext.triggerEvent('focusAndSelectTitle'); } }; @@ -152,10 +128,10 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain const subContexts = appContext.tabManager.getActiveContext().getSubContexts(); const {ntxId} = subContexts[subContexts.length - 1]; - appContext.triggerCommand("openNewNoteSplit", {ntxId, notePath}); + await appContext.triggerCommand("openNewNoteSplit", {ntxId, notePath}); if (activate) { - appContext.triggerEvent('focusAndSelectTitle'); + await appContext.triggerEvent('focusAndSelectTitle'); } }; @@ -581,7 +557,7 @@ export default FrontendScriptApi;
From d2d286a4ff708bfe9e6bf2c04935379b9fd0db68 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Wed, 31 May 2023 21:47:43 +0800 Subject: [PATCH 18/40] Show highlighted text in the left pane --- src/public/app/layouts/desktop_layout.js | 4 +- src/public/app/widgets/highlighted_text.js | 36 ++++----- .../options/text_notes/highlighted_text.js | 81 +++++++++---------- 3 files changed, 59 insertions(+), 62 deletions(-) diff --git a/src/public/app/layouts/desktop_layout.js b/src/public/app/layouts/desktop_layout.js index 3a9ec2930..0125e4ed1 100644 --- a/src/public/app/layouts/desktop_layout.js +++ b/src/public/app/layouts/desktop_layout.js @@ -44,7 +44,7 @@ import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js"; import SharedInfoWidget from "../widgets/shared_info.js"; import FindWidget from "../widgets/find.js"; import TocWidget from "../widgets/toc.js"; -import HltWidget from "../widgets/highlighted_text.js"; +import HighlightTextWidget from "../widgets/highlighted_text.js"; import BulkActionsDialog from "../widgets/dialogs/bulk_actions.js"; import AboutDialog from "../widgets/dialogs/about.js"; import HelpDialog from "../widgets/dialogs/help.js"; @@ -182,7 +182,7 @@ export default class DesktopLayout { ) .child(new RightPaneContainer() .child(new TocWidget()) - .child(new HltWidget()) + .child(new HighlightTextWidget()) .child(...this.customWidgets.get('right-pane')) ) ) diff --git a/src/public/app/widgets/highlighted_text.js b/src/public/app/widgets/highlighted_text.js index 3dbfc9b5c..43bc377ce 100644 --- a/src/public/app/widgets/highlighted_text.js +++ b/src/public/app/widgets/highlighted_text.js @@ -10,41 +10,41 @@ import RightPanelWidget from "./right_panel_widget.js"; import options from "../services/options.js"; import OnClickButtonWidget from "./buttons/onclick_button.js"; -const TPL = `
+const TPL = `
- +
`; -export default class HltWidget extends RightPanelWidget { +export default class HighlightTextWidget extends RightPanelWidget { constructor() { super(); @@ -59,20 +59,20 @@ export default class HltWidget extends RightPanelWidget { isEnabled() { return super.isEnabled() && this.note.type === 'text' - && !this.noteContext.viewScope.hltTemporarilyHidden + && !this.noteContext.viewScope.highlightedTextTemporarilyHidden && this.noteContext.viewScope.viewMode === 'default'; } async doRenderBody() { this.$body.empty().append($(TPL)); - this.$hlt = this.$body.find('.hlt'); - this.$body.find('.hlt-widget').append(this.closeHltButton.render()); + this.$hlt = this.$body.find('.highlighted-text'); + this.$body.find('.highlighted-text-widget').append(this.closeHltButton.render()); } async refreshWithNote(note) { - const hltLabel = note.getLabel('hlt'); + const hltLabel = note.getLabel('hideHighlightWidget'); - if (hltLabel?.value === 'hide') { + if (hltLabel?.value=="" || hltLabel?.value=== "true") { this.toggleInt(false); this.triggerCommand("reEvaluateRightPaneVisibility"); return; @@ -90,7 +90,7 @@ export default class HltWidget extends RightPanelWidget { } this.$hlt.html($hlt); this.toggleInt( - ["", "show"].includes(hltLabel?.value) + [undefined, "false"].includes(hltLabel?.value) || hltColors!="" || hltBgColors!="" ); @@ -200,7 +200,7 @@ export default class HltWidget extends RightPanelWidget { $li.css("display","none"); } //The font color and background color may be nested or adjacent to each other. At this time, connect the front and back li to avoid interruption - if(hltIndex!=0 && hltBCs[hltIndex-1].nextSibling ===hltBCs[hltIndex] && $hlt.children().last().css("display")!="none"){ + if(hltIndex!=0 && hltBCs[hltIndex-1].nextSibling === hltBCs[hltIndex] && $hlt.children().last().css("display")!="none"){ $hlt.children().last().append($li.html()); }else{ $li.on("click", () => this.jumpToHlt(hltIndex)); @@ -233,7 +233,7 @@ export default class HltWidget extends RightPanelWidget { } async closeHltCommand() { - this.noteContext.viewScope.hltTemporarilyHidden = true; + this.noteContext.viewScope.highlightedTextTemporarilyHidden = true; await this.refresh(); this.triggerCommand('reEvaluateRightPaneVisibility'); } @@ -255,13 +255,13 @@ class CloseHltButton extends OnClickButtonWidget { super(); this.icon("bx-x") - .title("Close HLT") + .title("Close HighlightTextWidget") .titlePlacement("bottom") .onClick((widget, e) => { e.stopPropagation(); widget.triggerCommand("closeHlt"); }) - .class("icon-action close-hlt"); + .class("icon-action close-highlighted-text"); } } diff --git a/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js b/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js index 7fc2ce825..ec884959a 100644 --- a/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js +++ b/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js @@ -1,67 +1,64 @@ import OptionsWidget from "../options_widget.js"; const TPL = ` -
+

Highlighted Text

- Displays highlighted text in the left pane. You can customize the highlighted text displayed in the left pane: + Displays highlighted text in the right panel. You can customize the highlighted text displayed in the right panel:
Text color:
- - - - - - - - - - - - - - - + + + + + + + + + + + + + + +
Background color:
- - - - - - - - - - - - - - - + + + + + + + + + + + + + + +
`; export default class HighlightedTextOptions extends OptionsWidget { doRender() { this.$widget = $(TPL); - this.$hltColors = this.$widget.find(".hlt-color"); + this.$hltColors = this.$widget.find(".highlighted-text-color"); this.$hltColors.on('change', () => { - const hltColorVals=this.$widget.find('input.hlt-color[type="checkbox"]:checked').map(function() { + const hltColorVals=this.$widget.find('input.highlighted-text-color[type="checkbox"]:checked').map(function() { return this.value; }).get(); this.updateOption('highlightedTextColors', JSON.stringify(hltColorVals)); }); - this.$hltBgColors = this.$widget.find(".hlt-background-color"); + this.$hltBgColors = this.$widget.find(".highlighted-text-background-color"); this.$hltBgColors.on('change', () =>{ - const hltBgColorVals=this.$widget.find('input.hlt-background-color[type="checkbox"]:checked').map(function() { + const hltBgColorVals=this.$widget.find('input.highlighted-text-background-color[type="checkbox"]:checked').map(function() { return this.value; }).get(); this.updateOption('highlightedTextBgColors', JSON.stringify(hltBgColorVals)); @@ -72,14 +69,14 @@ export default class HighlightedTextOptions extends OptionsWidget { async optionsLoaded(options) { const hltColorVals=JSON.parse(options.highlightedTextColors); const hltBgColorVals=JSON.parse(options.highlightedTextBgColors); - this.$widget.find('input.hlt-color[type="checkbox"]').each(function () { + this.$widget.find('input.highlighted-text-color[type="checkbox"]').each(function () { if ($.inArray($(this).val(), hltColorVals) !== -1) { $(this).prop("checked", true); } else { $(this).prop("checked", false); } }); - this.$widget.find('input.hlt-background-color[type="checkbox"]').each(function () { + this.$widget.find('input.highlighted-text-background-color[type="checkbox"]').each(function () { if ($.inArray($(this).val(), hltBgColorVals) !== -1) { $(this).prop("checked", true); } else { From 92d5aeae41ea384c1b0249bd70721481bf7b4dfc Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Wed, 31 May 2023 22:34:24 +0800 Subject: [PATCH 19/40] Show highlighted text in the right panel --- src/public/app/widgets/highlighted_text.js | 11 ++-- .../options/text_notes/highlighted_text.js | 62 +++++++++---------- src/services/options_init.js | 4 +- 3 files changed, 40 insertions(+), 37 deletions(-) diff --git a/src/public/app/widgets/highlighted_text.js b/src/public/app/widgets/highlighted_text.js index 43bc377ce..c9f3ef08c 100644 --- a/src/public/app/widgets/highlighted_text.js +++ b/src/public/app/widgets/highlighted_text.js @@ -82,11 +82,14 @@ export default class HighlightTextWidget extends RightPanelWidget { let optionsHltColors = JSON.parse(options.get('highlightedTextColors')); let optionsHltBgColors = JSON.parse(options.get('highlightedTextBgColors')); + const colorToValDic={"Dark": "#000000", "Dim grey": "#4d4d4d", "Grey": "#999999", "Light grey": "#e6e6e6", "White": "#ffffff", "Red": "#e64c4c", "Orange": "#e6994c", "Yellow": "#e6e64c", "Light green": "#99e64c", "Green": "#4ce64c", "Aquamarine": "#4ce699", "Turquoise": "#4ce6e6", "Light blue": "#4c99e6", "Blue": "#4c4ce6", "Purple": "#994ce6"} + const optionsHltColorsVal = optionsHltColors.map(color => colorToValDic[color]); + const optionsHltBgColorsVal = optionsHltBgColors.map(color => colorToValDic[color]); // Check for type text unconditionally in case alwaysShowWidget is set if (this.note.type === 'text') { const { content } = await note.getNoteComplement(); //hltColors/hltBgColors are the colors/background-color that appear in notes and in options - ({ $hlt, hltColors, hltBgColors } = await this.getHlt(content, optionsHltColors, optionsHltBgColors)); + ({ $hlt, hltColors, hltBgColors } = await this.getHlt(content, optionsHltColorsVal, optionsHltBgColorsVal)); } this.$hlt.html($hlt); this.toggleInt( @@ -167,7 +170,7 @@ export default class HighlightTextWidget extends RightPanelWidget { /** * Builds a jquery table of helight text. */ - getHlt(html, optionsHltColors, optionsHltBgColors) { + getHlt(html, optionsHltColorsVal, optionsHltBgColorsVal) { const hltBCs = $(html).find(`span[style*="background-color"],span[style*="color"]`) const $hlt = $("
    "); let hltColors = []; @@ -181,7 +184,7 @@ export default class HighlightTextWidget extends RightPanelWidget { if (color != "") { var hexColor = this.colorToHex(color); - if (this.hexIsInOptionHexs(hexColor,optionsHltColors)) { + if (this.hexIsInOptionHexs(hexColor,optionsHltColorsVal)) { $li.html(hltText) hltColors.push(hexColor); liDisplay=true; @@ -189,7 +192,7 @@ export default class HighlightTextWidget extends RightPanelWidget { } if (bgColor != "") { var hexBgColor = this.colorToHex(bgColor); - if (this.hexIsInOptionHexs(hexBgColor,optionsHltBgColors)) { + if (this.hexIsInOptionHexs(hexBgColor,optionsHltBgColorsVal)) { //When you need to add a background color, in order to make the display more comfortable, change the background color to transparent $li.html(hltText.css("background-color", hexBgColor+"80")) hltBgColors.push(hexBgColor); diff --git a/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js b/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js index ec884959a..d5c284d94 100644 --- a/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js +++ b/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js @@ -3,7 +3,7 @@ import OptionsWidget from "../options_widget.js"; const TPL = `

    Highlighted Text

    - Displays highlighted text in the right panel. You can customize the highlighted text displayed in the right panel: + You can customize the highlighted text displayed in the right panel:
    Text color:
    - + @@ -28,7 +28,7 @@ const TPL = `
    Background color:
    - + From 0a66809bf0ca8aabe4af5426b1bc6eaa68098dd9 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Thu, 1 Jun 2023 20:38:55 +0800 Subject: [PATCH 26/40] Not show when nothing is checked in the options --- src/public/app/widgets/highlighted_text.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/public/app/widgets/highlighted_text.js b/src/public/app/widgets/highlighted_text.js index 1182c6fc2..b6ced3124 100644 --- a/src/public/app/widgets/highlighted_text.js +++ b/src/public/app/widgets/highlighted_text.js @@ -81,16 +81,16 @@ export default class HighlightTextWidget extends RightPanelWidget { } const hltLabel = note.getLabel('hideHighlightWidget'); - if (hltLabel?.value == "" || hltLabel?.value === "true") { + let optionsHltColors = JSON.parse(options.get('highlightedTextColors')); + let optionsHltBgColors = JSON.parse(options.get('highlightedTextBgColors')); + + if (hltLabel?.value == "" || hltLabel?.value === "true" || (optionsHltColors=="" && optionsHltBgColors=="")) { this.toggleInt(false); this.triggerCommand("reEvaluateRightPaneVisibility"); return; } let $hlt = "", hltLiCount = -1; - - let optionsHltColors = JSON.parse(options.get('highlightedTextColors')); - let optionsHltBgColors = JSON.parse(options.get('highlightedTextBgColors')); //Obtained by `textEditor.config.get('fontColor.colors'), but this command can only be used in edit mode, so it is directly saved here const colorToValDic = { "Black": "hsl(0,0%,0%)", "Dim grey": "hsl(0,0%,30%)", "Grey": "hsl(0,0%,60%)", "Light grey": "hsl(0,0%,90%)", "White": "hsl(0,0%,100%)", "Red": "hsl(0,75%,60%)", "Orange": "hsl(30,75%,60%)", "Yellow": "hsl(60,75%,60%)", "Light green": "hsl(90,75%,60%)", "Green": "hsl(120,75%,60%)", "Aquamarine": "hsl(150,75%,60%)", "Turquoise": "hsl(180,75%,60%)", "Light blue": "hsl(210,75%,60%)", "Blue": "hsl(240,75%,60%)", "Purple": "hsl(270,75%,60%)" } const optionsHltColorsVal = optionsHltColors.map(color => colorToValDic[color]); From 86a080e7ecbd332f95688de971921b903bda87a0 Mon Sep 17 00:00:00 2001 From: mm21 <8033134+mm21@users.noreply.github.com> Date: Thu, 1 Jun 2023 17:20:11 +0000 Subject: [PATCH 27/40] Add ETAPI test to repro inherited attribute issue for cloned note #3994 --- ...inherited-attribute-cloned-workaround.http | 142 ++++++++++++++++++ .../get-inherited-attribute-cloned.http | 116 ++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 test-etapi/get-inherited-attribute-cloned-workaround.http create mode 100644 test-etapi/get-inherited-attribute-cloned.http diff --git a/test-etapi/get-inherited-attribute-cloned-workaround.http b/test-etapi/get-inherited-attribute-cloned-workaround.http new file mode 100644 index 000000000..3bd88402c --- /dev/null +++ b/test-etapi/get-inherited-attribute-cloned-workaround.http @@ -0,0 +1,142 @@ +POST {{triliumHost}}/etapi/create-note +Authorization: {{authToken}} +Content-Type: application/json + +{ + "parentNoteId": "root", + "title": "Hello parent", + "type": "text", + "content": "Hi there!" +} + +> {% +client.assert(response.status === 201); +client.global.set("parentNoteId", response.body.note.noteId); +client.global.set("parentBranchId", response.body.branch.branchId); +%} + +### Create inheritable parent attribute + +POST {{triliumHost}}/etapi/attributes +Authorization: {{authToken}} +Content-Type: application/json + +{ + "noteId": "{{parentNoteId}}", + "type": "label", + "name": "mylabel", + "value": "", + "isInheritable": true, + "position": 10 +} + +> {% +client.assert(response.status === 201); +client.global.set("parentAttributeId", response.body.attributeId); +%} + +### Create child note under root + +POST {{triliumHost}}/etapi/create-note +Authorization: {{authToken}} +Content-Type: application/json + +{ + "parentNoteId": "root", + "title": "Hello child", + "type": "text", + "content": "Hi there!" +} + +> {% +client.assert(response.status === 201); +client.global.set("childNoteId", response.body.note.noteId); +client.global.set("childBranchId", response.body.branch.branchId); +%} + +### Create child attribute + +POST {{triliumHost}}/etapi/attributes +Authorization: {{authToken}} +Content-Type: application/json + +{ + "noteId": "{{childNoteId}}", + "type": "label", + "name": "mylabel", + "value": "val", + "isInheritable": false, + "position": 10 +} + +> {% +client.assert(response.status === 201); +client.global.set("childAttributeId", response.body.attributeId); +%} + +### Clone child to parent + +POST {{triliumHost}}/etapi/branches +Authorization: {{authToken}} +Content-Type: application/json + +{ + "noteId": "{{childNoteId}}", + "parentNoteId": "{{parentNoteId}}" +} + +> {% +client.assert(response.status === 201); +client.assert(response.body.parentNoteId == client.global.get("parentNoteId")); +%} + +### Workaround: create dummy attribute + +POST {{triliumHost}}/etapi/attributes +Authorization: {{authToken}} +Content-Type: application/json + +{ + "noteId": "{{childNoteId}}", + "type": "label", + "name": "childlabel", + "value": "val", + "isInheritable": false +} + +> {% +client.assert(response.status === 201); +client.global.set("dummyAttributeId", response.body.attributeId); +%} + +### Workaround: delete dummy attribute + +DELETE {{triliumHost}}/etapi/attributes/{{dummyAttributeId}} +Authorization: {{authToken}} + +> {% client.assert(response.status === 204); %} + +### + +GET {{triliumHost}}/etapi/notes/{{childNoteId}} +Authorization: {{authToken}} + +> {% + +function hasAttribute(list, attributeId) { + for (let i = 0; i < list.length; i++) { + if (list[i]["attributeId"] === attributeId) { + return true; + } + } + return false; +} + +client.assert(response.status === 200); +client.assert(response.body.noteId == client.global.get("childNoteId")); +client.assert(response.body.attributes.length == 2); +client.assert(hasAttribute(response.body.attributes, + client.global.get("parentAttributeId"))); +client.assert(hasAttribute(response.body.attributes, + client.global.get("childAttributeId"))); +%} diff --git a/test-etapi/get-inherited-attribute-cloned.http b/test-etapi/get-inherited-attribute-cloned.http new file mode 100644 index 000000000..06c1aa976 --- /dev/null +++ b/test-etapi/get-inherited-attribute-cloned.http @@ -0,0 +1,116 @@ +POST {{triliumHost}}/etapi/create-note +Authorization: {{authToken}} +Content-Type: application/json + +{ + "parentNoteId": "root", + "title": "Hello parent", + "type": "text", + "content": "Hi there!" +} + +> {% +client.assert(response.status === 201); +client.global.set("parentNoteId", response.body.note.noteId); +client.global.set("parentBranchId", response.body.branch.branchId); +%} + +### Create inheritable parent attribute + +POST {{triliumHost}}/etapi/attributes +Authorization: {{authToken}} +Content-Type: application/json + +{ + "noteId": "{{parentNoteId}}", + "type": "label", + "name": "mylabel", + "value": "", + "isInheritable": true, + "position": 10 +} + +> {% +client.assert(response.status === 201); +client.global.set("parentAttributeId", response.body.attributeId); +%} + +### Create child note under root + +POST {{triliumHost}}/etapi/create-note +Authorization: {{authToken}} +Content-Type: application/json + +{ + "parentNoteId": "root", + "title": "Hello child", + "type": "text", + "content": "Hi there!" +} + +> {% +client.assert(response.status === 201); +client.global.set("childNoteId", response.body.note.noteId); +client.global.set("childBranchId", response.body.branch.branchId); +%} + +### Create child attribute + +POST {{triliumHost}}/etapi/attributes +Authorization: {{authToken}} +Content-Type: application/json + +{ + "noteId": "{{childNoteId}}", + "type": "label", + "name": "mylabel", + "value": "val", + "isInheritable": false, + "position": 10 +} + +> {% +client.assert(response.status === 201); +client.global.set("childAttributeId", response.body.attributeId); +%} + +### Clone child to parent + +POST {{triliumHost}}/etapi/branches +Authorization: {{authToken}} +Content-Type: application/json + +{ + "noteId": "{{childNoteId}}", + "parentNoteId": "{{parentNoteId}}" +} + +> {% +client.assert(response.status === 201); +client.assert(response.body.parentNoteId == client.global.get("parentNoteId")); +%} + +### + +GET {{triliumHost}}/etapi/notes/{{childNoteId}} +Authorization: {{authToken}} + +> {% + +function hasAttribute(list, attributeId) { + for (let i = 0; i < list.length; i++) { + if (list[i]["attributeId"] === attributeId) { + return true; + } + } + return false; +} + +client.assert(response.status === 200); +client.assert(response.body.noteId == client.global.get("childNoteId")); +client.assert(response.body.attributes.length == 2); +client.assert(hasAttribute(response.body.attributes, + client.global.get("parentAttributeId"))); +client.assert(hasAttribute(response.body.attributes, + client.global.get("childAttributeId"))); +%} From d9359c7c558dd5cec293d4fe4a4294cc5ebbaee7 Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 1 Jun 2023 22:51:34 +0200 Subject: [PATCH 28/40] downgrade excalidraw to 0.14.2, #3979 --- package-lock.json | 15 ++++++++------- package.json | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 682c4f5a0..14cc4892b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,13 +5,14 @@ "requires": true, "packages": { "": { + "name": "trilium", "version": "0.60.1-beta", "hasInstallScript": true, "license": "AGPL-3.0-only", "dependencies": { "@braintree/sanitize-url": "6.0.2", "@electron/remote": "2.0.9", - "@excalidraw/excalidraw": "0.15.2", + "@excalidraw/excalidraw": "0.14.2", "archiver": "5.3.1", "async-mutex": "0.4.0", "axios": "1.4.0", @@ -461,9 +462,9 @@ } }, "node_modules/@excalidraw/excalidraw": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@excalidraw/excalidraw/-/excalidraw-0.15.2.tgz", - "integrity": "sha512-rTI02kgWSTXiUdIkBxt9u/581F3eXcqQgJdIxmz54TFtG3ughoxO5fr4t7Fr2LZIturBPqfocQHGKZ0t2KLKgw==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/@excalidraw/excalidraw/-/excalidraw-0.14.2.tgz", + "integrity": "sha512-8LdjpTBWEK5waDWB7Bt/G9YBI4j0OxkstUhvaDGz7dwQGfzF6FW5CXBoYHNEoX0qmb+Fg/NPOlZ7FrKsrSVCqg==", "peerDependencies": { "react": "^17.0.2 || ^18.2.0", "react-dom": "^17.0.2 || ^18.2.0" @@ -13590,9 +13591,9 @@ "dev": true }, "@excalidraw/excalidraw": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@excalidraw/excalidraw/-/excalidraw-0.15.2.tgz", - "integrity": "sha512-rTI02kgWSTXiUdIkBxt9u/581F3eXcqQgJdIxmz54TFtG3ughoxO5fr4t7Fr2LZIturBPqfocQHGKZ0t2KLKgw==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/@excalidraw/excalidraw/-/excalidraw-0.14.2.tgz", + "integrity": "sha512-8LdjpTBWEK5waDWB7Bt/G9YBI4j0OxkstUhvaDGz7dwQGfzF6FW5CXBoYHNEoX0qmb+Fg/NPOlZ7FrKsrSVCqg==", "requires": {} }, "@gar/promisify": { diff --git a/package.json b/package.json index a0a42ea5f..3353f12b9 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "dependencies": { "@braintree/sanitize-url": "6.0.2", "@electron/remote": "2.0.9", - "@excalidraw/excalidraw": "0.15.2", + "@excalidraw/excalidraw": "0.14.2", "archiver": "5.3.1", "async-mutex": "0.4.0", "axios": "1.4.0", From 0c4492bcd033505c03dcb332d639d978262096e3 Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 1 Jun 2023 23:14:17 +0200 Subject: [PATCH 29/40] invalidate attribute cache on branch create/update, fixes #3994 --- src/becca/becca_loader.js | 43 ++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/becca/becca_loader.js b/src/becca/becca_loader.js index 82dd09e42..dd8e990a4 100644 --- a/src/becca/becca_loader.js +++ b/src/becca/becca_loader.js @@ -69,25 +69,6 @@ function reload() { require('../services/ws').reloadFrontend(); } -/** - * This gets run on entity being created or updated. - * - * @param entityName - * @param entityRow - can be a becca entity (change comes from this trilium instance) or just a row (from sync). - * Should be therefore treated as a row. - */ -function postProcessEntityUpdate(entityName, entityRow) { - if (entityName === 'notes') { - noteUpdated(entityRow); - } else if (entityName === 'branches') { - branchUpdated(entityRow); - } else if (entityName === 'attributes') { - attributeUpdated(entityRow); - } else if (entityName === 'note_reordering') { - noteReorderingUpdated(entityRow); - } -} - eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entityName, entityRow}) => { if (!becca.loaded) { return; @@ -119,6 +100,25 @@ eventService.subscribeBeccaLoader(eventService.ENTITY_CHANGED, ({entityName, en postProcessEntityUpdate(entityName, entity); }); +/** + * This gets run on entity being created or updated. + * + * @param entityName + * @param entityRow - can be a becca entity (change comes from this trilium instance) or just a row (from sync). + * Should be therefore treated as a row. + */ +function postProcessEntityUpdate(entityName, entityRow) { + if (entityName === 'notes') { + noteUpdated(entityRow); + } else if (entityName === 'branches') { + branchUpdated(entityRow); + } else if (entityName === 'attributes') { + attributeUpdated(entityRow); + } else if (entityName === 'note_reordering') { + noteReorderingUpdated(entityRow); + } +} + eventService.subscribeBeccaLoader([eventService.ENTITY_DELETED, eventService.ENTITY_DELETE_SYNCED], ({entityName, entityId}) => { if (!becca.loaded) { return; @@ -156,6 +156,7 @@ function branchDeleted(branchId) { .filter(parentBranch => parentBranch.branchId !== branch.branchId); if (childNote.parents.length > 0) { + // subtree notes might lose some inherited attributes childNote.invalidateSubTree(); } } @@ -185,6 +186,10 @@ function branchUpdated(branchRow) { if (childNote) { childNote.flatTextCache = null; childNote.sortParents(); + + // notes in the subtree can get new inherited attributes + // this is in theory needed upon branch creation, but there's no create event for sync changes + childNote.invalidateSubTree(); } const parentNote = becca.notes[branchRow.parentNoteId]; From e47c3a27b0eb1966f8a791901948485301df1e57 Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 1 Jun 2023 23:18:50 +0200 Subject: [PATCH 30/40] remove the workaround test --- ...inherited-attribute-cloned-workaround.http | 142 ------------------ 1 file changed, 142 deletions(-) delete mode 100644 test-etapi/get-inherited-attribute-cloned-workaround.http diff --git a/test-etapi/get-inherited-attribute-cloned-workaround.http b/test-etapi/get-inherited-attribute-cloned-workaround.http deleted file mode 100644 index 3bd88402c..000000000 --- a/test-etapi/get-inherited-attribute-cloned-workaround.http +++ /dev/null @@ -1,142 +0,0 @@ -POST {{triliumHost}}/etapi/create-note -Authorization: {{authToken}} -Content-Type: application/json - -{ - "parentNoteId": "root", - "title": "Hello parent", - "type": "text", - "content": "Hi there!" -} - -> {% -client.assert(response.status === 201); -client.global.set("parentNoteId", response.body.note.noteId); -client.global.set("parentBranchId", response.body.branch.branchId); -%} - -### Create inheritable parent attribute - -POST {{triliumHost}}/etapi/attributes -Authorization: {{authToken}} -Content-Type: application/json - -{ - "noteId": "{{parentNoteId}}", - "type": "label", - "name": "mylabel", - "value": "", - "isInheritable": true, - "position": 10 -} - -> {% -client.assert(response.status === 201); -client.global.set("parentAttributeId", response.body.attributeId); -%} - -### Create child note under root - -POST {{triliumHost}}/etapi/create-note -Authorization: {{authToken}} -Content-Type: application/json - -{ - "parentNoteId": "root", - "title": "Hello child", - "type": "text", - "content": "Hi there!" -} - -> {% -client.assert(response.status === 201); -client.global.set("childNoteId", response.body.note.noteId); -client.global.set("childBranchId", response.body.branch.branchId); -%} - -### Create child attribute - -POST {{triliumHost}}/etapi/attributes -Authorization: {{authToken}} -Content-Type: application/json - -{ - "noteId": "{{childNoteId}}", - "type": "label", - "name": "mylabel", - "value": "val", - "isInheritable": false, - "position": 10 -} - -> {% -client.assert(response.status === 201); -client.global.set("childAttributeId", response.body.attributeId); -%} - -### Clone child to parent - -POST {{triliumHost}}/etapi/branches -Authorization: {{authToken}} -Content-Type: application/json - -{ - "noteId": "{{childNoteId}}", - "parentNoteId": "{{parentNoteId}}" -} - -> {% -client.assert(response.status === 201); -client.assert(response.body.parentNoteId == client.global.get("parentNoteId")); -%} - -### Workaround: create dummy attribute - -POST {{triliumHost}}/etapi/attributes -Authorization: {{authToken}} -Content-Type: application/json - -{ - "noteId": "{{childNoteId}}", - "type": "label", - "name": "childlabel", - "value": "val", - "isInheritable": false -} - -> {% -client.assert(response.status === 201); -client.global.set("dummyAttributeId", response.body.attributeId); -%} - -### Workaround: delete dummy attribute - -DELETE {{triliumHost}}/etapi/attributes/{{dummyAttributeId}} -Authorization: {{authToken}} - -> {% client.assert(response.status === 204); %} - -### - -GET {{triliumHost}}/etapi/notes/{{childNoteId}} -Authorization: {{authToken}} - -> {% - -function hasAttribute(list, attributeId) { - for (let i = 0; i < list.length; i++) { - if (list[i]["attributeId"] === attributeId) { - return true; - } - } - return false; -} - -client.assert(response.status === 200); -client.assert(response.body.noteId == client.global.get("childNoteId")); -client.assert(response.body.attributes.length == 2); -client.assert(hasAttribute(response.body.attributes, - client.global.get("parentAttributeId"))); -client.assert(hasAttribute(response.body.attributes, - client.global.get("childAttributeId"))); -%} From 7e71029d1ccde2a16a3ab9ed073c2e9872f73999 Mon Sep 17 00:00:00 2001 From: zadam Date: Fri, 2 Jun 2023 16:05:02 +0200 Subject: [PATCH 31/40] use note size format also in file properties --- src/public/app/services/frontend_script_api.js | 7 +++++++ src/public/app/services/utils.js | 14 +++++++++++++- .../widgets/ribbon_widgets/file_properties.js | 2 +- .../widgets/ribbon_widgets/note_info_widget.js | 18 ++++-------------- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/public/app/services/frontend_script_api.js b/src/public/app/services/frontend_script_api.js index 98d4e8460..a7b2b1467 100644 --- a/src/public/app/services/frontend_script_api.js +++ b/src/public/app/services/frontend_script_api.js @@ -483,6 +483,13 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain */ this.randomString = utils.randomString; + /** + * @method + * @param {int} size in bytes + * @return {string} formatted string + */ + this.formatNoteSize = utils.formatNoteSize; + this.logMessages = {}; this.logSpacedUpdates = {}; diff --git a/src/public/app/services/utils.js b/src/public/app/services/utils.js index e0ef2d767..bf468c578 100644 --- a/src/public/app/services/utils.js +++ b/src/public/app/services/utils.js @@ -354,6 +354,17 @@ function escapeRegExp(str) { return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); } +function formatNoteSize(size) { + size = Math.max(Math.round(size / 1024), 1); + + if (size < 1024) { + return `${size} KiB`; + } + else { + return `${Math.round(size / 102.4) / 10} MiB`; + } +} + export default { reloadFrontendApp, parseDate, @@ -396,5 +407,6 @@ export default { filterAttributeName, isValidAttributeName, sleep, - escapeRegExp + escapeRegExp, + formatNoteSize }; diff --git a/src/public/app/widgets/ribbon_widgets/file_properties.js b/src/public/app/widgets/ribbon_widgets/file_properties.js index 454c73f1d..afa051168 100644 --- a/src/public/app/widgets/ribbon_widgets/file_properties.js +++ b/src/public/app/widgets/ribbon_widgets/file_properties.js @@ -136,7 +136,7 @@ export default class FilePropertiesWidget extends NoteContextAwareWidget { const noteComplement = await this.noteContext.getNoteComplement(); - this.$fileSize.text(`${noteComplement.contentLength} bytes`); + this.$fileSize.text(utils.formatNoteSize(noteComplement.contentLength)); // open doesn't work for protected notes since it works through browser which isn't in protected session this.$openButton.toggle(!note.isProtected); diff --git a/src/public/app/widgets/ribbon_widgets/note_info_widget.js b/src/public/app/widgets/ribbon_widgets/note_info_widget.js index 28459da5d..3b7756cf7 100644 --- a/src/public/app/widgets/ribbon_widgets/note_info_widget.js +++ b/src/public/app/widgets/ribbon_widgets/note_info_widget.js @@ -1,5 +1,6 @@ import NoteContextAwareWidget from "../note_context_aware_widget.js"; import server from "../../services/server.js"; +import utils from "../../services/utils.js"; const TPL = `
    @@ -105,12 +106,12 @@ export default class NoteInfoWidget extends NoteContextAwareWidget { this.$subTreeSize.empty().append($('')); const noteSizeResp = await server.get(`stats/note-size/${this.noteId}`); - this.$noteSize.text(this.formatSize(noteSizeResp.noteSize)); + this.$noteSize.text(utils.formatNoteSize(noteSizeResp.noteSize)); const subTreeResp = await server.get(`stats/subtree-size/${this.noteId}`); if (subTreeResp.subTreeNoteCount > 1) { - this.$subTreeSize.text(`(subtree size: ${this.formatSize(subTreeResp.subTreeSize)} in ${subTreeResp.subTreeNoteCount} notes)`); + this.$subTreeSize.text(`(subtree size: ${utils.formatNoteSize(subTreeResp.subTreeSize)} in ${subTreeResp.subTreeNoteCount} notes)`); } else { this.$subTreeSize.text(""); @@ -142,18 +143,7 @@ export default class NoteInfoWidget extends NoteContextAwareWidget { this.$calculateButton.show(); this.$noteSizesWrapper.hide(); } - - formatSize(size) { - size = Math.max(Math.round(size / 1024), 1); - - if (size < 1024) { - return `${size} KiB`; - } - else { - return `${Math.round(size / 102.4) / 10} MiB`; - } - } - + entitiesReloadedEvent({loadResults}) { if (loadResults.isNoteReloaded(this.noteId) || loadResults.isNoteContentReloaded(this.noteId)) { this.refresh(); From c4f69fd9cbcc9dff08aa7cb2b38a1fc151d30e34 Mon Sep 17 00:00:00 2001 From: zadam Date: Sat, 3 Jun 2023 00:21:46 +0200 Subject: [PATCH 32/40] don't allow patching relation's value in ETAPI #3998 --- src/etapi/attributes.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/etapi/attributes.js b/src/etapi/attributes.js index 6886e0845..fb8b2ad99 100644 --- a/src/etapi/attributes.js +++ b/src/etapi/attributes.js @@ -40,19 +40,25 @@ function register(router) { } }); - const ALLOWED_PROPERTIES_FOR_PATCH = { + const ALLOWED_PROPERTIES_FOR_PATCH_LABEL = { 'value': [v.notNull, v.isString], 'position': [v.notNull, v.isInteger] }; + const ALLOWED_PROPERTIES_FOR_PATCH_RELATION = { + 'position': [v.notNull, v.isInteger] + }; + eu.route(router, 'patch' ,'/etapi/attributes/:attributeId', (req, res, next) => { const attribute = eu.getAndCheckAttribute(req.params.attributeId); - if (attribute.type === 'relation') { + if (attribute.type === 'label') { + eu.validateAndPatch(attribute, req.body, ALLOWED_PROPERTIES_FOR_PATCH_LABEL); + } else if (attribute.type === 'relation') { eu.getAndCheckNote(req.body.value); - } - eu.validateAndPatch(attribute, req.body, ALLOWED_PROPERTIES_FOR_PATCH); + eu.validateAndPatch(attribute, req.body, ALLOWED_PROPERTIES_FOR_PATCH_RELATION); + } attribute.save(); From d3bf325f19f9a45bf4a0559fdc11f6b8959b1d0c Mon Sep 17 00:00:00 2001 From: zadam Date: Sat, 3 Jun 2023 00:32:16 +0200 Subject: [PATCH 33/40] added some comments to openapi spec --- src/etapi/etapi.openapi.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/etapi/etapi.openapi.yaml b/src/etapi/etapi.openapi.yaml index 974acbdfe..7c41693d1 100644 --- a/src/etapi/etapi.openapi.yaml +++ b/src/etapi/etapi.openapi.yaml @@ -374,7 +374,7 @@ paths: schema: $ref: '#/components/schemas/Error' patch: - description: patch a branch identified by the branchId with changes in the body + description: patch a branch identified by the branchId with changes in the body. Only prefix and notePosition can be updated. If you want to update other properties, you need to delete the old branch and create a new one. operationId: patchBranchById requestBody: required: true @@ -456,7 +456,7 @@ paths: schema: $ref: '#/components/schemas/Error' patch: - description: patch a attribute identified by the attributeId with changes in the body + description: patch a attribute identified by the attributeId with changes in the body. For labels, only value and position can be updated. For relations, only position can be updated. If you want to modify other properties, you need to delete the old attribute and create a new one. operationId: patchAttributeById requestBody: required: true From 1413756d0095d6b8384f297d78e1b35a9590de44 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Sat, 3 Jun 2023 11:55:50 +0800 Subject: [PATCH 34/40] Show highlighted text in right panel --- src/public/app/widgets/highlighted_text.js | 168 ++++++++++-------- .../options/text_notes/highlighted_text.js | 83 ++------- src/routes/api/options.js | 3 +- src/services/options_init.js | 3 +- 4 files changed, 113 insertions(+), 144 deletions(-) diff --git a/src/public/app/widgets/highlighted_text.js b/src/public/app/widgets/highlighted_text.js index b6ced3124..f13bdc6b8 100644 --- a/src/public/app/widgets/highlighted_text.js +++ b/src/public/app/widgets/highlighted_text.js @@ -81,25 +81,20 @@ export default class HighlightTextWidget extends RightPanelWidget { } const hltLabel = note.getLabel('hideHighlightWidget'); - let optionsHltColors = JSON.parse(options.get('highlightedTextColors')); - let optionsHltBgColors = JSON.parse(options.get('highlightedTextBgColors')); + const optionsHlt = JSON.parse(options.get('highlightedText')); - if (hltLabel?.value == "" || hltLabel?.value === "true" || (optionsHltColors=="" && optionsHltBgColors=="")) { + if (hltLabel?.value == "" || hltLabel?.value === "true" || optionsHlt == "") { this.toggleInt(false); this.triggerCommand("reEvaluateRightPaneVisibility"); return; } let $hlt = "", hltLiCount = -1; - //Obtained by `textEditor.config.get('fontColor.colors'), but this command can only be used in edit mode, so it is directly saved here - const colorToValDic = { "Black": "hsl(0,0%,0%)", "Dim grey": "hsl(0,0%,30%)", "Grey": "hsl(0,0%,60%)", "Light grey": "hsl(0,0%,90%)", "White": "hsl(0,0%,100%)", "Red": "hsl(0,75%,60%)", "Orange": "hsl(30,75%,60%)", "Yellow": "hsl(60,75%,60%)", "Light green": "hsl(90,75%,60%)", "Green": "hsl(120,75%,60%)", "Aquamarine": "hsl(150,75%,60%)", "Turquoise": "hsl(180,75%,60%)", "Light blue": "hsl(210,75%,60%)", "Blue": "hsl(240,75%,60%)", "Purple": "hsl(270,75%,60%)" } - const optionsHltColorsVal = optionsHltColors.map(color => colorToValDic[color]); - const optionsHltBgColorsVal = optionsHltBgColors.map(color => colorToValDic[color]); // Check for type text unconditionally in case alwaysShowWidget is set if (this.note.type === 'text') { const { content } = await note.getNoteComplement(); //hltColors/hltBgColors are the colors/background-color that appear in notes and in options - ({ $hlt, hltLiCount } = await this.getHlt(content, optionsHltColorsVal, optionsHltBgColorsVal)); + ({ $hlt, hltLiCount } = await this.getHlt(content, optionsHlt)); } this.$hlt.html($hlt); if ([undefined, "false"].includes(hltLabel?.value) && hltLiCount > 0) { @@ -110,90 +105,115 @@ export default class HighlightTextWidget extends RightPanelWidget { this.noteContext.viewScope.highlightedTextTemporarilyHiddenPrevious = false; } - this.triggerCommand("reEvaluateRightPaneVisibility"); } /** * Builds a jquery table of helight text. */ - getHlt(html, optionsHltColorsVal, optionsHltBgColorsVal) { - const hltTagsRegex = /]*(?:background-color|color):[^;>]+;[^>]*>(.*?)<\/span>/gi; - let prevEndIndex = -1; - let prevLiDisplay = false; - const $hlt = $("
      "); - let hltLiCount = 0; - for (let match = null, hltIndex = 0; ((match = hltTagsRegex.exec(html)) !== null); hltIndex++) { - var spanHtml = match[0]; - const styleString = match[0].match(/style="(.*?)"/)[1]; - const text = match[1]; - const startIndex = match.index; - const endIndex = hltTagsRegex.lastIndex - 1; - var $li = $('
    1. '); - - const styles = styleString - .split(';') - .filter(item => item.includes('background-color') || item.includes('color')) - .map(item => item.trim()); - - for (let stylesIndex = 0; stylesIndex < styles.length; stylesIndex++) { - var [color, colorVal] = styles[stylesIndex].split(':'); - colorVal = colorVal.replace(/\s+/g, ''); - if (color == "color" && optionsHltColorsVal.indexOf(colorVal) >= 0) { - $li.html(spanHtml) - hltLiCount++; - - } - else if (color == "background-color" && optionsHltBgColorsVal.indexOf(colorVal) >= 0) { - - //When you need to add a background color, in order to make the display more comfortable, change the background color to Translucent - const spanHtmlRegex = /background-color:\s*(hsl|rgb)\((\d{1,3}),(\d{1,3}%?),(\d{1,3}%?)\)/i; - let spanHtmlMatch = spanHtml.match(spanHtmlRegex); - if (spanHtmlMatch && spanHtmlMatch.length > 4) { - let newColorValue = `${spanHtmlMatch[1]}a(${spanHtmlMatch[2]},${spanHtmlMatch[3]},${spanHtmlMatch[4]},0.5)`; - spanHtml = spanHtml.replace(spanHtmlRegex, `background-color: ${newColorValue}`); - } - $li.html(spanHtml) - hltLiCount++; - - } else { - $li.css("display", "none"); - } - } - if ($li.css("display")!="none"){ - if (prevEndIndex != -1 && startIndex === prevEndIndex + 1 && prevLiDisplay == true) { - $hlt.children().last().append($li.html()); - } else { - if ($li.text().trim() == "") { $li.css("display", "none"); } - $li.on("click", () => this.jumpToHlt(hltIndex)); - $hlt.append($li); - } - } - - prevEndIndex = endIndex; - prevLiDisplay = $li.css("display")!="none"; + getHlt(html, optionsHlt) { + // element priority: span>i>strong>u + // matches a span containing background-color + const regex1 = /]*style\s*=\s*[^>]*background-color:[^>]*?>[\s\S]*?<\/span>/gi; + // matches a span containing color + const regex2 = /]*style\s*=\s*[^>]*[^-]color:[^>]*?>[\s\S]*?<\/span>/gi; + // match italics + const regex3 = /[\s\S]*?<\/i>/gi; + // match bold + const regex4 = /[\s\S]*?<\/strong>/gi; + // match underline + const regex5 = /[\s\S]*?<\/u>/g; + // Possible values in optionsHlt: '["bold","italic","underline","color","bgColor"]' + let findSubStr="", combinedRegexStr = ""; + if (optionsHlt.indexOf("bgColor") >= 0){ + findSubStr+=`,span[style*="background-color"]`; + combinedRegexStr+=`|${regex1.source}`; } + if (optionsHlt.indexOf("color") >= 0){ + findSubStr+=`,span[style*="color"]`; + combinedRegexStr+=`|${regex2.source}`; + } + if (optionsHlt.indexOf("italic") >= 0){ + findSubStr+=`,i`; + combinedRegexStr+=`|${regex3.source}`; + } + if (optionsHlt.indexOf("bold") >= 0){ + findSubStr+=`,strong`; + combinedRegexStr+=`|${regex4.source}`; + } + if (optionsHlt.indexOf("underline") >= 0){ + findSubStr+=`,u`; + combinedRegexStr+=`|${regex5.source}`; + } + + findSubStr = findSubStr.substring(1) + combinedRegexStr = `(` + combinedRegexStr.substring(1) + `)`; + const combinedRegex = new RegExp(combinedRegexStr, 'gi'); + var $hlt = $("
        "); + var hltLiCount = 0; + let prevEndIndex = -1; + for (let match = null, hltIndex = 0; ((match = combinedRegex.exec(html)) !== null); hltIndex++) { + var subHtml = match[0]; + const startIndex = match.index; + const endIndex = combinedRegex.lastIndex; + hltLiCount++; + if (prevEndIndex != -1 && startIndex === prevEndIndex) { + $hlt.children().last().append(subHtml); + } else { + var $li = $('
      1. '); + $li.html(subHtml); + if ($li.text().trim() == "") { $li.css("display", "none"); } + $li.on("click", () => this.jumpToHlt(findSubStr,hltIndex)); + $hlt.append($li); + } + prevEndIndex = endIndex; + } + return { $hlt, hltLiCount }; } - - async jumpToHlt(hltIndex) { + async jumpToHlt(findSubStr,hltIndex) { const isReadOnly = await this.noteContext.isReadOnly(); + let targetElement; if (isReadOnly) { const $container = await this.noteContext.getContentElement(); - const hltElement = $container.find(`span[style*="background-color"],span[style*="color"]`)[hltIndex]; - - if (hltElement != null) { - hltElement.scrollIntoView({ behavior: "smooth", block: "center" }); - } + targetElement=$container.find(findSubStr).filter(function() { + if (findSubStr.indexOf("color")>=0 && findSubStr.indexOf("background-color")<0){ + let color = this.style.color; + return $(this).prop('tagName')=="SPAN" && color==""?false:true; + }else{ + return true; + } + }).filter(function() { + return $(this).parent(findSubStr).length === 0 + && $(this).parent().parent(findSubStr).length === 0 + && $(this).parent().parent().parent(findSubStr).length === 0 + && $(this).parent().parent().parent().parent(findSubStr).length === 0; + }) } else { const textEditor = await this.noteContext.getTextEditor(); - $(textEditor.editing.view.domRoots.values().next().value).find(`span[style*="background-color"],span[style*="color"]`)[hltIndex].scrollIntoView({ - behavior: "smooth", block: "center" - }); + targetElement=$(textEditor.editing.view.domRoots.values().next().value).find(findSubStr).filter(function() { + // When finding span[style*="color"] but not looking for span[style*="background-color"], + // the background-color error will be regarded as color, so it needs to be filtered + if (findSubStr.indexOf("color")>=0 && findSubStr.indexOf("background-color")<0){ + let color = this.style.color; + return $(this).prop('tagName')=="SPAN" && color==""?false:true; + }else{ + return true; + } + }).filter(function() { + //Need to filter out the child elements of the element that has been found + return $(this).parent(findSubStr).length === 0 + && $(this).parent().parent(findSubStr).length === 0 + && $(this).parent().parent().parent(findSubStr).length === 0 + && $(this).parent().parent().parent().parent(findSubStr).length === 0; + }) } + targetElement[hltIndex].scrollIntoView({ + behavior: "smooth", block: "center" + }); } async closeHltCommand() { diff --git a/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js b/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js index 8af3503f8..90a47b616 100644 --- a/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js +++ b/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js @@ -1,83 +1,34 @@ import OptionsWidget from "../options_widget.js"; const TPL = ` -
        - +

        Highlighted Text

        - - You can customize the highlighted text displayed in the right panel: -
        Text color:
        - - - - - - - - - - - - - - - -
        Background color:
        - - - - - - - - - - - - - - - + + You can customize the highlighted text displayed in the right panel:
        + + + + + +
        `; export default class HighlightedTextOptions extends OptionsWidget { doRender() { this.$widget = $(TPL); - this.$hltColors = this.$widget.find(".highlighted-text-color"); - this.$hltColors.on('change', () => { - const hltColorVals=this.$widget.find('input.highlighted-text-color[type="checkbox"]:checked').map(function() { + this.$hlt = this.$widget.find(".highlighted-text-check"); + this.$hlt.on('change', () => { + const hltVals=this.$widget.find('input.highlighted-text-check[type="checkbox"]:checked').map(function() { return this.value; }).get(); - this.updateOption('highlightedTextColors', JSON.stringify(hltColorVals)); - - }); - this.$hltBgColors = this.$widget.find(".highlighted-text-background-color"); - this.$hltBgColors.on('change', () =>{ - const hltBgColorVals=this.$widget.find('input.highlighted-text-background-color[type="checkbox"]:checked').map(function() { - return this.value; - }).get(); - this.updateOption('highlightedTextBgColors', JSON.stringify(hltBgColorVals)); - }); - + this.updateOption('highlightedText', JSON.stringify(hltVals)); + }); } async optionsLoaded(options) { - const hltColorVals=JSON.parse(options.highlightedTextColors); - const hltBgColorVals=JSON.parse(options.highlightedTextBgColors); - this.$widget.find('input.highlighted-text-color[type="checkbox"]').each(function () { - if ($.inArray($(this).val(), hltColorVals) !== -1) { - $(this).prop("checked", true); - } else { - $(this).prop("checked", false); - } - }); - this.$widget.find('input.highlighted-text-background-color[type="checkbox"]').each(function () { - if ($.inArray($(this).val(), hltBgColorVals) !== -1) { + const hltVals=JSON.parse(options.highlightedText); + this.$widget.find('input.highlighted-text-check[type="checkbox"]').each(function () { + if ($.inArray($(this).val(), hltVals) !== -1) { $(this).prop("checked", true); } else { $(this).prop("checked", false); diff --git a/src/routes/api/options.js b/src/routes/api/options.js index 48f2590b9..4d9ee77a2 100644 --- a/src/routes/api/options.js +++ b/src/routes/api/options.js @@ -60,8 +60,7 @@ const ALLOWED_OPTIONS = new Set([ 'compressImages', 'downloadImagesAutomatically', 'minTocHeadings', - 'highlightedTextColors', - 'highlightedTextBgColors', + 'highlightedText', 'checkForUpdates', 'disableTray', 'customSearchEngineName', diff --git a/src/services/options_init.js b/src/services/options_init.js index 53ca56932..358403214 100644 --- a/src/services/options_init.js +++ b/src/services/options_init.js @@ -87,8 +87,7 @@ const defaultOptions = [ { name: 'compressImages', value: 'true', isSynced: true }, { name: 'downloadImagesAutomatically', value: 'true', isSynced: true }, { name: 'minTocHeadings', value: '5', isSynced: true }, - { name: 'highlightedTextColors', value: '["Red","Orange","Yellow","Light green","Green","Aquamarine","Turquoise","Light blue","Blue","Purple"]', isSynced: true }, - { name: 'highlightedTextBgColors', value: '["Red","Orange","Yellow","Light green","Green","Aquamarine","Turquoise","Light blue","Blue","Purple"]', isSynced: true }, + { name: 'highlightedText', value: '["bold","italic","underline","color","bgColor"]', isSynced: true }, { name: 'checkForUpdates', value: 'true', isSynced: true }, { name: 'disableTray', value: 'false', isSynced: false }, { name: 'customSearchEngineName', value: 'Duckduckgo', isSynced: false }, From 33fdad51593dd616f28521f7fb293ad41ebb3c15 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Sat, 3 Jun 2023 14:43:20 +0800 Subject: [PATCH 35/40] Show highlighted text in right panel --- src/public/app/widgets/highlighted_text.js | 25 ++++++++++++---------- src/public/app/widgets/toc.js | 4 ++++ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/public/app/widgets/highlighted_text.js b/src/public/app/widgets/highlighted_text.js index f13bdc6b8..f39e7c696 100644 --- a/src/public/app/widgets/highlighted_text.js +++ b/src/public/app/widgets/highlighted_text.js @@ -28,6 +28,8 @@ const TPL = `
        margin-bottom: 3px; text-align: justify; text-justify: distribute; + word-wrap: break-word; + hyphens: auto; } .highlighted-text li:hover { @@ -149,26 +151,27 @@ export default class HighlightTextWidget extends RightPanelWidget { findSubStr = findSubStr.substring(1) combinedRegexStr = `(` + combinedRegexStr.substring(1) + `)`; const combinedRegex = new RegExp(combinedRegexStr, 'gi'); - var $hlt = $("
          "); - var hltLiCount = 0; - let prevEndIndex = -1; - for (let match = null, hltIndex = 0; ((match = combinedRegex.exec(html)) !== null); hltIndex++) { + let $hlt = $("
            "); + let prevEndIndex = -1, hltLiCount = 0; + for (let match = null, hltIndex=0; ((match = combinedRegex.exec(html)) !== null); hltIndex++) { var subHtml = match[0]; const startIndex = match.index; const endIndex = combinedRegex.lastIndex; - hltLiCount++; if (prevEndIndex != -1 && startIndex === prevEndIndex) { $hlt.children().last().append(subHtml); } else { - var $li = $('
          1. '); - $li.html(subHtml); - if ($li.text().trim() == "") { $li.css("display", "none"); } - $li.on("click", () => this.jumpToHlt(findSubStr,hltIndex)); - $hlt.append($li); + if ([...subHtml.matchAll(/(?<=^|>)[^><]+?(?=<|$)/g)].map(matchTmp => matchTmp[0]).join('').trim() != ""){ + var $li = $('
          2. '); + $li.html(subHtml); + $li.on("click", () => this.jumpToHlt(findSubStr,hltIndex)); + $hlt.append($li); + hltLiCount++; + }else{ + continue + } } prevEndIndex = endIndex; } - return { $hlt, hltLiCount diff --git a/src/public/app/widgets/toc.js b/src/public/app/widgets/toc.js index f17c3e65b..43ecb8f2d 100644 --- a/src/public/app/widgets/toc.js +++ b/src/public/app/widgets/toc.js @@ -38,6 +38,10 @@ const TPL = `
            .toc li { cursor: pointer; + text-align: justify; + text-justify: distribute; + word-wrap: break-word; + hyphens: auto; } .toc li:hover { From 446c41d020f0df9647af491c5b24e3e20e87631e Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Sat, 3 Jun 2023 17:00:15 +0800 Subject: [PATCH 36/40] Show highlighted text in right panel --- src/public/app/layouts/desktop_layout.js | 4 ++-- src/public/app/widgets/highlighted_text.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/public/app/layouts/desktop_layout.js b/src/public/app/layouts/desktop_layout.js index 0125e4ed1..17d569c4c 100644 --- a/src/public/app/layouts/desktop_layout.js +++ b/src/public/app/layouts/desktop_layout.js @@ -44,7 +44,7 @@ import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js"; import SharedInfoWidget from "../widgets/shared_info.js"; import FindWidget from "../widgets/find.js"; import TocWidget from "../widgets/toc.js"; -import HighlightTextWidget from "../widgets/highlighted_text.js"; +import HighlightedTextWidget from "../widgets/highlighted_text.js"; import BulkActionsDialog from "../widgets/dialogs/bulk_actions.js"; import AboutDialog from "../widgets/dialogs/about.js"; import HelpDialog from "../widgets/dialogs/help.js"; @@ -182,7 +182,7 @@ export default class DesktopLayout { ) .child(new RightPaneContainer() .child(new TocWidget()) - .child(new HighlightTextWidget()) + .child(new HighlightedTextWidget()) .child(...this.customWidgets.get('right-pane')) ) ) diff --git a/src/public/app/widgets/highlighted_text.js b/src/public/app/widgets/highlighted_text.js index f39e7c696..2ddecce88 100644 --- a/src/public/app/widgets/highlighted_text.js +++ b/src/public/app/widgets/highlighted_text.js @@ -46,7 +46,7 @@ const TPL = `
            `; -export default class HighlightTextWidget extends RightPanelWidget { +export default class HighlightedTextWidget extends RightPanelWidget { constructor() { super(); @@ -242,7 +242,7 @@ class CloseHltButton extends OnClickButtonWidget { super(); this.icon("bx-x") - .title("Close HighlightTextWidget") + .title("Close HighlightedTextWidget") .titlePlacement("bottom") .onClick((widget, e) => { e.stopPropagation(); From 3e3d7aa4d7e6cebadf7ec7083c11715e50c89992 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Sat, 3 Jun 2023 17:12:06 +0800 Subject: [PATCH 37/40] Show highlighted text in right panel --- src/public/app/widgets/highlighted_text.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/public/app/widgets/highlighted_text.js b/src/public/app/widgets/highlighted_text.js index 2ddecce88..b22697994 100644 --- a/src/public/app/widgets/highlighted_text.js +++ b/src/public/app/widgets/highlighted_text.js @@ -95,7 +95,6 @@ export default class HighlightedTextWidget extends RightPanelWidget { // Check for type text unconditionally in case alwaysShowWidget is set if (this.note.type === 'text') { const { content } = await note.getNoteComplement(); - //hltColors/hltBgColors are the colors/background-color that appear in notes and in options ({ $hlt, hltLiCount } = await this.getHlt(content, optionsHlt)); } this.$hlt.html($hlt); From 282d135f0f1baac2f5be2b18ee073de3f20be50b Mon Sep 17 00:00:00 2001 From: dymani Date: Sat, 3 Jun 2023 22:03:54 +0800 Subject: [PATCH 38/40] Fix shortcuts not resetting to default --- src/public/app/widgets/type_widgets/options/shortcuts.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/public/app/widgets/type_widgets/options/shortcuts.js b/src/public/app/widgets/type_widgets/options/shortcuts.js index a2c89437b..b37975b08 100644 --- a/src/public/app/widgets/type_widgets/options/shortcuts.js +++ b/src/public/app/widgets/type_widgets/options/shortcuts.js @@ -116,11 +116,11 @@ export default class KeyboardShortcutsOptions extends OptionsWidget { return; } - $table.find('input.form-control').each(function() { - const defaultShortcuts = this.$widget.find(this).attr('data-default-keyboard-shortcuts'); + $table.find('input.form-control').each((_index, el) => { + const defaultShortcuts = this.$widget.find(el).attr('data-default-keyboard-shortcuts'); - if (this.$widget.find(this).val() !== defaultShortcuts) { - this.$widget.find(this) + if (this.$widget.find(el).val() !== defaultShortcuts) { + this.$widget.find(el) .val(defaultShortcuts) .trigger('change'); } From 8852e8e531062dcf92f30cb1ea8da57e3ac2502e Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Sun, 4 Jun 2023 16:02:30 +0800 Subject: [PATCH 39/40] Show highlighted text in right panel --- src/public/app/widgets/highlighted_text.js | 14 ++++++++------ src/public/app/widgets/toc.js | 8 ++++---- .../options/text_notes/highlighted_text.js | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/public/app/widgets/highlighted_text.js b/src/public/app/widgets/highlighted_text.js index b22697994..daced39d4 100644 --- a/src/public/app/widgets/highlighted_text.js +++ b/src/public/app/widgets/highlighted_text.js @@ -72,11 +72,11 @@ export default class HighlightedTextWidget extends RightPanelWidget { } async refreshWithNote(note) { - /*The reason for adding highlightedTextTemporarilyHiddenPrevious is to record whether the previous state of the highlightedText is hidden or displayed, + /*The reason for adding highlightedTextPreviousVisible is to record whether the previous state of the highlightedText is hidden or displayed, * and then let it be displayed/hidden at the initial time. * If there is no such value, when the right panel needs to display toc but not highlighttext, every time the note content is changed, * highlighttext Widget will appear and then close immediately, because getHlt function will consume time*/ - if (this.noteContext.viewScope.highlightedTextTemporarilyHiddenPrevious == true) { + if (this.noteContext.viewScope.highlightedTextPreviousVisible == true) { this.toggleInt(true); } else { this.toggleInt(false); @@ -100,20 +100,19 @@ export default class HighlightedTextWidget extends RightPanelWidget { this.$hlt.html($hlt); if ([undefined, "false"].includes(hltLabel?.value) && hltLiCount > 0) { this.toggleInt(true); - this.noteContext.viewScope.highlightedTextTemporarilyHiddenPrevious = true; + this.noteContext.viewScope.highlightedTextPreviousVisible = true; } else { this.toggleInt(false); - this.noteContext.viewScope.highlightedTextTemporarilyHiddenPrevious = false; + this.noteContext.viewScope.highlightedTextPreviousVisible = false; } this.triggerCommand("reEvaluateRightPaneVisibility"); } /** - * Builds a jquery table of helight text. + * Builds a table of helight text. */ getHlt(html, optionsHlt) { - // element priority: span>i>strong>u // matches a span containing background-color const regex1 = /]*style\s*=\s*[^>]*background-color:[^>]*?>[\s\S]*?<\/span>/gi; // matches a span containing color @@ -125,6 +124,7 @@ export default class HighlightedTextWidget extends RightPanelWidget { // match underline const regex5 = /[\s\S]*?<\/u>/g; // Possible values in optionsHlt: '["bold","italic","underline","color","bgColor"]' + // element priority: span>i>strong>u let findSubStr="", combinedRegexStr = ""; if (optionsHlt.indexOf("bgColor") >= 0){ findSubStr+=`,span[style*="background-color"]`; @@ -157,8 +157,10 @@ export default class HighlightedTextWidget extends RightPanelWidget { const startIndex = match.index; const endIndex = combinedRegex.lastIndex; if (prevEndIndex != -1 && startIndex === prevEndIndex) { + //If the previous element is connected to this element in HTML, then concatenate them into one. $hlt.children().last().append(subHtml); } else { + //hide li if its text content is empty if ([...subHtml.matchAll(/(?<=^|>)[^><]+?(?=<|$)/g)].map(matchTmp => matchTmp[0]).join('').trim() != ""){ var $li = $('
          3. '); $li.html(subHtml); diff --git a/src/public/app/widgets/toc.js b/src/public/app/widgets/toc.js index 43ecb8f2d..8d66b3294 100644 --- a/src/public/app/widgets/toc.js +++ b/src/public/app/widgets/toc.js @@ -84,11 +84,11 @@ export default class TocWidget extends RightPanelWidget { } async refreshWithNote(note) { - /*The reason for adding tocTemporarilyHiddenPrevious is to record whether the previous state of the toc is hidden or displayed, + /*The reason for adding tocPreviousVisible is to record whether the previous state of the toc is hidden or displayed, * and then let it be displayed/hidden at the initial time. If there is no such value, * when the right panel needs to display highlighttext but not toc, every time the note content is changed, * toc will appear and then close immediately, because getToc(html) function will consume time*/ - if (this.noteContext.viewScope.tocTemporarilyHiddenPrevious ==true){ + if (this.noteContext.viewScope.tocPreviousVisible ==true){ this.toggleInt(true); }else{ this.toggleInt(false); @@ -112,10 +112,10 @@ export default class TocWidget extends RightPanelWidget { this.$toc.html($toc); if (["", "show"].includes(tocLabel?.value) || headingCount >= options.getInt('minTocHeadings')){ this.toggleInt(true); - this.noteContext.viewScope.tocTemporarilyHiddenPrevious=true; + this.noteContext.viewScope.tocPreviousVisible=true; }else{ this.toggleInt(false); - this.noteContext.viewScope.tocTemporarilyHiddenPrevious=false; + this.noteContext.viewScope.tocPreviousVisible=false; } this.triggerCommand("reEvaluateRightPaneVisibility"); diff --git a/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js b/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js index 90a47b616..4f96999cc 100644 --- a/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js +++ b/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js @@ -16,7 +16,7 @@ const TPL = ` export default class HighlightedTextOptions extends OptionsWidget { doRender() { this.$widget = $(TPL); - this.$hlt = this.$widget.find(".highlighted-text-check"); + this.$hlt = this.$widget.find("input.highlighted-text-check"); this.$hlt.on('change', () => { const hltVals=this.$widget.find('input.highlighted-text-check[type="checkbox"]:checked').map(function() { return this.value; From c177aaa901a9388bbce5341b3e6fdb41195dcbe2 Mon Sep 17 00:00:00 2001 From: zadam Date: Sun, 4 Jun 2023 17:46:37 +0200 Subject: [PATCH 40/40] refactoring of highlight list --- src/public/app/layouts/desktop_layout.js | 4 +- ...highlighted_text.js => highlights_list.js} | 174 +++++++++--------- .../options/text_notes/highlighted_text.js | 26 +-- 3 files changed, 104 insertions(+), 100 deletions(-) rename src/public/app/widgets/{highlighted_text.js => highlights_list.js} (51%) diff --git a/src/public/app/layouts/desktop_layout.js b/src/public/app/layouts/desktop_layout.js index e5c440cdc..cd5bb1912 100644 --- a/src/public/app/layouts/desktop_layout.js +++ b/src/public/app/layouts/desktop_layout.js @@ -44,7 +44,7 @@ import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js"; import SharedInfoWidget from "../widgets/shared_info.js"; import FindWidget from "../widgets/find.js"; import TocWidget from "../widgets/toc.js"; -import HighlightedTextWidget from "../widgets/highlighted_text.js"; +import HighlightsListWidget from "../widgets/highlights_list.js"; import BulkActionsDialog from "../widgets/dialogs/bulk_actions.js"; import AboutDialog from "../widgets/dialogs/about.js"; import HelpDialog from "../widgets/dialogs/help.js"; @@ -185,7 +185,7 @@ export default class DesktopLayout { ) .child(new RightPaneContainer() .child(new TocWidget()) - .child(new HighlightedTextWidget()) + .child(new HighlightsListWidget()) .child(...this.customWidgets.get('right-pane')) ) ) diff --git a/src/public/app/widgets/highlighted_text.js b/src/public/app/widgets/highlights_list.js similarity index 51% rename from src/public/app/widgets/highlighted_text.js rename to src/public/app/widgets/highlights_list.js index daced39d4..4e88e9e45 100644 --- a/src/public/app/widgets/highlighted_text.js +++ b/src/public/app/widgets/highlights_list.js @@ -1,7 +1,7 @@ /** * Widget: Show highlighted text in the right pane * - * By design there's no support for nonsensical or malformed constructs: + * By design, there's no support for nonsensical or malformed constructs: * - For example, if there is a formula in the middle of the highlighted text, the two ends of the formula will be regarded as two entries */ @@ -10,20 +10,20 @@ import RightPanelWidget from "./right_panel_widget.js"; import options from "../services/options.js"; import OnClickButtonWidget from "./buttons/onclick_button.js"; -const TPL = `
            +const TPL = `
            - +
            `; -export default class HighlightedTextWidget extends RightPanelWidget { +export default class HighlightsListWidget extends RightPanelWidget { constructor() { super(); @@ -67,38 +67,38 @@ export default class HighlightedTextWidget extends RightPanelWidget { async doRenderBody() { this.$body.empty().append($(TPL)); - this.$hlt = this.$body.find('.highlighted-text'); - this.$body.find('.highlighted-text-widget').append(this.closeHltButton.render()); + this.$highlightsList = this.$body.find('.highlists-list'); + this.$body.find('.highlists-list-widget').append(this.closeHltButton.render()); } async refreshWithNote(note) { - /*The reason for adding highlightedTextPreviousVisible is to record whether the previous state of the highlightedText is hidden or displayed, - * and then let it be displayed/hidden at the initial time. - * If there is no such value, when the right panel needs to display toc but not highlighttext, every time the note content is changed, - * highlighttext Widget will appear and then close immediately, because getHlt function will consume time*/ - if (this.noteContext.viewScope.highlightedTextPreviousVisible == true) { + /* The reason for adding highlightedTextPreviousVisible is to record whether the previous state + of the highlightedText is hidden or displayed, and then let it be displayed/hidden at the initial time. + If there is no such value, when the right panel needs to display toc but not highlighttext, + every time the note content is changed, highlighttext Widget will appear and then close immediately, + because getHlt function will consume time */ + if (this.noteContext.viewScope.highlightedTextPreviousVisible) { this.toggleInt(true); } else { this.toggleInt(false); } - const hltLabel = note.getLabel('hideHighlightWidget'); const optionsHlt = JSON.parse(options.get('highlightedText')); - if (hltLabel?.value == "" || hltLabel?.value === "true" || optionsHlt == "") { + if (note.isLabelTruthy('hideHighlightWidget') || !optionsHlt) { this.toggleInt(false); this.triggerCommand("reEvaluateRightPaneVisibility"); return; } - let $hlt = "", hltLiCount = -1; + let $highlightsList = "", hltLiCount = -1; // Check for type text unconditionally in case alwaysShowWidget is set if (this.note.type === 'text') { - const { content } = await note.getNoteComplement(); - ({ $hlt, hltLiCount } = await this.getHlt(content, optionsHlt)); + const {content} = await note.getNoteComplement(); + ({$highlightsList, hltLiCount} = this.getHighlightList(content, optionsHlt)); } - this.$hlt.html($hlt); - if ([undefined, "false"].includes(hltLabel?.value) && hltLiCount > 0) { + this.$highlightsList.empty().append($highlightsList); + if (hltLiCount > 0) { this.toggleInt(true); this.noteContext.viewScope.highlightedTextPreviousVisible = true; } else { @@ -109,12 +109,9 @@ export default class HighlightedTextWidget extends RightPanelWidget { this.triggerCommand("reEvaluateRightPaneVisibility"); } - /** - * Builds a table of helight text. - */ - getHlt(html, optionsHlt) { + getHighlightList(content, optionsHlt) { // matches a span containing background-color - const regex1 = /]*style\s*=\s*[^>]*background-color:[^>]*?>[\s\S]*?<\/span>/gi; + const regex1 = /]*style\s*=\s*[^>]*background-color:[^>]*?>[\s\S]*?<\/span>/gi; // matches a span containing color const regex2 = /]*style\s*=\s*[^>]*[^-]color:[^>]*?>[\s\S]*?<\/span>/gi; // match italics @@ -125,97 +122,103 @@ export default class HighlightedTextWidget extends RightPanelWidget { const regex5 = /[\s\S]*?<\/u>/g; // Possible values in optionsHlt: '["bold","italic","underline","color","bgColor"]' // element priority: span>i>strong>u - let findSubStr="", combinedRegexStr = ""; - if (optionsHlt.indexOf("bgColor") >= 0){ - findSubStr+=`,span[style*="background-color"]`; - combinedRegexStr+=`|${regex1.source}`; + let findSubStr = "", combinedRegexStr = ""; + if (optionsHlt.includes("bgColor")) { + findSubStr += `,span[style*="background-color"]`; + combinedRegexStr += `|${regex1.source}`; } - if (optionsHlt.indexOf("color") >= 0){ - findSubStr+=`,span[style*="color"]`; - combinedRegexStr+=`|${regex2.source}`; + if (optionsHlt.includes("color")) { + findSubStr += `,span[style*="color"]`; + combinedRegexStr += `|${regex2.source}`; } - if (optionsHlt.indexOf("italic") >= 0){ - findSubStr+=`,i`; - combinedRegexStr+=`|${regex3.source}`; + if (optionsHlt.includes("italic")) { + findSubStr += `,i`; + combinedRegexStr += `|${regex3.source}`; } - if (optionsHlt.indexOf("bold") >= 0){ - findSubStr+=`,strong`; - combinedRegexStr+=`|${regex4.source}`; + if (optionsHlt.indexOf("bold")) { + findSubStr += `,strong`; + combinedRegexStr += `|${regex4.source}`; } - if (optionsHlt.indexOf("underline") >= 0){ - findSubStr+=`,u`; - combinedRegexStr+=`|${regex5.source}`; + if (optionsHlt.includes("underline")) { + findSubStr += `,u`; + combinedRegexStr += `|${regex5.source}`; } findSubStr = findSubStr.substring(1) combinedRegexStr = `(` + combinedRegexStr.substring(1) + `)`; const combinedRegex = new RegExp(combinedRegexStr, 'gi'); - let $hlt = $("
              "); + const $highlightsList = $("
                "); let prevEndIndex = -1, hltLiCount = 0; - for (let match = null, hltIndex=0; ((match = combinedRegex.exec(html)) !== null); hltIndex++) { - var subHtml = match[0]; + for (let match = null, hltIndex = 0; ((match = combinedRegex.exec(content)) !== null); hltIndex++) { + const subHtml = match[0]; const startIndex = match.index; const endIndex = combinedRegex.lastIndex; - if (prevEndIndex != -1 && startIndex === prevEndIndex) { - //If the previous element is connected to this element in HTML, then concatenate them into one. - $hlt.children().last().append(subHtml); + if (prevEndIndex !== -1 && startIndex === prevEndIndex) { + // If the previous element is connected to this element in HTML, then concatenate them into one. + $highlightsList.children().last().append(subHtml); } else { - //hide li if its text content is empty - if ([...subHtml.matchAll(/(?<=^|>)[^><]+?(?=<|$)/g)].map(matchTmp => matchTmp[0]).join('').trim() != ""){ - var $li = $('
              1. '); - $li.html(subHtml); - $li.on("click", () => this.jumpToHlt(findSubStr,hltIndex)); - $hlt.append($li); + // TODO: can't be done with $(subHtml).text()? + const hasText = [...subHtml.matchAll(/(?<=^|>)[^><]+?(?=<|$)/g)].map(matchTmp => matchTmp[0]).join('').trim(); + + if (hasText) { + $highlightsList.append( + $('
              2. ') + .html(subHtml) + .on("click", () => this.jumpToHighlightedText(findSubStr, hltIndex)) + ); + hltLiCount++; - }else{ - continue + } else { + // hide li if its text content is empty + continue; } } prevEndIndex = endIndex; } return { - $hlt, + $highlightsList, hltLiCount }; } - async jumpToHlt(findSubStr,hltIndex) { + + async jumpToHighlightedText(findSubStr, itemIndex) { const isReadOnly = await this.noteContext.isReadOnly(); let targetElement; if (isReadOnly) { const $container = await this.noteContext.getContentElement(); - targetElement=$container.find(findSubStr).filter(function() { - if (findSubStr.indexOf("color")>=0 && findSubStr.indexOf("background-color")<0){ + targetElement = $container.find(findSubStr).filter(function () { + if (findSubStr.indexOf("color") >= 0 && findSubStr.indexOf("background-color") < 0) { let color = this.style.color; - return $(this).prop('tagName')=="SPAN" && color==""?false:true; - }else{ + return !($(this).prop('tagName') === "SPAN" && color === ""); + } else { return true; - } - }).filter(function() { - return $(this).parent(findSubStr).length === 0 - && $(this).parent().parent(findSubStr).length === 0 - && $(this).parent().parent().parent(findSubStr).length === 0 - && $(this).parent().parent().parent().parent(findSubStr).length === 0; + } + }).filter(function () { + return $(this).parent(findSubStr).length === 0 + && $(this).parent().parent(findSubStr).length === 0 + && $(this).parent().parent().parent(findSubStr).length === 0 + && $(this).parent().parent().parent().parent(findSubStr).length === 0; }) } else { const textEditor = await this.noteContext.getTextEditor(); - targetElement=$(textEditor.editing.view.domRoots.values().next().value).find(findSubStr).filter(function() { - // When finding span[style*="color"] but not looking for span[style*="background-color"], + targetElement = $(textEditor.editing.view.domRoots.values().next().value).find(findSubStr).filter(function () { + // When finding span[style*="color"] but not looking for span[style*="background-color"], // the background-color error will be regarded as color, so it needs to be filtered - if (findSubStr.indexOf("color")>=0 && findSubStr.indexOf("background-color")<0){ + if (findSubStr.indexOf("color") >= 0 && findSubStr.indexOf("background-color") < 0) { let color = this.style.color; - return $(this).prop('tagName')=="SPAN" && color==""?false:true; - }else{ + return !($(this).prop('tagName') === "SPAN" && color === ""); + } else { return true; - } - }).filter(function() { - //Need to filter out the child elements of the element that has been found - return $(this).parent(findSubStr).length === 0 - && $(this).parent().parent(findSubStr).length === 0 - && $(this).parent().parent().parent(findSubStr).length === 0 - && $(this).parent().parent().parent().parent(findSubStr).length === 0; + } + }).filter(function () { + // Need to filter out the child elements of the element that has been found + return $(this).parent(findSubStr).length === 0 + && $(this).parent().parent(findSubStr).length === 0 + && $(this).parent().parent().parent(findSubStr).length === 0 + && $(this).parent().parent().parent().parent(findSubStr).length === 0; }) } - targetElement[hltIndex].scrollIntoView({ + targetElement[itemIndex].scrollIntoView({ behavior: "smooth", block: "center" }); } @@ -226,7 +229,7 @@ export default class HighlightedTextWidget extends RightPanelWidget { this.triggerCommand('reEvaluateRightPaneVisibility'); } - async entitiesReloadedEvent({ loadResults }) { + async entitiesReloadedEvent({loadResults}) { if (loadResults.isNoteContentReloaded(this.noteId)) { await this.refresh(); } else if (loadResults.getAttributes().find(attr => attr.type === 'label' @@ -237,7 +240,6 @@ export default class HighlightedTextWidget extends RightPanelWidget { } } - class CloseHltButton extends OnClickButtonWidget { constructor() { super(); @@ -250,6 +252,6 @@ class CloseHltButton extends OnClickButtonWidget { widget.triggerCommand("closeHlt"); }) - .class("icon-action close-highlighted-text"); + .class("icon-action close-highlists-list"); } } diff --git a/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js b/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js index 4f96999cc..72d0ccd49 100644 --- a/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js +++ b/src/public/app/widgets/type_widgets/options/text_notes/highlighted_text.js @@ -4,13 +4,15 @@ const TPL = `

                Highlighted Text

                - You can customize the highlighted text displayed in the right panel:
                +

                You can customize the highlighted text displayed in the right panel:

                - - - - - +
                + + + + + +
        `; export default class HighlightedTextOptions extends OptionsWidget { @@ -18,20 +20,20 @@ export default class HighlightedTextOptions extends OptionsWidget { this.$widget = $(TPL); this.$hlt = this.$widget.find("input.highlighted-text-check"); this.$hlt.on('change', () => { - const hltVals=this.$widget.find('input.highlighted-text-check[type="checkbox"]:checked').map(function() { + const hltVals = this.$widget.find('input.highlighted-text-check[type="checkbox"]:checked').map(function () { return this.value; - }).get(); + }).get(); this.updateOption('highlightedText', JSON.stringify(hltVals)); - }); + }); } async optionsLoaded(options) { - const hltVals=JSON.parse(options.highlightedText); + const hltVals = JSON.parse(options.highlightedText); this.$widget.find('input.highlighted-text-check[type="checkbox"]').each(function () { if ($.inArray($(this).val(), hltVals) !== -1) { - $(this).prop("checked", true); + $(this).prop("checked", true); } else { - $(this).prop("checked", false); + $(this).prop("checked", false); } }); }