Skip to content

Commit

Permalink
feat(utils): clipboard 유틸 함수 추가 (clipboardFallbackTextCopy, clipboard…
Browse files Browse the repository at this point in the history
…TextCopy, clipboardImageCopy) (#121)

* feat(utils): Add Clipboard 유틸 함수 추가 (clipboardFallbackTextCopy, clipboardTextCopy, clipboardImageCopy)

* docs: clipboard docs 수정

* refac(utils: clipboardTextCopy 리팩토링

* docs: clipboard 함수 문서 수정

* feat(utils): clipboardImageCopy image/svg+xml 타입 text()처리

* docs: clipboardImageCopy 문서 수정
  • Loading branch information
ssi02014 authored May 14, 2024
1 parent 3764e14 commit 65c6667
Show file tree
Hide file tree
Showing 11 changed files with 231 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/wicked-needles-cheat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modern-kit/utils': minor
---

feat(utils): Add Clipboard 유틸 함수 추가 (clipboardFallbackTextCopy, clipboardTextCopy, clipboardImageCopy) - @ssi02014
28 changes: 28 additions & 0 deletions docs/docs/utils/clipboard/clipboardFallbackTextCopy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# clipboardFallbackTextCopy

인자로 넘겨준 문자열을 `Clipboard`에 저장하는 함수입니다.

`Clipboard API`를 지원하지 않는 브라우저 환경일 경우, 또는 `HTTPS` 환경이 아닌 경우 등 `Clipboard API`를 사용할 수 없는 경우 실행되는 Fallback 함수입니다.

> Secure context: This feature is available only in [secure contexts (HTTPS)](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts), in some or all supporting browsers.
<br />

## Code
[🔗 실제 구현 코드 확인](https://github.com/modern-agile-team/modern-kit/blob/main/packages/utils/src/clipboard/clipboardFallbackTextCopy/index.ts)

## Interface
```ts title="typescript"
const clipboardFallbackTextCopy: (value: string) => void
```
## Usage
```ts title="typescript"
import { clipboardFallbackTextCopy } from '@modern-kit/utils';

clipboardFallbackTextCopy("복사 할 텍스트");
```

## Note
- [Clipboard API - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard)
- [Clipboard API Browser Compatibility - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard#browser_compatibility)
82 changes: 82 additions & 0 deletions docs/docs/utils/clipboard/clipboardImageCopy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# clipboardImageCopy

인자로 넘겨준 이미지 소스를 통해 이미지를 읽어 `Clipboard`에 저장하는 함수입니다.

`navigator.clipboard.write`는 일부 브라우저에서 지원하지 않습니다. `write`가 지원하지 않을 경우 [clipboardTextCopy](https://modern-agile-team.github.io/modern-kit/docs/utils/clipboard/clipboardTextCopy)가 호출 됩니다.

Chrome 환경에서 Clipboard API의 `write()``text/plain`, `text/html`, `image/png` 포맷만을 지원합니다. 따라서, `jp(e)g`, `webp`와 같은 이미지 포맷은 클립보드에 복사할 수 없습니다. 원활한 클립보드 복사를 위해서는 `png` 포맷의 이미지 사용을 권장합니다.
- [Chromium - ClipboardWriter::IsValidType](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/clipboard/clipboard_writer.cc;l=304;drc=e882b8e4a8272f65cb14c608d3d2bc4f0512aa20)
- 💡 **타 이미지 포맷을 png 포맷으로 변환하는 옵션을 추가 할 예정입니다.**

이미지 타입이 `image/svg+xml`의 경우에는 `소스 코드`를 복사해서 활용 할 수 있게 [clipboardTextCopy](https://modern-agile-team.github.io/modern-kit/docs/utils/clipboard/clipboardTextCopy)가 호출됩니다.

<br />

## Code
[🔗 실제 구현 코드 확인](https://github.com/modern-agile-team/modern-kit/blob/main/packages/utils/src/clipboard/clipboardImageCopy/index.ts)

## Interface
```ts title="typescript"
const clipboardImageCopy: (imgSrc: string) => Promise<void>
```
## Usage
### Default
```ts title="typescript"
import { clipboardImageCopy } from '@modern-kit/utils';

clipboardImageCopy("복사 할 이미지 src");
```

<br />

### image/svg+xml (1)
```ts title="typescript"
import { clipboardImageCopy } from '@modern-kit/utils';

clipboardImageCopy(
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0xMS41IC0xMC4yMzE3NCAyMyAyMC40NjM0OCI+CiAgPHRpdGxlPlJlYWN0IExvZ288L3RpdGxlPgogIDxjaXJjbGUgY3g9IjAiIGN5PSIwIiByPSIyLjA1IiBmaWxsPSIjNjFkYWZiIi8+CiAgPGcgc3Ryb2tlPSIjNjFkYWZiIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiPgogICAgPGVsbGlwc2Ugcng9IjExIiByeT0iNC4yIi8+CiAgICA8ZWxsaXBzZSByeD0iMTEiIHJ5PSI0LjIiIHRyYW5zZm9ybT0icm90YXRlKDYwKSIvPgogICAgPGVsbGlwc2Ugcng9IjExIiByeT0iNC4yIiB0cmFuc2Zvcm09InJvdGF0ZSgxMjApIi8+CiAgPC9nPgo8L3N2Zz4K'
);

/* Clipboard 복사 결과: text()
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-11.5 -10.23174 23 20.46348">
<title>React Logo</title>
<circle cx="0" cy="0" r="2.05" fill="#61dafb"/>
<g stroke="#61dafb" stroke-width="1" fill="none">
<ellipse rx="11" ry="4.2"/>
<ellipse rx="11" ry="4.2" transform="rotate(60)"/>
<ellipse rx="11" ry="4.2" transform="rotate(120)"/>
</g>
</svg>
*/
```

<br />

### image/svg+xml (2)
```ts title="React(typescript)"
import { clipboardImageCopy } from '@modern-kit/utils';
import svg from "./assets/react.svg";

clipboardImageCopy(svg);

/* Clipboard 복사 결과: text()
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-11.5 -10.23174 23 20.46348">
<title>React Logo</title>
<circle cx="0" cy="0" r="2.05" fill="#61dafb"/>
<g stroke="#61dafb" stroke-width="1" fill="none">
<ellipse rx="11" ry="4.2"/>
<ellipse rx="11" ry="4.2" transform="rotate(60)"/>
<ellipse rx="11" ry="4.2" transform="rotate(120)"/>
</g>
</svg>
*/
```

## Note
- [Clipboard API - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard)
- [Clipboard API Browser Compatibility - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard#browser_compatibility)
- [How to copy images(en) - web.dev](https://web.dev/patterns/clipboard/copy-images)
- [How to copy images(ko) - web.dev](https://web.dev/patterns/clipboard/copy-images?hl=ko)
- [Unblocking clipboard access (en) - web.dev](https://web.dev/articles/async-clipboard)
- [Unblocking clipboard access (ko) - web.dev](https://web.dev/articles/async-clipboard?hl=ko)
28 changes: 28 additions & 0 deletions docs/docs/utils/clipboard/clipboardTextCopy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# clipboardTextCopy

인자로 넘겨준 문자열을 `Clipboard`에 저장하는 함수입니다. `Clipboard API`를 지원하지 않는 브라우저 환경일 경우 [clipboardFallbackTextCopy](https://modern-agile-team.github.io/modern-kit/docs/utils/clipboard/clipboardFallbackTextCopy)가 호출됩니다.

<br />

## Code
[🔗 실제 구현 코드 확인](https://github.com/modern-agile-team/modern-kit/blob/main/packages/utils/src/clipboard/clipboardTextCopy/index.ts)

## Interface
```ts title="typescript"
const clipboardTextCopy: (value: string) => Promise<void>
```
## Usage
```ts title="typescript"
import { clipboardTextCopy } from '@modern-kit/utils';

clipboardTextCopy("복사 할 텍스트");
```

## Note
- [Clipboard API - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard)
- [Clipboard API Browser Compatibility - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard#browser_compatibility)
- [How to Copy Text(en) - web.dev](https://web.dev/patterns/clipboard/copy-text)
- [How to Copy Text(ko) - web.dev](https://web.dev/patterns/clipboard/copy-text?hl=ko)
- [Unblocking clipboard access (en) - web.dev](https://web.dev/articles/async-clipboard)
- [Unblocking clipboard access (ko) - web.dev](https://web.dev/articles/async-clipboard?hl=ko)
13 changes: 13 additions & 0 deletions packages/utils/src/clipboard/clipboardFallbackTextCopy/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const clipboardFallbackTextCopy = (value: string) => {
const textArea = document.createElement('textarea');

textArea.value = value;
textArea.style.opacity = '0';

document.body.appendChild(textArea);
textArea.focus();
textArea.select();

document.execCommand('copy');
document.body.removeChild(textArea);
};
45 changes: 45 additions & 0 deletions packages/utils/src/clipboard/clipboardImageCopy/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { isClient } from '../../device';
import { clipboardTextCopy } from '../clipboardTextCopy';

const fallbackImageCopy = async (res: Response) => {
const textData = await res.text();
await clipboardTextCopy(textData);
};

export const clipboardImageCopy = async (imgSrc: string) => {
if (!isClient()) {
console.error('Cannot be executed unless it is a browser environment.');
return;
}

try {
const hasNavigatorClipboard = 'clipboard' in window.navigator;
const response = await fetch(imgSrc);

if (!hasNavigatorClipboard) {
await fallbackImageCopy(response);
return;
}

const hasNavigatorClipboardWrite = 'write' in window.navigator.clipboard;

if (!hasNavigatorClipboardWrite) {
await fallbackImageCopy(response);
return;
}

const cloneResponse = response.clone();
const blobData = await cloneResponse.blob();

if (blobData.type === 'image/svg+xml') {
await fallbackImageCopy(response);
return;
}

await navigator.clipboard.write([
new ClipboardItem({ [blobData.type]: blobData }),
]);
} catch (err: any) {
console.error(`Copying to the clipboard failed. message: ${err.message}`);
}
};
21 changes: 21 additions & 0 deletions packages/utils/src/clipboard/clipboardTextCopy/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { isClient } from '../../device';
import { clipboardFallbackTextCopy } from '../clipboardFallbackTextCopy';

export const clipboardTextCopy = async (value: string) => {
if (!isClient()) {
console.error('Cannot be executed unless it is a client environment.');
return;
}

try {
const hasNavigatorClipboard = 'clipboard' in window.navigator;

if (!hasNavigatorClipboard) {
return clipboardFallbackTextCopy(value);
}

await navigator.clipboard.writeText(value);
} catch (err: any) {
console.error(`Copying to the clipboard failed. message: ${err.message}`);
}
};
5 changes: 5 additions & 0 deletions packages/utils/src/clipboard/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// ignore test

export * from './clipboardFallbackTextCopy';
export * from './clipboardImageCopy';
export * from './clipboardTextCopy';
1 change: 1 addition & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './clipboard';
export * from './common';
export * from './device';
export * from './formatter';
Expand Down
2 changes: 2 additions & 0 deletions packages/utils/src/storage/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// ignore test

export * from './clearStorage';
export * from './getStorageItem';
export * from './removeStorageItem';
Expand Down
2 changes: 1 addition & 1 deletion packages/utils/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default defineConfig({
globals: true,
coverage: {
provider: 'istanbul',
exclude: ['src/storage'],
exclude: ['src/storage', 'src/clipboard'],
},
},
});

0 comments on commit 65c6667

Please sign in to comment.