refactor: extract post content JS into standalone script
This commit is contained in:
parent
0345fa70f2
commit
44c1358da5
118
public/js/post-content.js
Normal file
118
public/js/post-content.js
Normal file
@ -0,0 +1,118 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function initCopyButtons(root) {
|
||||
var blocks = root.querySelectorAll("pre > code");
|
||||
for (var i = 0; i < blocks.length; i++) {
|
||||
var code = blocks[i];
|
||||
var pre = code.parentElement;
|
||||
if (!pre) continue;
|
||||
if (pre.querySelector(".copy-code")) continue;
|
||||
|
||||
var btn = document.createElement("button");
|
||||
btn.className = "copy-code";
|
||||
btn.textContent = "copy";
|
||||
btn.setAttribute("aria-label", "Copy code");
|
||||
|
||||
(function (codeText) {
|
||||
btn.addEventListener("click", function () {
|
||||
var self = this;
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(codeText);
|
||||
} else {
|
||||
var ta = document.createElement("textarea");
|
||||
ta.value = codeText;
|
||||
ta.style.position = "fixed";
|
||||
ta.style.opacity = "0";
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(ta);
|
||||
}
|
||||
self.textContent = "copied!";
|
||||
setTimeout(function () {
|
||||
self.textContent = "copy";
|
||||
}, 2000);
|
||||
});
|
||||
})(code.textContent || "");
|
||||
|
||||
pre.appendChild(btn);
|
||||
}
|
||||
}
|
||||
|
||||
function closeLightbox(overlay) {
|
||||
if (overlay && overlay.parentNode) {
|
||||
overlay.parentNode.removeChild(overlay);
|
||||
document.body.style.overflow = "";
|
||||
}
|
||||
}
|
||||
|
||||
function initImageZoom(root) {
|
||||
var images = root.querySelectorAll("img");
|
||||
for (var i = 0; i < images.length; i++) {
|
||||
var img = images[i];
|
||||
if (img.getAttribute("data-zoom-enabled")) continue;
|
||||
var src = img.getAttribute("src") || img.src || "";
|
||||
if (src.indexOf("data:") === 0) continue;
|
||||
|
||||
img.setAttribute("data-zoom-enabled", "true");
|
||||
|
||||
var originalSrc = img.src;
|
||||
var sep = originalSrc.indexOf("?") !== -1 ? "&" : "?";
|
||||
img.src = originalSrc + sep + "w=800";
|
||||
img.classList.add("md-content-img-zoomable");
|
||||
|
||||
(function (origSrc, altText) {
|
||||
img.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
var overlay = document.createElement("div");
|
||||
overlay.className = "md-image-lightbox-overlay";
|
||||
|
||||
var container = document.createElement("div");
|
||||
container.className = "md-image-lightbox-content";
|
||||
|
||||
var fullImg = document.createElement("img");
|
||||
fullImg.src = origSrc;
|
||||
fullImg.alt = altText;
|
||||
|
||||
var closeBtn = document.createElement("button");
|
||||
closeBtn.className = "md-image-lightbox-close";
|
||||
closeBtn.textContent = "\u2715";
|
||||
|
||||
container.appendChild(fullImg);
|
||||
container.appendChild(closeBtn);
|
||||
overlay.appendChild(container);
|
||||
document.body.appendChild(overlay);
|
||||
document.body.style.overflow = "hidden";
|
||||
|
||||
var onKey = function (ev) {
|
||||
if (ev.key === "Escape") {
|
||||
cleanup(overlay, onKey);
|
||||
}
|
||||
};
|
||||
var cleanup = function (ol, kh) {
|
||||
closeLightbox(ol);
|
||||
document.removeEventListener("keydown", kh);
|
||||
};
|
||||
overlay.addEventListener("click", function () {
|
||||
cleanup(overlay, onKey);
|
||||
});
|
||||
container.addEventListener("click", function (ev) {
|
||||
ev.stopPropagation();
|
||||
});
|
||||
closeBtn.addEventListener("click", function () {
|
||||
cleanup(overlay, onKey);
|
||||
});
|
||||
document.addEventListener("keydown", onKey);
|
||||
});
|
||||
})(originalSrc, img.alt || "");
|
||||
}
|
||||
}
|
||||
|
||||
window.__initPostContent = function (selector) {
|
||||
var root = document.querySelector(selector);
|
||||
if (!root) return;
|
||||
initCopyButtons(root);
|
||||
initImageZoom(root);
|
||||
};
|
||||
})();
|
||||
@ -4,176 +4,8 @@ use dioxus::prelude::*;
|
||||
pub fn PostContent(content_html: String) -> Element {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use_effect(move || {
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
if let Some(window) = web_sys::window() {
|
||||
if let Some(document) = window.document() {
|
||||
// Add copy buttons to all code blocks
|
||||
if let Ok(elements) = document.query_selector_all("pre > code") {
|
||||
for i in 0..elements.length() {
|
||||
if let Some(codeblock) = elements.item(i) {
|
||||
if let Some(parent) = codeblock.parent_element() {
|
||||
// Check if button already exists
|
||||
if parent.query_selector(".copy-code").ok().flatten().is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let copybutton = document.create_element("button").unwrap();
|
||||
copybutton.set_class_name("copy-code");
|
||||
copybutton.set_text_content(Some("copy"));
|
||||
copybutton.set_attribute("aria-label", "Copy code").unwrap();
|
||||
|
||||
let code_text = codeblock.text_content().unwrap_or_default();
|
||||
let btn = copybutton.clone();
|
||||
let copy_closure = Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| {
|
||||
let has_clipboard = web_sys::window()
|
||||
.map(|w| {
|
||||
let clip = js_sys::Reflect::get(&w.navigator(), &js_sys::JsString::from("clipboard")).unwrap_or(wasm_bindgen::JsValue::UNDEFINED);
|
||||
!clip.is_undefined()
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
if has_clipboard {
|
||||
let _ = js_sys::eval(&format!(
|
||||
"navigator.clipboard.writeText({})",
|
||||
serde_json::to_string(&code_text).unwrap_or_default()
|
||||
));
|
||||
} else {
|
||||
let _ = js_sys::eval(&format!(
|
||||
"((t)=>{{const e=document.createElement('textarea');e.value=t;e.style.position='fixed';e.style.opacity='0';document.body.appendChild(e);e.select();document.execCommand('copy');document.body.removeChild(e)}})({})",
|
||||
serde_json::to_string(&code_text).unwrap_or_default()
|
||||
));
|
||||
}
|
||||
|
||||
btn.set_text_content(Some("copied!"));
|
||||
let btn_clone = btn.clone();
|
||||
let reset_closure = Closure::wrap(Box::new(move || {
|
||||
btn_clone.set_text_content(Some("copy"));
|
||||
}) as Box<dyn FnMut()>);
|
||||
let _ = web_sys::window()
|
||||
.unwrap()
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
reset_closure.as_ref().unchecked_ref(),
|
||||
2000,
|
||||
);
|
||||
reset_closure.forget();
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
copybutton.add_event_listener_with_callback("click", copy_closure.as_ref().unchecked_ref()).unwrap();
|
||||
copy_closure.forget();
|
||||
|
||||
let _ = parent.append_child(©button);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add click-to-zoom for images in post content
|
||||
if let Ok(images) = document.query_selector_all(".md-content img") {
|
||||
for i in 0..images.length() {
|
||||
if let Some(img) = images.item(i) {
|
||||
let img_element = img.clone().dyn_into::<web_sys::HtmlImageElement>().unwrap();
|
||||
|
||||
// Skip if already processed
|
||||
if img_element.get_attribute("data-zoom-enabled").is_some() {
|
||||
continue;
|
||||
}
|
||||
img_element.set_attribute("data-zoom-enabled", "true").unwrap();
|
||||
|
||||
let document_clone = document.clone();
|
||||
let original_src = img_element.src();
|
||||
let alt = img_element.alt();
|
||||
|
||||
if original_src.starts_with("data:") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Replace src with thumbnail version (add ?w=800)
|
||||
let thumb_src = if original_src.contains('?') {
|
||||
format!("{}&w=800", original_src)
|
||||
} else {
|
||||
format!("{}?w=800", original_src)
|
||||
};
|
||||
img_element.set_src(&thumb_src);
|
||||
img_element.set_class_name("md-content-img-zoomable");
|
||||
|
||||
let closure = Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| {
|
||||
// Create overlay
|
||||
let overlay = document_clone.create_element("div").unwrap();
|
||||
overlay.set_class_name("md-image-lightbox-overlay");
|
||||
|
||||
// Create image container
|
||||
let container = document_clone.create_element("div").unwrap();
|
||||
container.set_class_name("md-image-lightbox-content");
|
||||
|
||||
// Create full-size image
|
||||
let full_img = document_clone.create_element("img").unwrap();
|
||||
let full_img_el = full_img.dyn_ref::<web_sys::HtmlImageElement>().unwrap();
|
||||
full_img_el.set_src(&original_src);
|
||||
full_img_el.set_alt(&alt);
|
||||
|
||||
// Create close button
|
||||
let close_btn = document_clone.create_element("button").unwrap();
|
||||
close_btn.set_class_name("md-image-lightbox-close");
|
||||
close_btn.set_text_content(Some("✕"));
|
||||
|
||||
// Assemble
|
||||
let _ = container.append_child(&full_img);
|
||||
let _ = container.append_child(&close_btn);
|
||||
let _ = overlay.append_child(&container);
|
||||
let _ = document_clone.body().unwrap().append_child(&overlay);
|
||||
|
||||
// Prevent body scroll
|
||||
document_clone.body().unwrap().set_attribute("style", "overflow: hidden;").unwrap();
|
||||
|
||||
// Click overlay background to close
|
||||
let overlay_for_bg = overlay.clone();
|
||||
let close_bg = Closure::wrap(Box::new(move |_evt: web_sys::MouseEvent| {
|
||||
if let Some(parent) = overlay_for_bg.parent_node() {
|
||||
let _ = parent.remove_child(&overlay_for_bg);
|
||||
}
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
overlay.add_event_listener_with_callback("click", close_bg.as_ref().unchecked_ref()).unwrap();
|
||||
close_bg.forget();
|
||||
|
||||
// Click container stops propagation (so clicking image doesn't close)
|
||||
let stop_prop = Closure::wrap(Box::new(move |evt: web_sys::MouseEvent| {
|
||||
evt.stop_propagation();
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
container.add_event_listener_with_callback("click", stop_prop.as_ref().unchecked_ref()).unwrap();
|
||||
stop_prop.forget();
|
||||
|
||||
// Click close button to close
|
||||
let overlay_for_btn = overlay.clone();
|
||||
let close_btn_handler = Closure::wrap(Box::new(move |_evt: web_sys::MouseEvent| {
|
||||
if let Some(parent) = overlay_for_btn.parent_node() {
|
||||
let _ = parent.remove_child(&overlay_for_btn);
|
||||
}
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
close_btn.add_event_listener_with_callback("click", close_btn_handler.as_ref().unchecked_ref()).unwrap();
|
||||
close_btn_handler.forget();
|
||||
|
||||
// Escape key to close
|
||||
let overlay_for_key = overlay.clone();
|
||||
let key_handler = Closure::wrap(Box::new(move |evt: web_sys::KeyboardEvent| {
|
||||
if evt.key() == "Escape" {
|
||||
if let Some(parent) = overlay_for_key.parent_node() {
|
||||
let _ = parent.remove_child(&overlay_for_key);
|
||||
}
|
||||
}
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
document_clone.add_event_listener_with_callback("keydown", key_handler.as_ref().unchecked_ref()).unwrap();
|
||||
key_handler.forget();
|
||||
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
|
||||
img.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref()).unwrap();
|
||||
closure.forget();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = js_sys::eval(include_str!("../../../public/js/post-content.js"));
|
||||
let _ = js_sys::eval("window.__initPostContent('.post-content')");
|
||||
});
|
||||
|
||||
rsx! {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user