jg-clipboard 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/API.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # <a id="api-reference"></a>jg-clipboard API reference
2
2
 
3
- Every export of `jg-clipboard@0.1.1`, 10 in all, with its signature, its errors, and an example. It's generated from the package's TSDoc.
3
+ Every export of `jg-clipboard@0.2.0`, 10 in all, with its signature, its errors, and an example. It's generated from the package's TSDoc.
4
4
 
5
5
  - Functions: [`copyHtml`](#api-copyHtml), [`copyImage`](#api-copyImage), [`copyText`](#api-copyText), [`getClipboardSupport`](#api-getClipboardSupport), [`readClipboard`](#api-readClipboard), [`readText`](#api-readText)
6
6
  - Constants: [`ERRORS`](#api-ERRORS)
@@ -153,13 +153,13 @@ if (support.copyImage) {
153
153
 
154
154
  *Function*
155
155
 
156
- Reads the clipboard's text, HTML, and image with one `read()` call, so the person pasting sees one prompt at most. Call it from a click or key press handler, which Safari requires.
156
+ Reads the clipboard's text, HTML, and image with one `read()` call, so the person pasting sees one prompt at most. Call it from a click or key press handler, which Safari requires. An image the browser can't read never hides the text or HTML copied with it, such as the picture of spreadsheet cells that comes with their text: the image is then null.
157
157
 
158
158
  ```ts
159
159
  readClipboard(): Promise<IClipboardContent>
160
160
  ```
161
161
 
162
- **Returns** `Promise<IClipboardContent>`: A promise that resolves to each kind of content, with null for the kinds the clipboard doesn't hold. The image is PNG.
162
+ **Returns** `Promise<IClipboardContent>`: A promise that resolves to each kind of content, with null for the kinds the clipboard doesn't hold or the image it can't read. The image is PNG.
163
163
 
164
164
  **Throws**
165
165
 
@@ -167,8 +167,8 @@ readClipboard(): Promise<IClipboardContent>
167
167
  - `CLIPBOARD_NOT_FOCUSED`: The page must have focus to use the clipboard.
168
168
  - `CLIPBOARD_NO_USER_GESTURE`: The browser only allows this right after a click or a key press.
169
169
  - `CLIPBOARD_PERMISSION_DENIED`: The permission to use the clipboard was denied.
170
- - `CLIPBOARD_INVALID_IMAGE`: The image couldn't be decoded, so it can't be copied as PNG.
171
- - `CLIPBOARD_READ_FAILED`: The clipboard couldn't be read.
170
+ - `CLIPBOARD_INVALID_IMAGE`: The clipboard holds only an image, which couldn't be decoded as PNG.
171
+ - `CLIPBOARD_READ_FAILED`: The clipboard couldn't be read, or holds only an image it couldn't read.
172
172
 
173
173
  **Example**
174
174
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Changed
6
+
7
+ - `readClipboard` returns the text and HTML when the clipboard's image can't be read, with `image: null`, instead of failing as a whole. It still throws `CLIPBOARD_INVALID_IMAGE`, or `CLIPBOARD_READ_FAILED`, when the image is all the clipboard holds.
8
+
3
9
  ## 0.1.1
4
10
 
5
11
  ### Added
package/README.md CHANGED
@@ -45,7 +45,7 @@ await copyHtml('<strong>Order #42</strong> shipped', { text: 'Order #42 shipped'
45
45
  await copyImage(fetch('/chart.jpg').then((response) => response.blob()));
46
46
  ```
47
47
 
48
- Read from a paste button. `readClipboard` reads the text, HTML, and image with one call, so the person pasting sees one prompt at most:
48
+ Read from a paste button. `readClipboard` reads the text, HTML, and image with one call, so the person pasting sees one prompt at most. An image the browser can't read comes back as `null` when text or HTML was copied with it, so the text still pastes:
49
49
 
50
50
  ```ts
51
51
  import { readClipboard, readText } from 'jg-clipboard';
@@ -75,7 +75,7 @@ Every failure is an `Exception` with one of these codes, and the browser's error
75
75
  | `CLIPBOARD_NOT_FOCUSED` | The browser refused because the page doesn't have focus |
76
76
  | `CLIPBOARD_NO_USER_GESTURE` | The browser refused because the call didn't follow a click or a key press |
77
77
  | `CLIPBOARD_PERMISSION_DENIED` | The browser refused for any other reason, such as a denied or dismissed permission; `data.permission` holds the Permissions API's state, or `null` |
78
- | `CLIPBOARD_INVALID_IMAGE` | The image can't be decoded, so it can't be copied as PNG |
78
+ | `CLIPBOARD_INVALID_IMAGE` | The image can't be decoded as PNG: an image to copy, or one `readClipboard` read without text or HTML beside it |
79
79
  | `CLIPBOARD_WRITE_FAILED` | A copy failed otherwise, such as content whose promise rejected |
80
80
  | `CLIPBOARD_READ_FAILED` | A read failed otherwise |
81
81
 
package/RECIPES.md CHANGED
@@ -104,4 +104,4 @@ pasteButton.addEventListener('click', async () => {
104
104
  });
105
105
  ```
106
106
 
107
- Safari and Firefox show a paste prompt on every read, and Chromium asks for the clipboard-read permission once, so read only when the person clicks. Which content wins when the clipboard holds both depends on the app: copied spreadsheet cells can come with a picture of the cells, so a field for tables would check `text` first. `CLIPBOARD_INVALID_IMAGE` means the clipboard held an image the browser can't decode, and `readClipboard` then fails as a whole, even when there was text too.
107
+ Safari and Firefox show a paste prompt on every read, and Chromium asks for the clipboard-read permission once, so read only when the person clicks. Which content wins when the clipboard holds both depends on the app: copied spreadsheet cells can come with a picture of the cells, so a field for tables would check `text` first. `CLIPBOARD_INVALID_IMAGE` means the clipboard held only an image the browser can't decode. When text or HTML came with it, `readClipboard` returns them with `image: null` instead, so the paste falls through to the text.
@@ -20,9 +20,11 @@ import { type IClipboardContent } from '../shared/index.js';
20
20
  export declare const readText: () => Promise<string>;
21
21
  /**
22
22
  * Reads the clipboard's text, HTML, and image with one `read()` call, so the person pasting sees
23
- * one prompt at most. Call it from a click or key press handler, which Safari requires.
23
+ * one prompt at most. Call it from a click or key press handler, which Safari requires. An image
24
+ * the browser can't read never hides the text or HTML copied with it, such as the picture of
25
+ * spreadsheet cells that comes with their text: the image is then null.
24
26
  * @returns A promise that resolves to each kind of content, with null for the kinds the clipboard
25
- * doesn't hold. The image is PNG.
27
+ * doesn't hold or the image it can't read. The image is PNG.
26
28
  * @example
27
29
  * import { readClipboard } from 'jg-clipboard';
28
30
  *
@@ -37,7 +39,7 @@ export declare const readText: () => Promise<string>;
37
39
  * - CLIPBOARD_NOT_FOCUSED: The page must have focus to use the clipboard.
38
40
  * - CLIPBOARD_NO_USER_GESTURE: The browser only allows this right after a click or a key press.
39
41
  * - CLIPBOARD_PERMISSION_DENIED: The permission to use the clipboard was denied.
40
- * - CLIPBOARD_INVALID_IMAGE: The image couldn't be decoded, so it can't be copied as PNG.
41
- * - CLIPBOARD_READ_FAILED: The clipboard couldn't be read.
42
+ * - CLIPBOARD_INVALID_IMAGE: The clipboard holds only an image, which couldn't be decoded as PNG.
43
+ * - CLIPBOARD_READ_FAILED: The clipboard couldn't be read, or holds only an image it couldn't read.
42
44
  */
43
45
  export declare const readClipboard: () => Promise<IClipboardContent>;
package/dist/read/read.js CHANGED
@@ -55,9 +55,11 @@ const readImage = async (items) => {
55
55
  };
56
56
  /**
57
57
  * Reads the clipboard's text, HTML, and image with one `read()` call, so the person pasting sees
58
- * one prompt at most. Call it from a click or key press handler, which Safari requires.
58
+ * one prompt at most. Call it from a click or key press handler, which Safari requires. An image
59
+ * the browser can't read never hides the text or HTML copied with it, such as the picture of
60
+ * spreadsheet cells that comes with their text: the image is then null.
59
61
  * @returns A promise that resolves to each kind of content, with null for the kinds the clipboard
60
- * doesn't hold. The image is PNG.
62
+ * doesn't hold or the image it can't read. The image is PNG.
61
63
  * @example
62
64
  * import { readClipboard } from 'jg-clipboard';
63
65
  *
@@ -72,8 +74,8 @@ const readImage = async (items) => {
72
74
  * - CLIPBOARD_NOT_FOCUSED: The page must have focus to use the clipboard.
73
75
  * - CLIPBOARD_NO_USER_GESTURE: The browser only allows this right after a click or a key press.
74
76
  * - CLIPBOARD_PERMISSION_DENIED: The permission to use the clipboard was denied.
75
- * - CLIPBOARD_INVALID_IMAGE: The image couldn't be decoded, so it can't be copied as PNG.
76
- * - CLIPBOARD_READ_FAILED: The clipboard couldn't be read.
77
+ * - CLIPBOARD_INVALID_IMAGE: The clipboard holds only an image, which couldn't be decoded as PNG.
78
+ * - CLIPBOARD_READ_FAILED: The clipboard couldn't be read, or holds only an image it couldn't read.
77
79
  */
78
80
  export const readClipboard = async () => {
79
81
  const clipboard = getSecureClipboard();
@@ -92,9 +94,16 @@ export const readClipboard = async () => {
92
94
  const [text, html, image] = await Promise.all([
93
95
  readType(items, 'text/plain'),
94
96
  readType(items, 'text/html'),
95
- readImage(items),
97
+ // a failed image is kept as its error, so the text copied with it can still be pasted
98
+ readImage(items).then((png) => ({ png }), (failure) => ({ failure })),
96
99
  ]);
97
- return { text, html, image };
100
+ if (!('failure' in image)) {
101
+ return { text, html, image: image.png };
102
+ }
103
+ if (!text && !html) {
104
+ throw image.failure;
105
+ }
106
+ return { text, html, image: null };
98
107
  }
99
108
  catch (error) {
100
109
  throw isException(error)
@@ -1 +1 @@
1
- {"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/read/read.ts"],"names":[],"mappings":"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","sourcesContent":["import { Exception, isException } from 'jg-exception';\n\nimport { toPng } from '../copy/image.js';\nimport {\n ERRORS,\n getSecureClipboard,\n toClipboardException,\n unsupported,\n type IClipboardContent,\n} from '../shared/index.js';\n\n/**\n * Reads the text on the clipboard. Browsers ask before a page reads it: Safari and Firefox show a\n * paste prompt, and Chromium asks for the clipboard-read permission once. Call it from a click or\n * key press handler, which Safari requires.\n * @returns A promise that resolves to the text, or `''` when the clipboard holds none.\n * @example\n * import { readText } from 'jg-clipboard';\n *\n * pasteButton.addEventListener('click', async () => {\n * input.value = await readText();\n * });\n * @throws\n * - CLIPBOARD_UNSUPPORTED: The clipboard can't read text here.\n * - CLIPBOARD_NOT_FOCUSED: The page must have focus to use the clipboard.\n * - CLIPBOARD_NO_USER_GESTURE: The browser only allows this right after a click or a key press.\n * - CLIPBOARD_PERMISSION_DENIED: The permission to use the clipboard was denied.\n * - CLIPBOARD_READ_FAILED: The clipboard couldn't be read.\n */\nexport const readText = async (): Promise<string> => {\n const clipboard = getSecureClipboard();\n const read = clipboard?.readText;\n if (typeof read !== 'function') {\n throw unsupported('read text');\n }\n try {\n return await read.call(clipboard);\n } catch (error) {\n throw await toClipboardException(error, 'read');\n }\n};\n\n/**\n * Reads one type from the first item that has it.\n * @param items The clipboard's items.\n * @param type The MIME type.\n * @returns A promise that resolves to the item's text, or null when no item has the type.\n */\nconst readType = async (items: ClipboardItems, type: string): Promise<string | null> => {\n const item = items.find(({ types }) => types.includes(type));\n return item === undefined ? null : (await item.getType(type)).text();\n};\n\n/**\n * Reads the first image, preferring PNG, as PNG.\n * @param items The clipboard's items.\n * @returns A promise that resolves to the PNG, or null when no item holds an image.\n */\nconst readImage = async (items: ClipboardItems): Promise<Blob | null> => {\n const types = items.flatMap((item) => item.types.map((type) => ({ item, type })));\n const image =\n types.find(({ type }) => type === 'image/png') ??\n types.find(({ type }) => type.startsWith('image/'));\n return image === undefined ? null : toPng(await image.item.getType(image.type));\n};\n\n/**\n * Reads the clipboard's text, HTML, and image with one `read()` call, so the person pasting sees\n * one prompt at most. Call it from a click or key press handler, which Safari requires.\n * @returns A promise that resolves to each kind of content, with null for the kinds the clipboard\n * doesn't hold. The image is PNG.\n * @example\n * import { readClipboard } from 'jg-clipboard';\n *\n * pasteButton.addEventListener('click', async () => {\n * const { html, text, image } = await readClipboard();\n * if (image) {\n * preview.src = URL.createObjectURL(image);\n * }\n * });\n * @throws\n * - CLIPBOARD_UNSUPPORTED: The clipboard can't read here.\n * - CLIPBOARD_NOT_FOCUSED: The page must have focus to use the clipboard.\n * - CLIPBOARD_NO_USER_GESTURE: The browser only allows this right after a click or a key press.\n * - CLIPBOARD_PERMISSION_DENIED: The permission to use the clipboard was denied.\n * - CLIPBOARD_INVALID_IMAGE: The image couldn't be decoded, so it can't be copied as PNG.\n * - CLIPBOARD_READ_FAILED: The clipboard couldn't be read.\n */\nexport const readClipboard = async (): Promise<IClipboardContent> => {\n const clipboard = getSecureClipboard();\n const read = clipboard?.read;\n if (typeof read !== 'function') {\n throw unsupported('read');\n }\n let items: ClipboardItems;\n try {\n items = await read.call(clipboard);\n } catch (error) {\n throw await toClipboardException(error, 'read');\n }\n try {\n const [text, html, image] = await Promise.all([\n readType(items, 'text/plain'),\n readType(items, 'text/html'),\n readImage(items),\n ]);\n return { text, html, image };\n } catch (error) {\n throw isException(error)\n ? error\n : new Exception(\"The clipboard's content couldn't be read.\", {\n code: ERRORS.CLIPBOARD_READ_FAILED,\n cause: error,\n });\n }\n};\n"]}
1
+ {"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/read/read.ts"],"names":[],"mappings":"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","sourcesContent":["import { Exception, isException } from 'jg-exception';\n\nimport { toPng } from '../copy/image.js';\nimport {\n ERRORS,\n getSecureClipboard,\n toClipboardException,\n unsupported,\n type IClipboardContent,\n} from '../shared/index.js';\n\n/**\n * Reads the text on the clipboard. Browsers ask before a page reads it: Safari and Firefox show a\n * paste prompt, and Chromium asks for the clipboard-read permission once. Call it from a click or\n * key press handler, which Safari requires.\n * @returns A promise that resolves to the text, or `''` when the clipboard holds none.\n * @example\n * import { readText } from 'jg-clipboard';\n *\n * pasteButton.addEventListener('click', async () => {\n * input.value = await readText();\n * });\n * @throws\n * - CLIPBOARD_UNSUPPORTED: The clipboard can't read text here.\n * - CLIPBOARD_NOT_FOCUSED: The page must have focus to use the clipboard.\n * - CLIPBOARD_NO_USER_GESTURE: The browser only allows this right after a click or a key press.\n * - CLIPBOARD_PERMISSION_DENIED: The permission to use the clipboard was denied.\n * - CLIPBOARD_READ_FAILED: The clipboard couldn't be read.\n */\nexport const readText = async (): Promise<string> => {\n const clipboard = getSecureClipboard();\n const read = clipboard?.readText;\n if (typeof read !== 'function') {\n throw unsupported('read text');\n }\n try {\n return await read.call(clipboard);\n } catch (error) {\n throw await toClipboardException(error, 'read');\n }\n};\n\n/**\n * Reads one type from the first item that has it.\n * @param items The clipboard's items.\n * @param type The MIME type.\n * @returns A promise that resolves to the item's text, or null when no item has the type.\n */\nconst readType = async (items: ClipboardItems, type: string): Promise<string | null> => {\n const item = items.find(({ types }) => types.includes(type));\n return item === undefined ? null : (await item.getType(type)).text();\n};\n\n/**\n * Reads the first image, preferring PNG, as PNG.\n * @param items The clipboard's items.\n * @returns A promise that resolves to the PNG, or null when no item holds an image.\n */\nconst readImage = async (items: ClipboardItems): Promise<Blob | null> => {\n const types = items.flatMap((item) => item.types.map((type) => ({ item, type })));\n const image =\n types.find(({ type }) => type === 'image/png') ??\n types.find(({ type }) => type.startsWith('image/'));\n return image === undefined ? null : toPng(await image.item.getType(image.type));\n};\n\n/**\n * Reads the clipboard's text, HTML, and image with one `read()` call, so the person pasting sees\n * one prompt at most. Call it from a click or key press handler, which Safari requires. An image\n * the browser can't read never hides the text or HTML copied with it, such as the picture of\n * spreadsheet cells that comes with their text: the image is then null.\n * @returns A promise that resolves to each kind of content, with null for the kinds the clipboard\n * doesn't hold or the image it can't read. The image is PNG.\n * @example\n * import { readClipboard } from 'jg-clipboard';\n *\n * pasteButton.addEventListener('click', async () => {\n * const { html, text, image } = await readClipboard();\n * if (image) {\n * preview.src = URL.createObjectURL(image);\n * }\n * });\n * @throws\n * - CLIPBOARD_UNSUPPORTED: The clipboard can't read here.\n * - CLIPBOARD_NOT_FOCUSED: The page must have focus to use the clipboard.\n * - CLIPBOARD_NO_USER_GESTURE: The browser only allows this right after a click or a key press.\n * - CLIPBOARD_PERMISSION_DENIED: The permission to use the clipboard was denied.\n * - CLIPBOARD_INVALID_IMAGE: The clipboard holds only an image, which couldn't be decoded as PNG.\n * - CLIPBOARD_READ_FAILED: The clipboard couldn't be read, or holds only an image it couldn't read.\n */\nexport const readClipboard = async (): Promise<IClipboardContent> => {\n const clipboard = getSecureClipboard();\n const read = clipboard?.read;\n if (typeof read !== 'function') {\n throw unsupported('read');\n }\n let items: ClipboardItems;\n try {\n items = await read.call(clipboard);\n } catch (error) {\n throw await toClipboardException(error, 'read');\n }\n try {\n const [text, html, image] = await Promise.all([\n readType(items, 'text/plain'),\n readType(items, 'text/html'),\n // a failed image is kept as its error, so the text copied with it can still be pasted\n readImage(items).then(\n (png) => ({ png }),\n (failure: unknown) => ({ failure }),\n ),\n ]);\n if (!('failure' in image)) {\n return { text, html, image: image.png };\n }\n if (!text && !html) {\n throw image.failure;\n }\n return { text, html, image: null };\n } catch (error) {\n throw isException(error)\n ? error\n : new Exception(\"The clipboard's content couldn't be read.\", {\n code: ERRORS.CLIPBOARD_READ_FAILED,\n cause: error,\n });\n }\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jg-clipboard",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Copy and read text, HTML, and images on the browser clipboard, with support detection and coded errors that say why the browser refused.",
5
5
  "author": "Jesus Graterol",
6
6
  "license": "MIT",