use indoc::indoc; use serde_json; use crate::css::scope_css; use crate::compiler::models::{PageRoute, Project, Template}; use crate::js::minify_javascript; impl Project { pub fn compile_script(&self) -> String { let template = indoc! { r#" (function() { if (typeof Handlebars === 'undefined') { const script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js'; document.head.appendChild(script); } const tempDefine = window.define; window.define = undefined; const script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js'; script.onload = function() { window.define = tempDefine; {pages_script} const domReadyEvent = new Event('DOMContentLoaded', { bubbles: true, cancelable: true, }); document.dispatchEvent(domReadyEvent); window.dispatchEvent(domReadyEvent); }; document.head.appendChild(script); })() "# }; let mut script = String::new(); for page in &self.pages { let template_script = indoc! { r#" if (eval({page_route})) { {content_script} } "# }; let route_script = page.route.as_script(); let content_script = page .content .iter() .map(|template| template.as_script()) .collect::>() .join("\n"); script.push_str( &template_script .replace( "{page_route}", &serde_json::to_string(&minify_javascript(&route_script)).unwrap(), ) .replace("{content_script}", &content_script), ); } minify_javascript(&template.replace("{pages_script}", &script)) } } impl PageRoute { fn as_script(&self) -> String { match self { PageRoute::Regexpr(expr) => { let template = indoc! { r#" (function() { const regex = new RegExp({expr}); return regex.test(window.location.pathname); })() "# }; template.replace("{expr}", &serde_json::to_string(&expr.trim()).unwrap()) } PageRoute::Static(route) => { let template = indoc! { r#" (function() { return window.location.pathname === {route}; })() "# }; template.replace("{route}", &serde_json::to_string(&route).unwrap()) } PageRoute::CustomScript(script) => script.clone(), } } } impl Template { pub fn as_script(&self) -> String { match self { Template::CustomScript { script, style } => { if style.is_none() || style.as_ref().unwrap().trim().is_empty() { return script.clone(); } let style_script = style.as_ref().map_or(String::new(), |s| { format!( r#" const styleElement = document.createElement('style'); styleElement.textContent = {style}; document.head.appendChild(styleElement); "#, style = serde_json::to_string(s).unwrap() ) }); format!("{}\n{}", style_script, script) } Template::TemplateInjector { template, replace_selector, scraper, style, } => { let scope_class = format!("template-style-{}", rand::random::()); let style_script = if let Some(style) = style { if style.trim().is_empty() { String::new() } else { format!( indoc! {r#" const styleElement = document.createElement('style'); styleElement.textContent = {style}; document.head.appendChild(styleElement); "# }, style = &serde_json::to_string(&scope_css(&style, &scope_class)).unwrap() ) } } else { String::new() }; let script_template = indoc! { r#" (function() { {style_script} const parsedData = eval({scraper} || "(() => ({}))"); const template = Handlebars.compile({template}); const rendered = template(parsedData); const targetElement = document.querySelector({replace_selector}); const temp = document.createElement('template'); temp.innerHTML = rendered; const newElement = temp.content.firstElementChild; targetElement.replaceWith(newElement); newElement.classList.add({scope_class}); })(); "# }; script_template .replace("{style_script}", &style_script) .replace( "{template}", &serde_json::to_string(&template.trim()).unwrap(), ) .replace( "{replace_selector}", &serde_json::to_string(&replace_selector).unwrap(), ) .replace( "{scope_class}", &serde_json::to_string(&scope_class).unwrap(), ) .replace( "{scraper}", &serde_json::to_string(&minify_javascript( scraper.as_deref().unwrap_or(""), )) .unwrap(), ) } } } }