From c53c034a2000c98be259fef2010998378aba2d31 Mon Sep 17 00:00:00 2001 From: Aran-Fey Date: Thu, 30 May 2024 09:57:13 +0200 Subject: [PATCH] reorganize clipboard code --- frontend/code/components/codeBlock.ts | 4 +- frontend/code/components/scrollTarget.ts | 4 +- frontend/code/rpc.ts | 4 +- frontend/code/rpcFunctions.ts | 31 ------------ frontend/code/utils.ts | 64 +++++++++++++++++------- rio/errors.py | 13 +++++ rio/session.py | 37 ++++++-------- 7 files changed, 79 insertions(+), 78 deletions(-) diff --git a/frontend/code/components/codeBlock.ts b/frontend/code/components/codeBlock.ts index ae787c12..c3e543df 100644 --- a/frontend/code/components/codeBlock.ts +++ b/frontend/code/components/codeBlock.ts @@ -9,7 +9,7 @@ import { Language } from 'highlight.js'; import { LayoutContext } from '../layouting'; import { getElementHeight, getElementWidth } from '../layoutHelpers'; -import { copyToClipboard, firstDefined } from '../utils'; +import { setClipboard, firstDefined } from '../utils'; import { applyIcon } from '../designApplication'; export type CodeBlockState = ComponentState & { @@ -109,7 +109,7 @@ export function convertDivToCodeBlock( copyButton.addEventListener('click', (event) => { const codeToCopy = (preElement as HTMLPreElement).textContent ?? ''; - copyToClipboard(codeToCopy); + setClipboard(codeToCopy); copyButton.title = 'Copied!'; applyIcon( diff --git a/frontend/code/components/scrollTarget.ts b/frontend/code/components/scrollTarget.ts index c338e63b..eb045663 100644 --- a/frontend/code/components/scrollTarget.ts +++ b/frontend/code/components/scrollTarget.ts @@ -5,7 +5,7 @@ import { import { ComponentId } from '../dataModels'; import { getTextDimensions } from '../layoutHelpers'; import { LayoutContext } from '../layouting'; -import { copyToClipboard } from '../utils'; +import { setClipboard } from '../utils'; import { ComponentBase, ComponentState } from './componentBase'; export type ScrollTargetState = ComponentState & { @@ -106,7 +106,7 @@ export class ScrollTargetComponent extends ComponentBase { let url = new URL(window.location.href); url.hash = this.state.id; - copyToClipboard(url.toString()); + setClipboard(url.toString()); } updateNaturalWidth(ctx: LayoutContext): void { diff --git a/frontend/code/rpc.ts b/frontend/code/rpc.ts index 9c0942e2..0ad8a15e 100644 --- a/frontend/code/rpc.ts +++ b/frontend/code/rpc.ts @@ -6,10 +6,8 @@ import { registerFont, closeSession, setTitle, - setClipboard, - getClipboard, - ClipboardError, } from './rpcFunctions'; +import { setClipboard, getClipboard, ClipboardError } from './utils'; import { AsyncQueue, commitCss } from './utils'; let websocket: WebSocket | null = null; diff --git a/frontend/code/rpcFunctions.ts b/frontend/code/rpcFunctions.ts index e30536f9..7708cb56 100644 --- a/frontend/code/rpcFunctions.ts +++ b/frontend/code/rpcFunctions.ts @@ -132,34 +132,3 @@ export function setTitle(title: string): void { export function closeSession(): void { window.close(); // TODO: What if the browser doesn't allow this? } - -export class ClipboardError extends Error { - constructor(message: string) { - super(message); - this.name = this.constructor.name; - } -} - -export async function setClipboard(text: string): Promise { - if (!navigator.clipboard) { - throw new ClipboardError('Clipboard API is not available'); - } - try { - await navigator.clipboard.writeText(text); - } catch (error) { - console.warn(`Failed to set clipboard content: ${error}`); - throw new ClipboardError(`Failed to set clipboard content: ${error}`); - } -} - -export async function getClipboard(): Promise { - if (!navigator.clipboard) { - throw new ClipboardError('Clipboard API is not available'); - } - try { - return await navigator.clipboard.readText(); - } catch (error) { - console.warn(`Failed to get clipboard content: ${error}`); - throw new ClipboardError(`Failed to get clipboard content: ${error}`); - } -} diff --git a/frontend/code/utils.ts b/frontend/code/utils.ts index b1295c97..133772a6 100644 --- a/frontend/code/utils.ts +++ b/frontend/code/utils.ts @@ -120,28 +120,56 @@ export class TimeoutError extends Error { } } -/// Copies the given text to the clipboard -export function copyToClipboard(text: string): void { - if (navigator.clipboard) { - navigator.clipboard.writeText(text).catch((error) => { - console.warn( - `Failed to set clipboard content using navigator.clipboard: ${error}` - ); - fallbackCopyToClipboard(text); - }); - } else { - fallbackCopyToClipboard(text); +export class ClipboardError extends Error { + constructor(message: string) { + super(message); + this.name = this.constructor.name; } } -function fallbackCopyToClipboard(text: string): void { - const textArea = document.createElement('textarea'); - textArea.value = text; +export async function setClipboard(text: string): Promise { + if (navigator.clipboard) { + try { + await navigator.clipboard.writeText(text); + return; + } catch (error) { + console.warn(`Failed to set clipboard content: ${error}`); + throw new ClipboardError( + `Failed to set clipboard content: ${error}` + ); + } + } - document.body.appendChild(textArea); - textArea.select(); - document.execCommand('copy'); - document.body.removeChild(textArea); + // Fallback in case `navigator.clipboard` isn't available or didn't work + if (document.execCommand) { + const textArea = document.createElement('textarea'); + textArea.value = text; + + document.body.appendChild(textArea); + textArea.select(); + document.execCommand('copy'); + document.body.removeChild(textArea); + } + + console.warn('Failed to set clipboard content: No clipboard API available'); + throw new ClipboardError( + 'Failed to set clipboard content: No clipboard API available' + ); +} + +export async function getClipboard(): Promise { + if (navigator.clipboard) { + try { + return await navigator.clipboard.readText(); + } catch (error) { + console.warn(`Failed to get clipboard content: ${error}`); + throw new ClipboardError( + `Failed to get clipboard content: ${error}` + ); + } + } + + throw new ClipboardError('Clipboard API is not available'); } /// Checks if there's an #url-fragment, and if so, scrolls the corresponding diff --git a/rio/errors.py b/rio/errors.py index 49681e97..23ca4559 100644 --- a/rio/errors.py +++ b/rio/errors.py @@ -33,3 +33,16 @@ class NavigationFailed(Exception): will simply display their fallback in that case. Thus this exception will not be raised in that case. """ + + +class ClipboardError(Exception): + """ + Exception raised for errors related to clipboard operations. + """ + + def __init__(self, message: str): + super().__init__(message) + + @property + def message(self) -> str: + return self.args[0] diff --git a/rio/session.py b/rio/session.py index 14599840..61e193e1 100644 --- a/rio/session.py +++ b/rio/session.py @@ -67,19 +67,6 @@ class WontSerialize(Exception): pass -class ClipboardError(Exception): - """ - Exception raised for errors related to clipboard operations. - """ - - def __init__(self, message: str): - super().__init__(message) - - @property - def message(self) -> str: - return self.args[0] - - async def dummy_send_message(message: Jsonable) -> None: raise NotImplementedError() # pragma: no cover @@ -2483,26 +2470,32 @@ a.remove(); async def set_clipboard(self, text: str) -> None: """ - Set the client's clipboard to the given text. + Copies the given text to the client's clipboard. ## Parameters - `text`: The text to set on the clipboard. + `text`: The text to copy to the clipboard. + + ## Raises + + `ClipboardError`: If the user's browser doesn't allow or support + clipboard operations. """ try: await self._remote_set_clipboard(text) - except Exception as e: - raise ClipboardError(f"Failed to set clipboard content: {str(e)}") + except unicall.RpcError as e: + raise errors.ClipboardError(e.message) from None async def get_clipboard(self) -> str: """ - Get the current text from the client's clipboard. + Gets the current text from the client's clipboard. - ## Returns + ## Raises - The text currently on the clipboard. + `ClipboardError`: If the user's browser doesn't allow or support + clipboard operations. """ try: return await self._remote_get_clipboard() - except Exception as e: - raise ClipboardError(f"Failed to get clipboard content: {str(e)}") + except unicall.RpcError as e: + raise errors.ClipboardError(e.message) from None