@@ -37,15 +37,49 @@ function render(key: SectionKey, value: unknown) {
3737 }
3838}
3939
40+ async function copyText ( text : string ) : Promise < boolean > {
41+ // Async Clipboard API needs `clipboard-write` permission policy on the
42+ // iframe. Hosts that don't honor _meta.ui.permissions.clipboardWrite
43+ // will reject it — fall back to execCommand via a hidden textarea.
44+ try {
45+ if ( navigator . clipboard ?. writeText ) {
46+ await navigator . clipboard . writeText ( text ) ;
47+ return true ;
48+ }
49+ } catch {
50+ // fall through
51+ }
52+ try {
53+ const ta = document . createElement ( "textarea" ) ;
54+ ta . value = text ;
55+ ta . setAttribute ( "readonly" , "" ) ;
56+ ta . style . position = "fixed" ;
57+ ta . style . top = "-1000px" ;
58+ ta . style . opacity = "0" ;
59+ document . body . appendChild ( ta ) ;
60+ ta . select ( ) ;
61+ const ok = document . execCommand ( "copy" ) ;
62+ document . body . removeChild ( ta ) ;
63+ return ok ;
64+ } catch {
65+ return false ;
66+ }
67+ }
68+
4069for ( const btn of Array . from (
4170 document . querySelectorAll < HTMLButtonElement > ( "button[data-copy]" ) ,
4271) ) {
43- btn . addEventListener ( "click" , ( ) => {
72+ btn . addEventListener ( "click" , async ( ) => {
4473 const key = btn . dataset . copy as SectionKey ;
4574 const pre = sections [ key ] ?. querySelector ( "pre" ) ;
46- if ( pre ?. textContent ) {
47- navigator . clipboard . writeText ( pre . textContent ) . catch ( ( ) => { } ) ;
48- }
75+ const text = pre ?. textContent ;
76+ if ( ! text ) return ;
77+ const original = btn . textContent ;
78+ const ok = await copyText ( text ) ;
79+ btn . textContent = ok ? "Copied" : "Copy failed" ;
80+ setTimeout ( ( ) => {
81+ btn . textContent = original ;
82+ } , 1200 ) ;
4983 } ) ;
5084}
5185
0 commit comments