Skip to content

Commit c62cc00

Browse files
fundonpull[bot]
authored andcommitted
chore: enchance copy link action (#8797)
What's Changed * enhance copy link action * detect compatibility * fall back to `document.execCommand`
1 parent 2ea2877 commit c62cc00

File tree

5 files changed

+130
-34
lines changed

5 files changed

+130
-34
lines changed

packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/database-block.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
} from '@affine/core/components/hooks/affine/use-share-url';
66
import { getAffineCloudBaseUrl } from '@affine/core/modules/cloud/services/fetch';
77
import { EditorService } from '@affine/core/modules/editor';
8+
import { copyLinkToBlockStdScopeClipboard } from '@affine/core/utils/clipboard';
89
import { I18n } from '@affine/i18n';
910
import { track } from '@affine/track';
1011
import type {
@@ -63,18 +64,15 @@ function createCopyLinkToBlockMenuItem(
6364
const type = model.flavour;
6465
const page = editor.editorContainer$.value;
6566

66-
page?.host?.std.clipboard
67-
.writeToClipboard(items => {
68-
items['text/plain'] = str;
69-
// wrap a link
70-
items['text/html'] = `<a href="${str}">${str}</a>`;
71-
return items;
72-
})
73-
.then(() => {
74-
track.doc.editor.toolbar.copyBlockToLink({ type });
67+
copyLinkToBlockStdScopeClipboard(str, page?.host?.std.clipboard)
68+
.then(success => {
69+
if (!success) return;
70+
7571
notify.success({ title: I18n['Copied link to clipboard']() });
7672
})
7773
.catch(console.error);
74+
75+
track.doc.editor.toolbar.copyBlockToLink({ type });
7876
},
7977
});
8078
}

packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/widgets/toolbar.ts

