fix(editor): middle click open new tab (#12902)
Close [BS-3251](https://linear.app/affine-design/issue/BS-3251/正文的inline链接,chrome中,中键开新窗口的行为丢失了) #### PR Dependency Tree * **PR #12902** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Middle-clicking on links and references now opens them in a new browser tab. - Linux platform detection has been added for improved environment-specific behavior. - **Bug Fixes** - Middle-click paste prevention is now limited to Linux environments when the relevant setting is disabled and excludes clicks on links and references. - **Tests** - Added end-to-end tests to verify that middle-clicking links opens them in a new tab for external links, internal links, and reference documents. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
a8c18cd631
commit
1686b92adb
@ -59,6 +59,7 @@ export class AffineLink extends WithDisposable(ShadowlessElement) {
|
|||||||
|
|
||||||
refNodeSlotsProvider.docLinkClicked.next({
|
refNodeSlotsProvider.docLinkClicked.next({
|
||||||
...referenceInfo,
|
...referenceInfo,
|
||||||
|
openMode: e?.button === 1 ? 'open-in-new-tab' : undefined,
|
||||||
host: this.std.host,
|
host: this.std.host,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -149,6 +150,7 @@ export class AffineLink extends WithDisposable(ShadowlessElement) {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
style=${styleMap(style)}
|
style=${styleMap(style)}
|
||||||
@click=${this.openLink}
|
@click=${this.openLink}
|
||||||
|
@auxclick=${this.openLink}
|
||||||
@mouseup=${this._onMouseUp}
|
@mouseup=${this._onMouseUp}
|
||||||
><v-text .str=${this.delta.insert}></v-text
|
><v-text .str=${this.delta.insert}></v-text
|
||||||
></a>`;
|
></a>`;
|
||||||
|
@ -154,6 +154,8 @@ export class AffineReference extends WithDisposable(ShadowlessElement) {
|
|||||||
this.std.getOptional(RefNodeSlotsProvider)?.docLinkClicked.next({
|
this.std.getOptional(RefNodeSlotsProvider)?.docLinkClicked.next({
|
||||||
...this.referenceInfo,
|
...this.referenceInfo,
|
||||||
...event,
|
...event,
|
||||||
|
openMode:
|
||||||
|
event?.event?.button === 1 ? 'open-in-new-tab' : event?.openMode,
|
||||||
host: this.std.host,
|
host: this.std.host,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -285,6 +287,7 @@ export class AffineReference extends WithDisposable(ShadowlessElement) {
|
|||||||
class="affine-reference"
|
class="affine-reference"
|
||||||
style=${styleMap(style)}
|
style=${styleMap(style)}
|
||||||
@click=${(event: MouseEvent) => this.open({ event })}
|
@click=${(event: MouseEvent) => this.open({ event })}
|
||||||
|
@auxclick=${(event: MouseEvent) => this.open({ event })}
|
||||||
>${content}<v-text .str=${ZERO_WIDTH_FOR_EMBED_NODE}></v-text
|
>${content}<v-text .str=${ZERO_WIDTH_FOR_EMBED_NODE}></v-text
|
||||||
></span>`;
|
></span>`;
|
||||||
}
|
}
|
||||||
|
2
blocksuite/framework/global/src/env/index.ts
vendored
2
blocksuite/framework/global/src/env/index.ts
vendored
@ -26,4 +26,6 @@ export const IS_IPAD =
|
|||||||
|
|
||||||
export const IS_WINDOWS = /Win/.test(platform) || /win32/.test(platform);
|
export const IS_WINDOWS = /Win/.test(platform) || /win32/.test(platform);
|
||||||
|
|
||||||
|
export const IS_LINUX = /Linux/.test(platform);
|
||||||
|
|
||||||
export const IS_MOBILE = IS_IOS || IS_IPAD || IS_ANDROID;
|
export const IS_MOBILE = IS_IOS || IS_IPAD || IS_ANDROID;
|
||||||
|
@ -14,6 +14,7 @@ import track from '@affine/track';
|
|||||||
import { appendParagraphCommand } from '@blocksuite/affine/blocks/paragraph';
|
import { appendParagraphCommand } from '@blocksuite/affine/blocks/paragraph';
|
||||||
import type { DocTitle } from '@blocksuite/affine/fragments/doc-title';
|
import type { DocTitle } from '@blocksuite/affine/fragments/doc-title';
|
||||||
import { DisposableGroup } from '@blocksuite/affine/global/disposable';
|
import { DisposableGroup } from '@blocksuite/affine/global/disposable';
|
||||||
|
import { IS_LINUX } from '@blocksuite/affine/global/env';
|
||||||
import type { DocMode, RootBlockModel } from '@blocksuite/affine/model';
|
import type { DocMode, RootBlockModel } from '@blocksuite/affine/model';
|
||||||
import {
|
import {
|
||||||
customImageProxyMiddleware,
|
customImageProxyMiddleware,
|
||||||
@ -178,7 +179,14 @@ const BlockSuiteEditorImpl = ({
|
|||||||
const editorContainer = rootRef.current;
|
const editorContainer = rootRef.current;
|
||||||
if (editorContainer) {
|
if (editorContainer) {
|
||||||
const handleMiddleClick = (e: MouseEvent) => {
|
const handleMiddleClick = (e: MouseEvent) => {
|
||||||
if (!enableMiddleClickPaste && e.button === 1) {
|
if (
|
||||||
|
e.target instanceof HTMLElement &&
|
||||||
|
(e.target.closest('affine-reference') ||
|
||||||
|
e.target.closest('affine-link'))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!enableMiddleClickPaste && IS_LINUX && e.button === 1) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
import { toolbarButtons } from '@affine-test/kit/bs/linked-toolbar';
|
import { toolbarButtons } from '@affine-test/kit/bs/linked-toolbar';
|
||||||
import { waitNextFrame } from '@affine-test/kit/bs/misc';
|
import { waitNextFrame } from '@affine-test/kit/bs/misc';
|
||||||
import { test } from '@affine-test/kit/playwright';
|
import { test } from '@affine-test/kit/playwright';
|
||||||
import { clickEdgelessModeButton } from '@affine-test/kit/utils/editor';
|
import {
|
||||||
|
clickEdgelessModeButton,
|
||||||
|
locateToolbar,
|
||||||
|
} from '@affine-test/kit/utils/editor';
|
||||||
import {
|
import {
|
||||||
pasteByKeyboard,
|
pasteByKeyboard,
|
||||||
|
pressArrowUp,
|
||||||
|
pressBackspace,
|
||||||
|
pressEnter,
|
||||||
selectAllByKeyboard,
|
selectAllByKeyboard,
|
||||||
writeTextToClipboard,
|
writeTextToClipboard,
|
||||||
} from '@affine-test/kit/utils/keyboard';
|
} from '@affine-test/kit/utils/keyboard';
|
||||||
@ -13,6 +19,7 @@ import {
|
|||||||
createLinkedPage,
|
createLinkedPage,
|
||||||
createTodayPage,
|
createTodayPage,
|
||||||
getBlockSuiteEditorTitle,
|
getBlockSuiteEditorTitle,
|
||||||
|
type,
|
||||||
waitForEmptyEditor,
|
waitForEmptyEditor,
|
||||||
} from '@affine-test/kit/utils/page-logic';
|
} from '@affine-test/kit/utils/page-logic';
|
||||||
import {
|
import {
|
||||||
@ -1164,3 +1171,75 @@ test('should add HTTP protocol into link automatically', async ({ page }) => {
|
|||||||
url = await linkPreview.locator('a').getAttribute('href');
|
url = await linkPreview.locator('a').getAttribute('href');
|
||||||
expect(url).toBe(link);
|
expect(url).toBe(link);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should open link in new tab when middle clicking on link', async ({
|
||||||
|
page,
|
||||||
|
context,
|
||||||
|
}) => {
|
||||||
|
await pressEnter(page);
|
||||||
|
|
||||||
|
// external link
|
||||||
|
{
|
||||||
|
await type(page, 'external-link');
|
||||||
|
await selectAllByKeyboard(page);
|
||||||
|
const toolbar = locateToolbar(page);
|
||||||
|
await toolbar.getByTestId('link').click();
|
||||||
|
const input = page.locator('.affine-link-popover-input');
|
||||||
|
|
||||||
|
const externalUrl = new URL('https://github.com/').toString();
|
||||||
|
await input.fill(externalUrl);
|
||||||
|
await pressEnter(page);
|
||||||
|
|
||||||
|
const newTabPromise = context.waitForEvent('page');
|
||||||
|
|
||||||
|
await page.locator('affine-link').click({ button: 'middle' });
|
||||||
|
|
||||||
|
const newTab = await newTabPromise;
|
||||||
|
await expect(newTab).toHaveURL(externalUrl);
|
||||||
|
await newTab.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
await selectAllByKeyboard(page);
|
||||||
|
await pressBackspace(page);
|
||||||
|
|
||||||
|
// internal link
|
||||||
|
{
|
||||||
|
await type(page, 'internal-link');
|
||||||
|
const url = page.url();
|
||||||
|
await selectAllByKeyboard(page);
|
||||||
|
const toolbar = locateToolbar(page);
|
||||||
|
await toolbar.getByTestId('link').click();
|
||||||
|
const input = page.locator('.affine-link-popover-input');
|
||||||
|
await input.fill(url);
|
||||||
|
await pressEnter(page);
|
||||||
|
|
||||||
|
const newTabPromise = context.waitForEvent('page');
|
||||||
|
|
||||||
|
await page.locator('affine-link').click({ button: 'middle' });
|
||||||
|
|
||||||
|
const newTab = await newTabPromise;
|
||||||
|
// there is a refreshKey in the url
|
||||||
|
expect(newTab.url()).toContain(url);
|
||||||
|
await newTab.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
await selectAllByKeyboard(page);
|
||||||
|
await pressBackspace(page);
|
||||||
|
|
||||||
|
// reference doc
|
||||||
|
{
|
||||||
|
await pressArrowUp(page);
|
||||||
|
await type(page, 'ThisPage');
|
||||||
|
await pressEnter(page);
|
||||||
|
await type(page, '@ThisPage');
|
||||||
|
await pressEnter(page);
|
||||||
|
|
||||||
|
const newTabPromise = context.waitForEvent('page');
|
||||||
|
|
||||||
|
await page.locator('affine-reference').click({ button: 'middle' });
|
||||||
|
|
||||||
|
const newTab = await newTabPromise;
|
||||||
|
expect(newTab.url()).toContain(page.url());
|
||||||
|
await newTab.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user