Skip to content
This repository was archived by the owner on Jun 24, 2025. It is now read-only.

Commit 8486bbc

Browse files
committed
Merge branch 'develop' into open_new_tab
2 parents 2251a55 + 699cb8e commit 8486bbc

File tree

23 files changed

+244
-323
lines changed

23 files changed

+244
-323
lines changed

apps/client/src/menus/context_menu.ts

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ interface ContextMenuOptions<T> {
1010
items: MenuItem<T>[];
1111
/** On mobile, if set to `true` then the context menu is shown near the element. If `false` (default), then the context menu is shown at the bottom of the screen. */
1212
forcePositionOnMobile?: boolean;
13+
onHide?: () => void;
1314
}
1415

1516
interface MenuSeparatorItem {
@@ -36,15 +37,13 @@ export type ContextMenuEvent = PointerEvent | MouseEvent | JQuery.ContextMenuEve
3637
class ContextMenu {
3738
private $widget: JQuery<HTMLElement>;
3839
private $cover: JQuery<HTMLElement>;
39-
private dateContextMenuOpenedMs: number;
4040
private options?: ContextMenuOptions<any>;
4141
private isMobile: boolean;
4242

4343
constructor() {
4444
this.$widget = $("#context-menu-container");
4545
this.$cover = $("#context-menu-cover");
4646
this.$widget.addClass("dropend");
47-
this.dateContextMenuOpenedMs = 0;
4847
this.isMobile = utils.isMobile();
4948

5049
if (this.isMobile) {
@@ -76,8 +75,6 @@ class ContextMenu {
7675
keyboardActionService.updateDisplayedShortcuts(this.$widget);
7776

7877
this.positionMenu();
79-
80-
this.dateContextMenuOpenedMs = Date.now();
8178
}
8279

8380
positionMenu() {
@@ -186,8 +183,6 @@ class ContextMenu {
186183
return false;
187184
}
188185

189-
this.hide();
190-
191186
if ("handler" in item && item.handler) {
192187
item.handler(item, e);
193188
}
@@ -197,6 +192,12 @@ class ContextMenu {
197192
// it's important to stop the propagation especially for sub-menus, otherwise the event
198193
// might be handled again by top-level menu
199194
return false;
195+
})
196+
.on("mouseup", (e) =>{
197+
e.stopPropagation();
198+
// Hide the content menu on mouse up to prevent the mouse event from propagating to the elements below.
199+
this.hide();
200+
return false;
200201
});
201202

202203
if ("enabled" in item && item.enabled !== undefined && !item.enabled) {
@@ -220,27 +221,14 @@ class ContextMenu {
220221
}
221222

222223
async hide() {
223-
// this date checking comes from change in FF66 - https://github.com/zadam/trilium/issues/468
224-
// "contextmenu" event also triggers "click" event which depending on the timing can close the just opened context menu
225-
// we might filter out right clicks, but then it's better if even right clicks close the context menu
226-
if (Date.now() - this.dateContextMenuOpenedMs > 300) {
227-
// seems like if we hide the menu immediately, some clicks can get propagated to the underlying component
228-
// see https://github.com/zadam/trilium/pull/3805 for details
229-
await timeout(100);
230-
this.$widget.removeClass("show");
231-
this.$cover.removeClass("show");
232-
$("body").removeClass("context-menu-shown");
233-
this.$widget.hide();
234-
}
224+
this.options?.onHide?.();
225+
this.$widget.removeClass("show");
226+
this.$cover.removeClass("show");
227+
$("body").removeClass("context-menu-shown");
228+
this.$widget.hide();
235229
}
236230
}
237231

238-
function timeout(ms: number) {
239-
return new Promise((accept, reject) => {
240-
setTimeout(accept, ms);
241-
});
242-
}
243-
244232
const contextMenu = new ContextMenu();
245233

246234
export default contextMenu;

apps/client/src/menus/tree_context_menu.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ interface ConvertToAttachmentResponse {
1919
attachment?: FAttachment;
2020
}
2121

22+
let lastTargetNode: HTMLElement | null = null;
23+
2224
// This will include all commands that implement ContextMenuCommandData, but it will not work if it additional options are added via the `|` operator,
2325
// so they need to be added manually.
2426
export type TreeCommandNames = FilteredCommandNames<ContextMenuCommandData> | "openBulkActionsDialog";
@@ -33,12 +35,19 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
3335
}
3436

3537
async show(e: PointerEvent | JQuery.TouchStartEvent | JQuery.ContextMenuEvent) {
36-
contextMenu.show({
38+
await contextMenu.show({
3739
x: e.pageX ?? 0,
3840
y: e.pageY ?? 0,
3941
items: await this.getMenuItems(),
40-
selectMenuItemHandler: (item, e) => this.selectMenuItemHandler(item)
42+
selectMenuItemHandler: (item, e) => this.selectMenuItemHandler(item),
43+
onHide: () => {
44+
lastTargetNode?.classList.remove('fancytree-menu-target');
45+
}
4146
});
47+
// It's placed after show to ensure the old target is cleared before showing the context menu again on repeated right-clicks.
48+
lastTargetNode?.classList.remove('fancytree-menu-target');
49+
lastTargetNode = this.node.span;
50+
lastTargetNode.classList.add('fancytree-menu-target');
4251
}
4352

4453
async getMenuItems(): Promise<MenuItem<TreeCommandNames>[]> {

apps/client/src/services/resizer.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,16 @@ function setupLeftPaneResizer(leftPaneVisible: boolean) {
2626
}
2727

2828
if (leftPaneVisible) {
29-
leftInstance = Split(["#left-pane", "#rest-pane"], {
30-
sizes: [leftPaneWidth, 100 - leftPaneWidth],
31-
gutterSize: DEFAULT_GUTTER_SIZE,
32-
onDragEnd: (sizes) => options.save("leftPaneWidth", Math.round(sizes[0]))
29+
// Delayed initialization ensures that all DOM elements are fully rendered and part of the layout,
30+
// preventing Split.js from retrieving incorrect dimensions due to #left-pane not being rendered yet,
31+
// which would cause the minSize setting to have no effect.
32+
requestAnimationFrame(() => {
33+
leftInstance = Split(["#left-pane", "#rest-pane"], {
34+
sizes: [leftPaneWidth, 100 - leftPaneWidth],
35+
gutterSize: DEFAULT_GUTTER_SIZE,
36+
minSize: [150, 300],
37+
onDragEnd: (sizes) => options.save("leftPaneWidth", Math.round(sizes[0]))
38+
});
3339
});
3440
}
3541
}

apps/client/src/stylesheets/style.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,14 @@ body .CodeMirror {
438438
background-color: #eeeeee;
439439
}
440440

441+
.cm-matchhighlight.ck-find-result{
442+
background: var(--ck-color-highlight-background);
443+
}
444+
445+
.cm-matchhighlight.ck-find-result_selected {
446+
background-color: #ff9633;
447+
}
448+
441449
.CodeMirror pre.CodeMirror-placeholder {
442450
color: #999 !important;
443451
}

apps/client/src/stylesheets/tree.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ span.fancytree-node:hover {
208208
border: 1px solid var(--main-border-color);
209209
}
210210

211+
span.fancytree-node.fancytree-menu-target {
212+
box-shadow: inset 0 0 0 1px var(--main-border-color);
213+
}
214+
211215
.fancytree-title:hover,
212216
span.fancytree-node:hover .fancytree-title {
213217
border: 0;

apps/client/src/widgets/buttons/left_pane_toggle.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ import { t } from "../../services/i18n.js";
55
import type { EventData } from "../../components/app_context.js";
66

77
export default class LeftPaneToggleWidget extends CommandButtonWidget {
8+
private currentLeftPaneVisible: boolean;
89

910
constructor(isHorizontalLayout: boolean) {
1011
super();
1112

13+
this.currentLeftPaneVisible = options.is("leftPaneVisible");
14+
1215
this.class(isHorizontalLayout ? "toggle-button" : "launcher-button");
1316

1417
this.settings.icon = () => {
@@ -21,21 +24,23 @@ export default class LeftPaneToggleWidget extends CommandButtonWidget {
2124

2225
this.settings.title = () => (options.is("leftPaneVisible") ? t("left_pane_toggle.hide_panel") : t("left_pane_toggle.show_panel"));
2326

24-
this.settings.command = () => (options.is("leftPaneVisible") ? "hideLeftPane" : "showLeftPane");
27+
this.settings.command = () => (this.currentLeftPaneVisible ? "hideLeftPane" : "showLeftPane");
2528

2629
if (isHorizontalLayout) {
2730
this.settings.titlePlacement = "bottom";
2831
}
2932
}
3033

3134
refreshIcon() {
32-
super.refreshIcon();
33-
34-
splitService.setupLeftPaneResizer(options.is("leftPaneVisible"));
35+
if (document.hasFocus() || this.currentLeftPaneVisible === true) {
36+
super.refreshIcon();
37+
splitService.setupLeftPaneResizer(this.currentLeftPaneVisible);
38+
}
3539
}
3640

3741
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
38-
if (loadResults.isOptionReloaded("leftPaneVisible")) {
42+
if (loadResults.isOptionReloaded("leftPaneVisible") && document.hasFocus()) {
43+
this.currentLeftPaneVisible = options.is("leftPaneVisible");
3944
this.refreshIcon();
4045
}
4146
}

apps/client/src/widgets/containers/left_pane_container.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default class LeftPaneContainer extends FlexContainer<Component> {
1717
}
1818

1919
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
20-
if (loadResults.isOptionReloaded("leftPaneVisible")) {
20+
if (loadResults.isOptionReloaded("leftPaneVisible") && document.hasFocus()) {
2121
const visible = this.isEnabled();
2222
this.toggleInt(visible);
2323

apps/client/src/widgets/find.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,9 @@ export default class FindWidget extends NoteContextAwareWidget {
143143
this.$currentFound = this.$widget.find(".find-widget-current-found");
144144
this.$totalFound = this.$widget.find(".find-widget-total-found");
145145
this.$caseSensitiveCheckbox = this.$widget.find(".find-widget-case-sensitive-checkbox");
146-
this.$caseSensitiveCheckbox.change(() => this.performFind());
146+
this.$caseSensitiveCheckbox.on("change", () => this.performFind());
147147
this.$matchWordsCheckbox = this.$widget.find(".find-widget-match-words-checkbox");
148-
this.$matchWordsCheckbox.change(() => this.performFind());
148+
this.$matchWordsCheckbox.on("change", () => this.performFind());
149149
this.$previousButton = this.$widget.find(".find-widget-previous-button");
150150
this.$previousButton.on("click", () => this.findNext(-1));
151151
this.$nextButton = this.$widget.find(".find-widget-next-button");
@@ -160,7 +160,7 @@ export default class FindWidget extends NoteContextAwareWidget {
160160
this.$replaceButton = this.$widget.find(".replace-widget-replace-button");
161161
this.$replaceButton.on("click", () => this.replace());
162162

163-
this.$input.keydown(async (e) => {
163+
this.$input.on("keydown", async (e) => {
164164
if ((e.metaKey || e.ctrlKey) && (e.key === "F" || e.key === "f")) {
165165
// If ctrl+f is pressed when the findbox is shown, select the
166166
// whole input to find
@@ -172,7 +172,7 @@ export default class FindWidget extends NoteContextAwareWidget {
172172
}
173173
});
174174

175-
this.$widget.keydown(async (e) => {
175+
this.$widget.on("keydown", async (e) => {
176176
if (e.key === "Escape") {
177177
await this.closeSearch();
178178
}
@@ -197,9 +197,14 @@ export default class FindWidget extends NoteContextAwareWidget {
197197
const isReadOnly = await this.noteContext?.isReadOnly();
198198

199199
let selectedText = "";
200-
if (this.note?.type === "code" && !isReadOnly && this.noteContext) {
201-
const codeEditor = await this.noteContext.getCodeEditor();
202-
selectedText = codeEditor.getSelection();
200+
if (this.note?.type === "code" && this.noteContext) {
201+
if (isReadOnly){
202+
const $content = await this.noteContext.getContentElement();
203+
selectedText = $content.find('.cm-matchhighlight').first().text();
204+
} else {
205+
const codeEditor = await this.noteContext.getCodeEditor();
206+
selectedText = codeEditor.getSelection();
207+
}
203208
} else {
204209
selectedText = window.getSelection()?.toString() || "";
205210
}
@@ -235,6 +240,12 @@ export default class FindWidget extends NoteContextAwareWidget {
235240
}
236241
}
237242

243+
async readOnlyTemporarilyDisabledEvent({ noteContext }: EventData<"readOnlyTemporarilyDisabled">) {
244+
if (this.isNoteContext(noteContext.ntxId)) {
245+
await this.closeSearch();
246+
}
247+
}
248+
238249
async getHandler() {
239250
if (this.note?.type === "render") {
240251
return this.htmlHandler;

apps/client/src/widgets/find_in_html.ts

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// uses for highlighting matches, use the same one on CodeMirror
33
// for consistency
44
import utils from "../services/utils.js";
5-
import appContext from "../components/app_context.js";
65
import type FindWidget from "./find.js";
76
import type { FindResult } from "./find.js";
87

@@ -39,12 +38,16 @@ export default class FindInHtml {
3938
caseSensitive: matchCase,
4039
done: async () => {
4140
this.$results = $content.find(`.${FIND_RESULT_CSS_CLASSNAME}`);
42-
this.currentIndex = 0;
41+
const scrollingContainer = $content[0].closest('.scrolling-container');
42+
const containerTop = scrollingContainer?.getBoundingClientRect().top ?? 0;
43+
const closestIndex = this.$results.toArray().findIndex(el => el.getBoundingClientRect().top >= containerTop);
44+
this.currentIndex = closestIndex >= 0 ? closestIndex : 0;
45+
4346
await this.jumpTo();
4447

4548
res({
4649
totalFound: this.$results.length,
47-
currentFound: Math.min(1, this.$results.length)
50+
currentFound: this.$results.length > 0 ? this.currentIndex + 1 : 0
4851
});
4952
}
5053
});
@@ -71,27 +74,17 @@ export default class FindInHtml {
7174

7275
async findBoxClosed(totalFound: number, currentFound: number) {
7376
const $content = await this.parent?.noteContext?.getContentElement();
74-
if ($content) {
77+
if (typeof $content?.unmark === 'function') {
7578
$content.unmark();
7679
}
7780
}
7881

7982
async jumpTo() {
8083
if (this.$results?.length) {
81-
const offsetTop = 100;
8284
const $current = this.$results.eq(this.currentIndex);
8385
this.$results.removeClass(FIND_RESULT_SELECTED_CSS_CLASSNAME);
84-
85-
if ($current.length) {
86-
$current.addClass(FIND_RESULT_SELECTED_CSS_CLASSNAME);
87-
const position = $current.position().top - offsetTop;
88-
89-
const $content = await this.parent.noteContext?.getContentElement();
90-
if ($content) {
91-
const $contentWidget = appContext.getComponentByEl($content[0]);
92-
$contentWidget.triggerCommand("scrollContainerTo", { position });
93-
}
94-
}
86+
$current[0].scrollIntoView();
87+
$current.addClass(FIND_RESULT_SELECTED_CSS_CLASSNAME);
9588
}
9689
}
9790
}

apps/client/src/widgets/find_in_text.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,26 @@ export default class FindInText {
5555
const options = { matchCase: matchCase, wholeWords: wholeWord };
5656
findResult = textEditor.execute("find", searchTerm, options);
5757
totalFound = findResult.results.length;
58-
// Find the result beyond the cursor
59-
const cursorPos = model.document.selection.getLastPosition();
60-
for (let i = 0; i < findResult.results.length; ++i) {
61-
const marker = findResult.results.get(i)?.marker;
62-
const fromPos = marker?.getStart();
63-
if (cursorPos && fromPos && fromPos.compareWith(cursorPos) !== "before") {
64-
currentFound = i;
65-
break;
58+
const selection = model.document.selection;
59+
// If text is selected, highlight the corresponding result;
60+
// otherwise, highlight the first visible result in the scrolling container.
61+
if (!selection.isCollapsed) {
62+
const cursorPos = selection.getFirstPosition();
63+
for (let i = 0; i < findResult.results.length; ++i) {
64+
const marker = findResult.results.get(i)?.marker;
65+
const fromPos = marker?.getStart();
66+
if (cursorPos && fromPos?.compareWith(cursorPos) !== "before") {
67+
currentFound = i;
68+
break;
69+
}
6670
}
71+
} else {
72+
const editorEl = textEditor?.sourceElement;
73+
const findResultElement = editorEl?.querySelectorAll(".ck-find-result");
74+
const scrollingContainer = editorEl?.closest('.scrolling-container');
75+
const containerTop = scrollingContainer?.getBoundingClientRect().top ?? 0;
76+
const closestIndex = Array.from(findResultElement ?? []).findIndex((el) => el.getBoundingClientRect().top >= containerTop);
77+
currentFound = closestIndex >= 0 ? closestIndex : 0;
6778
}
6879
}
6980

0 commit comments

Comments
 (0)