Merge pull request #36 from harrisonoconnorhover/fix/rich-table-clipboard

Fix copying selected cells as rich HTML tables
This commit is contained in:
Brandon Thomas
2026-10-10 12:58:38 -04:00
14 changed files with 465 additions and 70 deletions
+34
View File
@@ -93,6 +93,7 @@ impl eframe::App for WebShell {
fn services(inbox: Inbox, ctx: egui::Context) -> Services {
let open_inbox = inbox.clone();
Services {
copy_html: Some(Box::new(copy_html)),
open_async: Some(Box::new(move || {
let inbox = open_inbox.clone();
let ctx = ctx.clone();
@@ -124,6 +125,39 @@ fn services(inbox: Inbox, ctx: egui::Context) -> Services {
}
}
/// Start inside the copy gesture; deferring `write` can lose browser user activation.
fn copy_html(html: &str, text: &str) -> Result<(), String> {
let js = |e: wasm_bindgen::JsValue| format!("{e:?}");
let window = web_sys::window().ok_or("no window")?;
let clipboard = window.navigator().clipboard();
if clipboard.is_undefined() || clipboard.is_null() {
return Err("browser clipboard is unavailable (a secure context is required)".into());
}
if !js_sys::Reflect::get(&clipboard, &"write".into()).map_err(js)?.is_function() {
return Err("browser does not support rich clipboard writes".into());
}
let data = js_sys::Object::new();
for (mime, value) in [("text/html", html), ("text/plain", text)] {
let opts = web_sys::BlobPropertyBag::new();
opts.set_type(mime);
let parts = js_sys::Array::of1(&wasm_bindgen::JsValue::from_str(value));
let blob = web_sys::Blob::new_with_str_sequence_and_options(&parts, &opts).map_err(js)?;
js_sys::Reflect::set(&data, &mime.into(), &blob).map_err(js)?;
}
let item = web_sys::ClipboardItem::new_with_record_from_str_to_blob_promise(&data).map_err(js)?;
let promise = clipboard.write(&js_sys::Array::of1(&item));
let text = text.to_owned();
wasm_bindgen_futures::spawn_local(async move {
if let Err(e) = wasm_bindgen_futures::JsFuture::from(promise).await {
log::warn!("Rich clipboard write failed; trying plain text: {e:?}");
if let Err(e) = wasm_bindgen_futures::JsFuture::from(clipboard.write_text(&text)).await {
log::error!("Plain-text clipboard fallback also failed: {e:?}");
}
}
});
Ok(())
}
/// Triggers a browser download of `bytes`.
fn download(name: &str, bytes: &[u8]) -> Result<(), String> {
let js = |e: wasm_bindgen::JsValue| format!("{e:?}");