1+ // @ts -nocheck
12// clipboardUtils.js
23
3- import { DOMSerializer , DOMParser } from 'prosemirror-model' ;
4+ import { DOMParser } from 'prosemirror-model' ;
45
56/**
6- * Serializes the current selection in the editor state to HTML and plain text for clipboard use.
7- * @param {EditorState } state - The ProseMirror editor state containing the current selection.
8- * @returns {{ htmlString: string, text: string } } An object with the HTML string and plain text of the selection.
7+ * Checks if clipboard read permission is granted and handles permission prompts.
8+ * Returns true if clipboard-read permission is granted. If state is "prompt" it will
9+ * proactively trigger a readText() call which will surface the browser permission
10+ * dialog to the user. Falls back gracefully in older browsers that lack the
11+ * Permissions API.
12+ * @returns {Promise<boolean> } Whether clipboard read permission is granted
913 */
10- export function serializeSelectionToClipboard ( state ) {
11- const { from , to } = state . selection ;
12- const slice = state . selection . content ( ) ;
13- const htmlContainer = document . createElement ( 'div' ) ;
14- htmlContainer . appendChild ( DOMSerializer . fromSchema ( state . schema ) . serializeFragment ( slice . content ) ) ;
15- const htmlString = htmlContainer . innerHTML ;
16- const text = state . doc . textBetween ( from , to ) ;
17- return { htmlString , text } ;
18- }
14+ export async function ensureClipboardPermission ( ) {
15+ if ( typeof navigator === 'undefined' || ! navigator . clipboard ) {
16+ return false ;
17+ }
18+
19+ // Some older browsers do not expose navigator.permissions – assume granted
20+ if ( ! navigator . permissions || typeof navigator . permissions . query !== 'function' ) {
21+ return true ;
22+ }
1923
20- /**
21- * Writes HTML and plain text data to the system clipboard.
22- * Uses the Clipboard API if available, otherwise falls back to plain text.
23- * @param {{ htmlString: string, text: string } } param0 - The HTML and plain text to write to the clipboard.
24- * @returns {Promise<void> } A promise that resolves when the clipboard write is complete.
25- */
26- export async function writeToClipboard ( { htmlString, text } ) {
2724 try {
28- if ( navigator . clipboard && window . ClipboardItem ) {
29- const clipboardItem = new window . ClipboardItem ( {
30- 'text/html' : new Blob ( [ htmlString ] , { type : 'text/html' } ) ,
31- 'text/plain' : new Blob ( [ text ] , { type : 'text/plain' } ) ,
32- } ) ;
33- await navigator . clipboard . write ( [ clipboardItem ] ) ;
34- } else {
35- await navigator . clipboard . writeText ( text ) ;
25+ // @ts -ignore – string literal is valid at runtime; TS lib DOM typing not available in .js file
26+ const status = await navigator . permissions . query ( { name : 'clipboard-read' } ) ;
27+
28+ if ( status . state === 'granted' ) {
29+ return true ;
30+ }
31+
32+ if ( status . state === 'prompt' ) {
33+ // Trigger a readText() to make the browser show its permission prompt.
34+ try {
35+ await navigator . clipboard . readText ( ) ;
36+ return true ;
37+ } catch {
38+ return false ;
39+ }
3640 }
37- } catch ( e ) {
38- console . error ( 'Error writing to clipboard' , e ) ;
41+
42+ // If we hit this area this is state === 'denied'
43+ return false ;
44+ } catch {
45+ return false ;
3946 }
4047}
4148
@@ -48,7 +55,9 @@ export async function writeToClipboard({ htmlString, text }) {
4855export async function readFromClipboard ( state ) {
4956 let html = '' ;
5057 let text = '' ;
51- if ( navigator . clipboard && navigator . clipboard . read ) {
58+ const hasPermission = await ensureClipboardPermission ( ) ;
59+
60+ if ( hasPermission && navigator . clipboard && navigator . clipboard . read ) {
5261 try {
5362 const items = await navigator . clipboard . read ( ) ;
5463 for ( const item of items ) {
@@ -60,10 +69,13 @@ export async function readFromClipboard(state) {
6069 }
6170 }
6271 } catch {
63- text = await navigator . clipboard . readText ( ) ;
72+ // Fallback to plain text read; may still fail if permission denied
73+ try {
74+ text = await navigator . clipboard . readText ( ) ;
75+ } catch { }
6476 }
6577 } else {
66- text = await navigator . clipboard . readText ( ) ;
78+ // permissions denied or API unavailable; leave content empty
6779 }
6880 let content = null ;
6981 if ( html ) {
0 commit comments