Skip to content
This repository was archived by the owner on Jun 24, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion apps/client/src/components/tab_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,18 @@ export default class TabManager extends Component {
return noteContext;
}

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

await noteContext.setNote(targetNoteId);

if (activate && noteContext.notePath) {
this.activateNoteContext(noteContext.ntxId, false);
await this.triggerEvent("noteSwitchedAndActivated", {
noteContext,
notePath: noteContext.notePath
});
}
}

async openInSameTab(targetNoteId: string, hoistedNoteId: string | null = null) {
Expand Down
3 changes: 2 additions & 1 deletion apps/client/src/translations/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@
"collapseWholeTree": "collapse whole note tree",
"collapseSubTree": "collapse sub-tree",
"tabShortcuts": "Tab shortcuts",
"newTabNoteLink": "<kbd>CTRL+click</kbd> - (or middle mouse click) on note link opens note in a new tab",
"newTabNoteLink": "<kbd>Ctrl+click</kbd> - (or <kbd>middle mouse click</kbd>) on note link opens note in a new tab",
"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",
"onlyInDesktop": "Only in desktop (Electron build)",
"openEmptyTab": "open empty tab",
"closeActiveTab": "close active tab",
Expand Down
3 changes: 2 additions & 1 deletion apps/client/src/widgets/buttons/launcher/note_launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,12 @@ export default class NoteLauncher extends AbstractLauncher {
await appContext.tabManager.openInSameTab(targetNoteId, hoistedNoteId);
} else {
const ctrlKey = utils.isCtrlKey(evt);
const activate = evt.shiftKey ? true : false;

if ((evt.which === 1 && ctrlKey) || evt.which === 2) {
// TODO: Fix once tabManager is ported.
//@ts-ignore
await appContext.tabManager.openInNewTab(targetNoteId, hoistedNoteId);
await appContext.tabManager.openInNewTab(targetNoteId, hoistedNoteId, activate);
} else {
// TODO: Fix once tabManager is ported.
//@ts-ignore
Expand Down
8 changes: 7 additions & 1 deletion apps/client/src/widgets/buttons/open_note_button_widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,21 @@ export default class OpenNoteButtonWidget extends OnClickButtonWidget {
if (evt.which === 3) {
return;
}
const hoistedNoteId = this.getHoistedNoteId();
const ctrlKey = utils.isCtrlKey(evt);

if ((evt.which === 1 && ctrlKey) || evt.which === 2) {
await appContext.tabManager.openInNewTab(this.noteToOpen.noteId);
const activate = evt.shiftKey ? true : false;
await appContext.tabManager.openInNewTab(this.noteToOpen.noteId, hoistedNoteId, activate);
} else {
await appContext.tabManager.openInSameTab(this.noteToOpen.noteId);
}
}

getHoistedNoteId() {
return this.noteToOpen.getRelationValue("hoistedNote") || appContext.tabManager.getActiveContext()?.hoistedNoteId;
}

initialRenderCompleteEvent() {
// we trigger refresh above
}
Expand Down
1 change: 1 addition & 0 deletions apps/client/src/widgets/dialogs/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const TPL = /*html*/`
<p class="card-text">
<ul>
<li>${t("help.newTabNoteLink")}</li>
<li>${t("help.newTabWithActivationNoteLink")}</li>
</ul>
<h6>${t("help.onlyInDesktop")}:</h6>
<ul>
Expand Down
15 changes: 10 additions & 5 deletions apps/client/src/widgets/note_tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,9 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
const notePath = treeService.getNotePath(node);

if (notePath) {
appContext.tabManager.openTabWithNoteWithHoisting(notePath);
appContext.tabManager.openTabWithNoteWithHoisting(notePath, {
activate: e.shiftKey ? true : false
});
}

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

const targetType = data.targetType;
const node = data.node;
const ctrlKey = utils.isCtrlKey(event);

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

return false;
} else if (targetType === "title" || targetType === "icon") {
if (event.shiftKey) {
if (event.shiftKey && !ctrlKey) {
const activeNode = this.getActiveNode();

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

node.setFocus(true);
} else if ((!utils.isMac() && event.ctrlKey) || (utils.isMac() && event.metaKey)) {
} else if (ctrlKey) {
const notePath = treeService.getNotePath(node);
appContext.tabManager.openTabWithNoteWithHoisting(notePath);
appContext.tabManager.openTabWithNoteWithHoisting(notePath, {
activate: event.shiftKey ? true : false
});
} else if (event.altKey) {
node.setSelected(!node.isSelected());
node.setFocus(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ export default class AbstractTextTypeWidget extends TypeWidget {
const isLeftClick = e.which === 1;
const isMiddleClick = e.which === 2;
const ctrlKey = utils.isCtrlKey(e);
const activate = (isLeftClick && ctrlKey && e.shiftKey) || (isMiddleClick && e.shiftKey);

if ((isLeftClick && ctrlKey) || isMiddleClick) {
this.openImageInNewTab($(e.target));
this.openImageInNewTab($(e.target), activate);
} else if (isLeftClick && singleClickOpens) {
this.openImageInCurrentTab($(e.target));
}
Expand All @@ -39,11 +40,11 @@ export default class AbstractTextTypeWidget extends TypeWidget {
}
}

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

if (parsedImage) {
appContext.tabManager.openTabWithNoteWithHoisting(parsedImage.noteId, { viewScope: parsedImage.viewScope });
appContext.tabManager.openTabWithNoteWithHoisting(parsedImage.noteId, { activate, viewScope: parsedImage.viewScope });
} else {
window.open($img.prop("src"), "_blank");
}
Expand Down
47 changes: 47 additions & 0 deletions apps/server-e2e/src/layout/open_note_and_activate.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { test, expect } from "@playwright/test";
import App from "../support/app";

const NOTE_TITLE = "Trilium Integration Test DB";

test("Opens and activate a note from launcher Bar", async ({ page, context }) => {
const app = new App(page, context);
await app.goto();
await app.closeAllTabs();

const mapButton = app.launcherBar.locator(".launcher-button.bx-search.visible");
await expect(mapButton).toBeVisible();

await page.keyboard.down('Control');
await page.keyboard.down('Shift');

await mapButton.click();

await page.keyboard.up('Control');
await page.keyboard.up('Shift');

const tabs = app.tabBar.locator(".note-tab");
await expect(tabs).toHaveCount(2);

const secondTab = tabs.nth(1);
await expect(secondTab).toHaveAttribute('active', '');
});

test("Opens and activate a note from note tree", async ({ page, context }) => {
const app = new App(page, context);
await app.goto();
await app.closeAllTabs();

await page.keyboard.down('Control');
await page.keyboard.down('Shift');

await app.clickNoteOnNoteTreeByTitle(NOTE_TITLE);

await page.keyboard.up('Control');
await page.keyboard.up('Shift');

const tabs = app.tabBar.locator(".note-tab");
await expect(tabs).toHaveCount(2);

const secondTab = tabs.nth(1);
await expect(secondTab).toHaveAttribute('active', '');
});
Loading