@mirawision/copily
Version:
A comprehensive clipboard manipulation library for TypeScript, providing functionalities for copying/pasting text, HTML, JSON, images, files, and smart content detection.
308 lines (307 loc) • 10 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.fallbackCopyText = exports.clearClipboard = exports.pasteFile = exports.copyFile = exports.pasteImage = exports.copyImage = exports.pasteJSON = exports.copyJSON = exports.pasteHTML = exports.copyHTML = exports.pasteText = exports.copyText = void 0;
const types_1 = require("./types");
const utils_1 = require("./utils");
/**
* Copy plain text to the system clipboard.
*
* Requires a secure context and Clipboard API support.
*
* @param text Plain text to copy.
* @returns Resolves when the text is written to the clipboard.
* @throws {ClipboardUnsupportedError} If the Clipboard API is unavailable.
* @throws {ClipboardPermissionError} If the write operation is denied or fails.
* @example
* await copyText('Hello world');
*/
async function copyText(text) {
if (!(0, utils_1.supportsClipboardAPI)()) {
throw new types_1.ClipboardUnsupportedError();
}
try {
await navigator.clipboard.writeText(text);
}
catch (error) {
throw new types_1.ClipboardPermissionError('Failed to copy text to clipboard');
}
}
exports.copyText = copyText;
/**
* Read plain text from the system clipboard.
*
* @returns Resolves to the clipboard text. Returns an empty string if the
* clipboard is empty or cannot be read.
* @throws {ClipboardUnsupportedError} If the Clipboard API is unavailable.
* @example
* const text = await pasteText();
*/
async function pasteText() {
if (!(0, utils_1.supportsClipboardAPI)()) {
throw new types_1.ClipboardUnsupportedError();
}
try {
return await navigator.clipboard.readText();
}
catch (error) {
// Return empty string if clipboard is empty or not accessible
return '';
}
}
exports.pasteText = pasteText;
/**
* Copy HTML content to the clipboard with a plain-text fallback.
*
* The HTML is sanitized before being written. A text/plain version (tags
* stripped) is also provided for consumers that only support plain text.
*
* @param html HTML string to copy.
* @returns Resolves when the HTML is written to the clipboard.
* @throws {ClipboardUnsupportedError} If the Clipboard API is unavailable.
* @throws {ClipboardPermissionError} If the write operation fails.
* @example
* await copyHTML('<strong>Hello</strong>');
*/
async function copyHTML(html) {
if (!(0, utils_1.supportsClipboardAPI)()) {
throw new types_1.ClipboardUnsupportedError();
}
const sanitizedHTML = (0, utils_1.sanitizeHTML)(html);
try {
const clipboardItem = new ClipboardItem({
'text/html': new Blob([sanitizedHTML], { type: 'text/html' }),
'text/plain': new Blob([html.replace(/<[^>]*>/g, '')], { type: 'text/plain' })
});
await navigator.clipboard.write([clipboardItem]);
}
catch (error) {
throw new types_1.ClipboardPermissionError('Failed to copy HTML to clipboard');
}
}
exports.copyHTML = copyHTML;
/**
* Read HTML content from the clipboard.
*
* Attempts to read 'text/html' first, falling back to plain text via
* {@link pasteText} when HTML is not present.
*
* @returns Resolves to the HTML string (or plain text). Returns an empty
* string on failure.
* @throws {ClipboardUnsupportedError} If the Clipboard API is unavailable.
*/
async function pasteHTML() {
if (!(0, utils_1.supportsClipboardAPI)()) {
throw new types_1.ClipboardUnsupportedError();
}
try {
const items = await navigator.clipboard.read();
for (const item of items) {
if (item.types.includes('text/html')) {
const htmlBlob = await item.getType('text/html');
return await htmlBlob.text();
}
}
// Fallback to plain text
return await pasteText();
}
catch (error) {
return '';
}
}
exports.pasteHTML = pasteHTML;
/**
* Copy a JSON-serializable object to the clipboard as formatted text.
*
* @param obj Object to stringify and copy.
* @returns Resolves when the JSON string is written to the clipboard.
* @example
* await copyJSON({ id: 1 });
*/
async function copyJSON(obj) {
const jsonString = JSON.stringify(obj, null, 2);
await copyText(jsonString);
}
exports.copyJSON = copyJSON;
/**
* Parse the clipboard content as JSON.
*
* @returns Resolves to the parsed object.
* @throws {ClipboardFormatError} If the clipboard content is not valid JSON
* or cannot be parsed.
* @example
* const data = await pasteJSON();
*/
async function pasteJSON() {
const text = await pasteText();
if (!(0, utils_1.isJSON)(text)) {
throw new types_1.ClipboardFormatError('Clipboard content is not valid JSON');
}
try {
return JSON.parse(text);
}
catch (error) {
throw new types_1.ClipboardFormatError('Failed to parse clipboard content as JSON');
}
}
exports.pasteJSON = pasteJSON;
/**
* Copy an image to the clipboard.
*
* Accepts a Blob, an HTMLImageElement, or a URL string which will be fetched
* and converted to a Blob.
*
* @param image Image Blob, HTMLImageElement, or URL string.
* @returns Resolves when the image is written to the clipboard.
* @throws {ClipboardUnsupportedError} If the Clipboard API is unavailable.
* @throws {ClipboardPermissionError} If the write operation fails.
*/
async function copyImage(image) {
if (!(0, utils_1.supportsClipboardAPI)()) {
throw new types_1.ClipboardUnsupportedError();
}
let imageBlob;
if (image instanceof Blob) {
imageBlob = image;
}
else {
imageBlob = await (0, utils_1.imageToBlob)(image);
}
try {
const clipboardItem = new ClipboardItem({
[imageBlob.type]: imageBlob
});
await navigator.clipboard.write([clipboardItem]);
}
catch (error) {
throw new types_1.ClipboardPermissionError('Failed to copy image to clipboard');
}
}
exports.copyImage = copyImage;
/**
* Read an image from the clipboard.
*
* @returns Resolves to an image Blob, or null if no image is present.
* @throws {ClipboardUnsupportedError} If the Clipboard API is unavailable.
*/
async function pasteImage() {
if (!(0, utils_1.supportsClipboardAPI)()) {
throw new types_1.ClipboardUnsupportedError();
}
try {
const items = await navigator.clipboard.read();
for (const item of items) {
for (const type of item.types) {
if (type.startsWith('image/')) {
const imageBlob = await item.getType(type);
return imageBlob;
}
}
}
return null;
}
catch (error) {
return null;
}
}
exports.pasteImage = pasteImage;
/**
* Copy a file to the clipboard.
*
* @param file File or Blob to copy.
* @returns Resolves when the file is written to the clipboard.
* @throws {ClipboardUnsupportedError} If the Clipboard API is unavailable.
* @throws {ClipboardPermissionError} If the write operation fails.
*/
async function copyFile(file) {
if (!(0, utils_1.supportsClipboardAPI)()) {
throw new types_1.ClipboardUnsupportedError();
}
try {
const clipboardItem = new ClipboardItem({
[file.type]: file
});
await navigator.clipboard.write([clipboardItem]);
}
catch (error) {
throw new types_1.ClipboardPermissionError('Failed to copy file to clipboard');
}
}
exports.copyFile = copyFile;
/**
* Read a non-image file from the clipboard.
*
* The first non-image item is returned as a File, or null if not found.
*
* @returns Resolves to a File instance or null if none is present.
* @throws {ClipboardUnsupportedError} If the Clipboard API is unavailable.
*/
async function pasteFile() {
if (!(0, utils_1.supportsClipboardAPI)()) {
throw new types_1.ClipboardUnsupportedError();
}
try {
const items = await navigator.clipboard.read();
for (const item of items) {
for (const type of item.types) {
if (!type.startsWith('image/')) {
const fileBlob = await item.getType(type);
return new File([fileBlob], 'clipboard-file', { type });
}
}
}
return null;
}
catch (error) {
return null;
}
}
exports.pasteFile = pasteFile;
/**
* Clear clipboard content by writing an empty text payload.
*
* @returns Resolves when the clipboard has been cleared.
* @throws {ClipboardUnsupportedError} If the Clipboard API is unavailable.
* @throws {ClipboardPermissionError} If the write operation fails.
*/
async function clearClipboard() {
if (!(0, utils_1.supportsClipboardAPI)()) {
throw new types_1.ClipboardUnsupportedError();
}
try {
// Write empty content to clear clipboard
const emptyItem = new ClipboardItem({
'text/plain': new Blob([''], { type: 'text/plain' })
});
await navigator.clipboard.write([emptyItem]);
}
catch (error) {
throw new types_1.ClipboardPermissionError('Failed to clear clipboard');
}
}
exports.clearClipboard = clearClipboard;
/**
* Legacy fallback to copy text using `document.execCommand('copy')`.
*
* This is synchronous and intended as a last-resort for environments without
* Clipboard API support.
*
* @param text Plain text to copy.
* @example
* fallbackCopyText('hello');
*/
function fallbackCopyText(text) {
const textArea = (0, utils_1.createTemporaryElement)();
try {
textArea.value = text;
textArea.select();
textArea.setSelectionRange(0, 99999); // For mobile devices
const successful = document.execCommand('copy');
if (!successful) {
throw new Error('execCommand copy failed');
}
}
finally {
(0, utils_1.removeTemporaryElement)(textArea);
}
}
exports.fallbackCopyText = fallbackCopyText;