;; --------------------------------------------------------------------------- ;; Page shell — the outermost HTML document structure ;; ;; Replaces the Python HTML string template. All page data is computed in ;; Python and passed as keyword arguments. The component just arranges ;; the precomputed values into HTML structure. ;; ;; raw! is used for: ;; - (not an element) ;; - Script/style content (must not be HTML-escaped) ;; - Pre-rendered meta HTML from callers ;; --------------------------------------------------------------------------- (defcomp ~shared:shell/sx-page-shell (&key (title :as string) (meta-html :as string?) (csrf :as string) (sx-css :as string?) (sx-css-classes :as string?) (component-hash :as string?) (component-defs :as string?) (pages-sx :as string?) (page-sx :as string?) (asset-url :as string) (sx-js-hash :as string) (body-js-hash :as string?) (head-scripts :as list?) (inline-css :as string?) (inline-head-js :as string?) (init-sx :as string?) (body-scripts :as list?)) (<> (raw! "") (html :lang "en" (head (meta :charset "utf-8") (meta :name "viewport" :content "width=device-width, initial-scale=1") (meta :name "robots" :content "index,follow") (meta :name "theme-color" :content "#ffffff") (title title) (when meta-html (raw! meta-html)) (meta :name "csrf-token" :content csrf) (style :id "sx-css" (raw! (or sx-css ""))) (meta :name "sx-css-classes" :content (or sx-css-classes "")) ;; CDN / head scripts — configurable per app ;; Pass a list (even empty) to override defaults; nil = use defaults (if (not (nil? head-scripts)) (map (fn (src) (script :src src)) head-scripts) ;; Default: Prism + SweetAlert (legacy apps) (<> (script :src "https://unpkg.com/prismjs/prism.js") (script :src "https://unpkg.com/prismjs/components/prism-javascript.min.js") (script :src "https://unpkg.com/prismjs/components/prism-python.min.js") (script :src "https://unpkg.com/prismjs/components/prism-bash.min.js") (script :src "https://cdn.jsdelivr.net/npm/sweetalert2@11"))) ;; Inline JS — skipped when app provides its own inline-head-js (even empty) (if (not (nil? inline-head-js)) (when (not (empty? inline-head-js)) (script (raw! inline-head-js))) (<> (script (raw! "if(matchMedia('(hover:hover) and (pointer:fine)').matches){document.documentElement.classList.add('hover-capable')}")) (script (raw! "document.addEventListener('click',function(e){var t=e.target.closest('[data-close-details]');if(!t)return;var d=t.closest('details');if(d)d.removeAttribute('open')})")))) ;; Inline CSS — configurable per app ;; Pass a string (even empty) to override defaults; nil = use defaults (if (not (nil? inline-css)) (style (raw! inline-css)) ;; Default: all shared styles (legacy apps) (style (raw! "details[data-toggle-group=\"mobile-panels\"]>summary{list-style:none} details[data-toggle-group=\"mobile-panels\"]>summary::-webkit-details-marker{display:none} @media(min-width:768px){.nav-group:focus-within .submenu,.nav-group:hover .submenu{display:block}} img{max-width:100%;height:auto} .clamp-2{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden} .clamp-3{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden} .no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none} details.group{overflow:hidden}details.group>summary{list-style:none}details.group>summary::-webkit-details-marker{display:none} .sx-indicator{display:none}.sx-request .sx-indicator{display:inline-flex} .sx-error .sx-indicator{display:none}.sx-loading .sx-indicator{display:inline-flex} .js-wrap.open .js-pop{display:block}.js-wrap.open .js-backdrop{display:block}")))) (body :class "bg-stone-50 text-stone-900" (script :type "text/sx" :data-components true :data-hash component-hash (raw! (or component-defs ""))) (when init-sx (script :type "text/sx" :data-init true (raw! init-sx))) (script :type "text/sx-pages" (raw! (or pages-sx ""))) (script :type "text/sx" :data-mount "body" (raw! (or page-sx ""))) (script :src (str asset-url "/scripts/sx-browser.js?v=" sx-js-hash)) ;; Body scripts — configurable per app ;; Pass a list (even empty) to override defaults; nil = use defaults (if (not (nil? body-scripts)) (map (fn (src) (script :src src)) body-scripts) ;; Default: body.js (legacy apps) (script :src (str asset-url "/scripts/body.js?v=" body-js-hash)))))))