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

Commit 5f27357

Browse files
authored
Merge pull request #1854 from TriliumNext/open_new_tab
Open note and activate
2 parents f526332 + f26a7a9 commit 5f27357

File tree

8 files changed

+82
-12
lines changed

8 files changed

+82
-12
lines changed

apps/client/src/components/tab_manager.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,10 +277,18 @@ export default class TabManager extends Component {
277277
return noteContext;
278278
}
279279

280-
async openInNewTab(targetNoteId: string, hoistedNoteId: string | null = null) {
280+
async openInNewTab(targetNoteId: string, hoistedNoteId: string | null = null, activate: boolean = false) {
281281
const noteContext = await this.openEmptyTab(null, hoistedNoteId || this.getActiveContext()?.hoistedNoteId);
282282

283283
await noteContext.setNote(targetNoteId);
284+
285+
if (activate && noteContext.notePath) {
286+
this.activateNoteContext(noteContext.ntxId, false);
287+
await this.triggerEvent("noteSwitchedAndActivated", {
288+
noteContext,
289+
notePath: noteContext.notePath
290+
});
291+
}
284292
}
285293

286294
async openInSameTab(targetNoteId: string, hoistedNoteId: string | null = null) {

apps/client/src/translations/en/translation.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@
126126
"collapseWholeTree": "collapse whole note tree",
127127
"collapseSubTree": "collapse sub-tree",
128128
"tabShortcuts": "Tab shortcuts",
129-
"newTabNoteLink": "<kbd>CTRL+click</kbd> - (or middle mouse click) on note link opens note in a new tab",
129+
"newTabNoteLink": "<kbd>Ctrl+click</kbd> - (or <kbd>middle mouse click</kbd>) on note link opens note in a new tab",
130+
"newTabWithActivationNoteLink": "<kbd>Ctrl+Shift+click</kbd> - (or <kbd>Shift+middle mouse click</kbd>) on note link opens and activates the note in a new tab",
130131
"onlyInDesktop": "Only in desktop (Electron build)",
131132
"openEmptyTab": "open empty tab",
132133
"closeActiveTab": "close active tab",

apps/client/src/widgets/buttons/launcher/note_launcher.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,12 @@ export default class NoteLauncher extends AbstractLauncher {
5353
await appContext.tabManager.openInSameTab(targetNoteId, hoistedNoteId);
5454
} else {
5555
const ctrlKey = utils.isCtrlKey(evt);
56+
const activate = evt.shiftKey ? true : false;
5657

5758
if ((evt.which === 1 && ctrlKey) || evt.which === 2) {
5859
// TODO: Fix once tabManager is ported.
5960
//@ts-ignore
60-
await appContext.tabManager.openInNewTab(targetNoteId, hoistedNoteId);
61+
await appContext.tabManager.openInNewTab(targetNoteId, hoistedNoteId, activate);
6162
} else {
6263
// TODO: Fix once tabManager is ported.
6364
//@ts-ignore

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,21 @@ export default class OpenNoteButtonWidget extends OnClickButtonWidget {
2828
if (evt.which === 3) {
2929
return;
3030
}
31+
const hoistedNoteId = this.getHoistedNoteId();
3132
const ctrlKey = utils.isCtrlKey(evt);
3233

3334
if ((evt.which === 1 && ctrlKey) || evt.which === 2) {
34-
await appContext.tabManager.openInNewTab(this.noteToOpen.noteId);
35+
const activate = evt.shiftKey ? true : false;
36+
await appContext.tabManager.openInNewTab(this.noteToOpen.noteId, hoistedNoteId, activate);
3537
} else {
3638
await appContext.tabManager.openInSameTab(this.noteToOpen.noteId);
3739
}
3840
}
3941

42+
getHoistedNoteId() {
43+
return this.noteToOpen.getRelationValue("hoistedNote") || appContext.tabManager.getActiveContext()?.hoistedNoteId;
44+
}
45+
4046
initialRenderCompleteEvent() {
4147
// we trigger refresh above
4248
}

apps/client/src/widgets/dialogs/help.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const TPL = /*html*/`
3838
<p class="card-text">
3939
<ul>
4040
<li>${t("help.newTabNoteLink")}</li>
41+
<li>${t("help.newTabWithActivationNoteLink")}</li>
4142
</ul>
4243
<h6>${t("help.onlyInDesktop")}:</h6>
4344
<ul>

apps/client/src/widgets/note_tree.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,9 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
230230
const notePath = treeService.getNotePath(node);
231231

232232
if (notePath) {
233-
appContext.tabManager.openTabWithNoteWithHoisting(notePath);
233+
appContext.tabManager.openTabWithNoteWithHoisting(notePath, {
234+
activate: e.shiftKey ? true : false
235+
});
234236
}
235237

236238
e.stopPropagation();
@@ -343,11 +345,12 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
343345
},
344346
scrollParent: this.$tree,
345347
minExpandLevel: 2, // root can't be collapsed
346-
click: (event, data): boolean => {
348+
click: (event: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent | React.PointerEvent<HTMLCanvasElement>, data): boolean => {
347349
this.activityDetected();
348350

349351
const targetType = data.targetType;
350352
const node = data.node;
353+
const ctrlKey = utils.isCtrlKey(event);
351354

352355
if (node.isSelected() && targetType === "icon") {
353356
this.triggerCommand("openBulkActionsDialog", {
@@ -356,7 +359,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
356359

357360
return false;
358361
} else if (targetType === "title" || targetType === "icon") {
359-
if (event.shiftKey) {
362+
if (event.shiftKey && !ctrlKey) {
360363
const activeNode = this.getActiveNode();
361364

362365
if (activeNode.getParent() !== node.getParent()) {
@@ -381,9 +384,11 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
381384
}
382385

383386
node.setFocus(true);
384-
} else if ((!utils.isMac() && event.ctrlKey) || (utils.isMac() && event.metaKey)) {
387+
} else if (ctrlKey) {
385388
const notePath = treeService.getNotePath(node);
386-
appContext.tabManager.openTabWithNoteWithHoisting(notePath);
389+
appContext.tabManager.openTabWithNoteWithHoisting(notePath, {
390+
activate: event.shiftKey ? true : false
391+
});
387392
} else if (event.altKey) {
388393
node.setSelected(!node.isSelected());
389394
node.setFocus(true);

apps/client/src/widgets/type_widgets/abstract_text_type_widget.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ export default class AbstractTextTypeWidget extends TypeWidget {
2020
const isLeftClick = e.which === 1;
2121
const isMiddleClick = e.which === 2;
2222
const ctrlKey = utils.isCtrlKey(e);
23+
const activate = (isLeftClick && ctrlKey && e.shiftKey) || (isMiddleClick && e.shiftKey);
2324

2425
if ((isLeftClick && ctrlKey) || isMiddleClick) {
25-
this.openImageInNewTab($(e.target));
26+
this.openImageInNewTab($(e.target), activate);
2627
} else if (isLeftClick && singleClickOpens) {
2728
this.openImageInCurrentTab($(e.target));
2829
}
@@ -39,11 +40,11 @@ export default class AbstractTextTypeWidget extends TypeWidget {
3940
}
4041
}
4142

42-
async openImageInNewTab($img: JQuery<HTMLElement>) {
43+
async openImageInNewTab($img: JQuery<HTMLElement>, activate: boolean = false) {
4344
const parsedImage = await this.parseFromImage($img);
4445

4546
if (parsedImage) {
46-
appContext.tabManager.openTabWithNoteWithHoisting(parsedImage.noteId, { viewScope: parsedImage.viewScope });
47+
appContext.tabManager.openTabWithNoteWithHoisting(parsedImage.noteId, { activate, viewScope: parsedImage.viewScope });
4748
} else {
4849
window.open($img.prop("src"), "_blank");
4950
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { test, expect } from "@playwright/test";
2+
import App from "../support/app";
3+
4+
const NOTE_TITLE = "Trilium Integration Test DB";
5+
6+
test("Opens and activate a note from launcher Bar", async ({ page, context }) => {
7+
const app = new App(page, context);
8+
await app.goto();
9+
await app.closeAllTabs();
10+
11+
const mapButton = app.launcherBar.locator(".launcher-button.bx-search.visible");
12+
await expect(mapButton).toBeVisible();
13+
14+
await page.keyboard.down('Control');
15+
await page.keyboard.down('Shift');
16+
17+
await mapButton.click();
18+
19+
await page.keyboard.up('Control');
20+
await page.keyboard.up('Shift');
21+
22+
const tabs = app.tabBar.locator(".note-tab");
23+
await expect(tabs).toHaveCount(2);
24+
25+
const secondTab = tabs.nth(1);
26+
await expect(secondTab).toHaveAttribute('active', '');
27+
});
28+
29+
test("Opens and activate a note from note tree", async ({ page, context }) => {
30+
const app = new App(page, context);
31+
await app.goto();
32+
await app.closeAllTabs();
33+
34+
await page.keyboard.down('Control');
35+
await page.keyboard.down('Shift');
36+
37+
await app.clickNoteOnNoteTreeByTitle(NOTE_TITLE);
38+
39+
await page.keyboard.up('Control');
40+
await page.keyboard.up('Shift');
41+
42+
const tabs = app.tabBar.locator(".note-tab");
43+
await expect(tabs).toHaveCount(2);
44+
45+
const secondTab = tabs.nth(1);
46+
await expect(secondTab).toHaveAttribute('active', '');
47+
});

0 commit comments

Comments
 (0)