diff --git a/src/public/app/layouts/desktop_main_window_layout.js b/src/public/app/layouts/desktop_main_window_layout.js
index 7b4299700..2f8e4bb59 100644
--- a/src/public/app/layouts/desktop_main_window_layout.js
+++ b/src/public/app/layouts/desktop_main_window_layout.js
@@ -20,7 +20,7 @@ import NoteInfoWidget from "../widgets/collapsible_widgets/note_info.js";
import CalendarWidget from "../widgets/collapsible_widgets/calendar.js";
import LinkMapWidget from "../widgets/collapsible_widgets/link_map.js";
import NoteRevisionsWidget from "../widgets/collapsible_widgets/note_revisions.js";
-import SimilarNotesWidget from "../widgets/collapsible_widgets/similar_notes.js";
+import SimilarNotesWidget from "../widgets/similar_notes.js";
import WhatLinksHereWidget from "../widgets/collapsible_widgets/what_links_here.js";
import SidePaneToggles from "../widgets/side_pane_toggles.js";
import EditedNotesWidget from "../widgets/collapsible_widgets/edited_notes.js";
@@ -138,6 +138,7 @@ export default class DesktopMainWindowLayout {
)
.child(new TabCachingWidget(() => new AttributeListWidget()))
.child(new TabCachingWidget(() => new NoteDetailWidget()))
+ .child(new TabCachingWidget(() => new SimilarNotesWidget()))
.child(...this.customWidgets.get('center-pane'))
)
.child(new SidePaneContainer('right')
@@ -148,7 +149,6 @@ export default class DesktopMainWindowLayout {
.child(new TabCachingWidget(() => new EditedNotesWidget()))
.child(new TabCachingWidget(() => new LinkMapWidget()))
.child(new TabCachingWidget(() => new NoteRevisionsWidget()))
- .child(new TabCachingWidget(() => new SimilarNotesWidget()))
.child(new TabCachingWidget(() => new WhatLinksHereWidget()))
.child(...this.customWidgets.get('right-pane'))
)
diff --git a/src/public/app/widgets/attribute_list.js b/src/public/app/widgets/attribute_list.js
index 6cffc2c05..70d30e168 100644
--- a/src/public/app/widgets/attribute_list.js
+++ b/src/public/app/widgets/attribute_list.js
@@ -56,7 +56,7 @@ const TPL = `
cursor: pointer;
}
- .attr-expander:not(.error):hover hr {
+ .attr-expander:hover hr {
border-color: var(--main-text-color);
}
diff --git a/src/public/app/widgets/collapsible_widgets/similar_notes.js b/src/public/app/widgets/collapsible_widgets/similar_notes.js
deleted file mode 100644
index 29561dfbb..000000000
--- a/src/public/app/widgets/collapsible_widgets/similar_notes.js
+++ /dev/null
@@ -1,96 +0,0 @@
-import CollapsibleWidget from "../collapsible_widget.js";
-import linkService from "../../services/link.js";
-import server from "../../services/server.js";
-import treeCache from "../../services/tree_cache.js";
-
-const TPL = `
-
');
-
- for (const similarNote of similarNotes) {
- const note = await treeCache.getNote(similarNote.noteId, true);
-
- if (!note) {
- continue;
- }
-
- const $item = (await linkService.createNoteLink(similarNote.notePath.join("/")))
- .css("font-size", 24 * similarNote.coeff);
-
- $list.append($item);
- }
-
- this.$similarNotesContent.empty().append($list);
- }
-
- entitiesReloadedEvent({loadResults}) {
- if (this.note && this.title !== this.note.title) {
- this.rendered = false;
-
- this.refresh();
- }
- }
-}
diff --git a/src/public/app/widgets/note_tree.js b/src/public/app/widgets/note_tree.js
index 9d9ab7e29..b2406daef 100644
--- a/src/public/app/widgets/note_tree.js
+++ b/src/public/app/widgets/note_tree.js
@@ -51,7 +51,7 @@ const TPL = `
.tree-settings-button {
position: absolute;
top: 10px;
- right: 20px;
+ right: 10px;
z-index: 100;
}
diff --git a/src/public/app/widgets/similar_notes.js b/src/public/app/widgets/similar_notes.js
new file mode 100644
index 000000000..06eb31fca
--- /dev/null
+++ b/src/public/app/widgets/similar_notes.js
@@ -0,0 +1,163 @@
+import linkService from "../services/link.js";
+import server from "../services/server.js";
+import treeCache from "../services/tree_cache.js";
+import TabAwareWidget from "./tab_aware_widget.js";
+import options from "../services/options.js";
+
+const TPL = `
+
+`;
+
+export default class SimilarNotesWidget extends TabAwareWidget {
+ doRender() {
+ this.$widget = $(TPL);
+ this.overflowing();
+
+ this.$similarNotesWrapper = this.$widget.find(".similar-notes-wrapper");
+ this.$similarNotesExpanderText = this.$widget.find(".similar-notes-expander-text");
+
+ this.$expander = this.$widget.find('.similar-notes-expander');
+ this.$expander.on('click', async () => {
+ const collapse = this.$similarNotesWrapper.is(":visible");
+
+ await options.save('similarNotesExpanded', !collapse);
+
+ this.triggerEvent(`similarNotesCollapsedStateChanged`, {collapse});
+ });
+
+ return this.$widget;
+ }
+
+ noteSwitched() {
+ const noteId = this.noteId;
+
+ this.toggleInt(false);
+ this.$similarNotesWrapper.hide(); // we'll open it in refresh() if needed
+
+ // avoid executing this expensive operation multiple times when just going through notes (with keyboard especially)
+ // until the users settles on a note
+ setTimeout(() => {
+ if (this.noteId === noteId) {
+ this.refresh();
+ }
+ }, 1000);
+ }
+
+ async refreshWithNote(note) {
+ // remember which title was when we found the similar notes
+ this.title = note.title;
+
+ const similarNotes = await server.get('similar-notes/' + this.noteId);
+
+ this.toggleInt(similarNotes.length > 0);
+
+ if (similarNotes.length === 0) {
+ return;
+ }
+
+ if (options.is('similarNotesExpanded')) {
+ this.$similarNotesWrapper.show();
+ }
+
+ this.$similarNotesExpanderText.text(`${similarNotes.length} similar note${similarNotes.length === 1 ? '': "s"}`);
+
+ const noteIds = similarNotes.flatMap(note => note.notePath);
+
+ await treeCache.getNotes(noteIds, true); // preload all at once
+
+ const $list = $('
');
+
+ for (const similarNote of similarNotes) {
+ const note = await treeCache.getNote(similarNote.noteId, true);
+
+ if (!note) {
+ continue;
+ }
+
+ const $item = (await linkService.createNoteLink(similarNote.notePath.join("/")))
+ .css("font-size", 24 * similarNote.coeff);
+
+ $list.append($item);
+ }
+
+ this.$similarNotesWrapper.empty().append($list);
+ }
+
+ entitiesReloadedEvent({loadResults}) {
+ if (this.note && this.title !== this.note.title) {
+ this.rendered = false;
+
+ this.refresh();
+ }
+ }
+
+ /**
+ * This event is used to synchronize collapsed state of all the tab-cached widgets since they are all rendered
+ * separately but should behave uniformly for the user.
+ */
+ similarNotesCollapsedStateChangedEvent({collapse}) {
+ if (collapse) {
+ this.$similarNotesWrapper.slideUp(200);
+ } else {
+ this.$similarNotesWrapper.slideDown(200);
+ }
+ }
+}
diff --git a/src/public/app/widgets/standard_top_widget.js b/src/public/app/widgets/standard_top_widget.js
index d6382c9bc..8d16f3a75 100644
--- a/src/public/app/widgets/standard_top_widget.js
+++ b/src/public/app/widgets/standard_top_widget.js
@@ -82,7 +82,7 @@ export default class StandardTopWidget extends BasicWidget {
this.$leaveProtectedSessionButton = this.$widget.find(".leave-protected-session-button");
this.$leaveProtectedSessionButton.on('click', protectedSessionService.leaveProtectedSession);
- return this.$widget
+ return this.$widget;
}
protectedSessionStartedEvent() {
diff --git a/src/routes/api/options.js b/src/routes/api/options.js
index 8bd5579aa..24eae0a91 100644
--- a/src/routes/api/options.js
+++ b/src/routes/api/options.js
@@ -39,7 +39,8 @@ const ALLOWED_OPTIONS = new Set([
'rightPaneVisible',
'nativeTitleBarVisible',
'attributeListExpanded',
- 'promotedAttributesExpanded'
+ 'promotedAttributesExpanded',
+ 'similarNotesExpanded'
]);
function getOptions() {
diff --git a/src/services/note_cache/note_cache_service.js b/src/services/note_cache/note_cache_service.js
index a586a8bc8..8b563827b 100644
--- a/src/services/note_cache/note_cache_service.js
+++ b/src/services/note_cache/note_cache_service.js
@@ -229,7 +229,7 @@ function findSimilarNotes(noteId) {
results.sort((a, b) => a.coeff > b.coeff ? -1 : 1);
- return results.length > 50 ? results.slice(0, 50) : results;
+ return results.length > 50 ? results.slice(0, 200) : results;
}
/**
diff --git a/src/services/options_init.js b/src/services/options_init.js
index d17637364..4fe655a18 100644
--- a/src/services/options_init.js
+++ b/src/services/options_init.js
@@ -86,7 +86,8 @@ const defaultOptions = [
{ name: 'hideArchivedNotes_main', value: 'false', isSynced: false },
{ name: 'hideIncludedImages_main', value: 'true', isSynced: false },
{ name: 'attributeListExpanded', value: 'false', isSynced: false },
- { name: 'promotedAttributesExpanded', value: 'false', isSynced: true }
+ { name: 'promotedAttributesExpanded', value: 'true', isSynced: true },
+ { name: 'similarNotesExpanded', value: 'true', isSynced: true }
];
function initStartupOptions() {
diff --git a/src/services/search/services/search.js b/src/services/search/services/search.js
index 23a51c82e..1a2263703 100644
--- a/src/services/search/services/search.js
+++ b/src/services/search/services/search.js
@@ -102,7 +102,7 @@ function searchNotesForAutocomplete(query) {
fuzzyAttributeSearch: true
});
- const results = searchTrimmedNotes(query, parsingContext);
+ const {results} = searchTrimmedNotes(query, parsingContext);
highlightSearchResults(results, parsingContext.highlightedTokens);