+12-13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
} from '@affine/core/components/hooks/affine/use-share-url';
66
import { getAffineCloudBaseUrl } from '@affine/core/modules/cloud/services/fetch';
77
import { EditorService } from '@affine/core/modules/editor';
8+
import { copyLinkToBlockStdScopeClipboard } from '@affine/core/utils/clipboard';
89
import { I18n } from '@affine/i18n';
910
import { track } from '@affine/track';
1011
import type {
@@ -77,7 +78,7 @@ function createCopyLinkToBlockMenuItem(
7778
) {
7879
return {
7980
...item,
80-
action: (ctx: MenuContext) => {
81+
action: async (ctx: MenuContext) => {
8182
const baseUrl = getAffineCloudBaseUrl();
8283
if (!baseUrl) {
8384
ctx.close();
@@ -114,18 +115,16 @@ function createCopyLinkToBlockMenuItem(
114115
return;
115116
}
116117

117-
ctx.std.clipboard
118-
.writeToClipboard(items => {
119-
items['text/plain'] = str;
120-
// wrap a link
121-
items['text/html'] = `<a href="${str}">${str}</a>`;
122-
return items;
123-
})
124-
.then(() => {
125-
track.doc.editor.toolbar.copyBlockToLink({ type });
126-
notify.success({ title: I18n['Copied link to clipboard']() });
127-
})
128-
.catch(console.error);
118+
const success = await copyLinkToBlockStdScopeClipboard(
119+
str,
120+
ctx.std.clipboard
121+
);
122+
123+
if (success) {
124+
notify.success({ title: I18n['Copied link to clipboard']() });
125+
}
126+
127+
track.doc.editor.toolbar.copyBlockToLink({ type });
129128

130129
ctx.close();
131130
},

packages/frontend/core/src/components/hooks/affine/use-share-url.ts

+8-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { notify } from '@affine/component';
22
import { getAffineCloudBaseUrl } from '@affine/core/modules/cloud/services/fetch';
33
import { toURLSearchParams } from '@affine/core/modules/navigation';
4+
import { copyTextToClipboard } from '@affine/core/utils/clipboard';
45
import { useI18n } from '@affine/i18n';
56
import { track } from '@affine/track';
67
import { type EditorHost } from '@blocksuite/affine/block-std';
@@ -145,23 +146,18 @@ export const useSharingUrl = ({ workspaceId, pageId }: UseSharingUrl) => {
145146
elementIds,
146147
});
147148
if (sharingUrl) {
148-
navigator.clipboard
149-
.writeText(sharingUrl)
150-
.then(() => {
151-
notify.success({
152-
title: t['Copied link to clipboard'](),
153-
});
149+
copyTextToClipboard(sharingUrl)
150+
.then(success => {
151+
if (success) {
152+
notify.success({ title: t['Copied link to clipboard']() });
153+
}
154154
})
155155
.catch(err => {
156156
console.error(err);
157157
});
158-
track.$.sharePanel.$.copyShareLink({
159-
type,
160-
});
158+
track.$.sharePanel.$.copyShareLink({ type });
161159
} else {
162-
notify.error({
163-
title: 'Network not available',
164-
});
160+
notify.error({ title: 'Network not available' });
165161
}
166162
},
167163
[pageId, t, workspaceId]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
const createFakeElement = (text: string) => {
2+
const isRTL = document.documentElement.getAttribute('dir') === 'rtl';
3+
const fakeElement = document.createElement('textarea');
4+
// Prevent zooming on iOS
5+
fakeElement.style.fontSize = '12pt';
6+
// Reset box model
7+
fakeElement.style.border = '0';
8+
fakeElement.style.padding = '0';
9+
fakeElement.style.margin = '0';
10+
// Move element out of screen horizontally
11+
fakeElement.style.position = 'absolute';
12+
fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px';
13+
// Move element to the same position vertically
14+
const yPosition = window.pageYOffset || document.documentElement.scrollTop;
15+
fakeElement.style.top = `${yPosition}px`;
16+
17+
fakeElement.setAttribute('readonly', '');
18+
fakeElement.value = text;
19+
20+
return fakeElement;
21+
};
22+
23+
function command(type: string) {
24+
try {
25+
return document.execCommand(type);
26+
} catch (err) {
27+
console.error(err);
28+
return false;
29+
}
30+
}
31+
32+
export const fakeCopyAction = (text: string, container = document.body) => {
33+
let success = false;
34+
35+
const fakeElement = createFakeElement(text);
36+
container.append(fakeElement);
37+
38+
try {
39+
fakeElement.select();
40+
fakeElement.setSelectionRange(0, fakeElement.value.length);
41+
success = command('copy');
42+
} catch (err) {
43+
console.error(err);
44+
}
45+
46+
fakeElement.remove();
47+
48+
return success;
49+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { type Clipboard as BlockStdScopeClipboard } from '@blocksuite/affine/block-std';
2+
3+
import { fakeCopyAction } from './fake';
4+
5+
const clipboardWriteIsSupported =
6+
'clipboard' in navigator && 'write' in navigator.clipboard;
7+
8+
const clipboardWriteTextIsSupported =
9+
'clipboard' in navigator && 'writeText' in navigator.clipboard;
10+
11+
export const copyTextToClipboard = async (text: string) => {
12+
// 1. try using Async API first, works on HTTPS domain
13+
if (clipboardWriteTextIsSupported) {
14+
try {
15+
await navigator.clipboard.writeText(text);
16+
return true;
17+
} catch (err) {
18+
console.error(err);
19+
}
20+
}
21+
22+
// 2. try using `document.execCommand`
23+
// https://github.com/zenorocha/clipboard.js/blob/master/src/actions/copy.js
24+
return fakeCopyAction(text);
25+
};
26+
27+
export const copyLinkToBlockStdScopeClipboard = async (
28+
text: string,
29+
clipboard?: BlockStdScopeClipboard
30+
) => {
31+
let success = false;
32+
33+
if (!clipboard) return success;
34+
35+
if (clipboardWriteIsSupported) {
36+
try {
37+
await clipboard.writeToClipboard(items => {
38+
items['text/plain'] = text;
39+
// wrap a link
40+
items['text/html'] = `<a href="${text}">${text}</a>`;
41+
return items;
42+
});
43+
success = true;
44+
} catch (error) {
45+
console.error(error);
46+
}
47+
}
48+
49+
if (!success) {
50+
success = await copyTextToClipboard(text);
51+
}
52+
53+
return success;
54+
};

0 commit comments

Comments
 (0)