1
0
mirror of https://github.com/alexandrev/xslt-lab.git synced 2026-09-16 18:23:16 +00:00

Restore fixedheader + add stickybox in result pane

- fixedheader: native EthicalAds placement at top of app (above tabs),
  full-width sticky bar as designed
- stickybox: new slot in result pane right column (160px), shown whenever
  result pane is visible
- Two slots loaded via separate refs; both initialized on ethicalAdsReady

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
alexandrev-tibco
2026-05-05 14:21:52 +02:00
parent 2f525dacee
commit 9a1b9e7108
2 changed files with 74 additions and 68 deletions
+59 -45
View File
@@ -488,13 +488,14 @@ export default function App() {
const [widgetsReady, setWidgetsReady] = useState(false);
const [autoRunReady, setAutoRunReady] = useState(() => !!urlPreload);
const ethicalSlotRef = useRef(null);
const ethicalStickyRef = useRef(null);
const isLocalhost =
typeof window !== "undefined" &&
/^(localhost|127(?:\\.[0-9]+){3}|mac)$/i.test(window.location.hostname);
const ethicalAdsEnabled =
Boolean(ethicalAdsPublisher) &&
(!isLocalhost || env.VITE_ETHICALADS_DEV === "true");
const ethicalAdType = "text";
const ethicalAdType = "image";
const backendBase = (env.VITE_BACKEND_URL || "").replace(/\/$/, "");
@@ -1500,21 +1501,21 @@ export default function App() {
}, [ethicalAdsEnabled]);
useEffect(() => {
if (!ethicalAdsEnabled || !ethicalAdsReady || !ethicalSlotRef.current) return;
const doLoad = () => {
if (!ethicalAdsEnabled || !ethicalAdsReady) return;
const loadSlot = (ref) => {
try {
if (ethicalSlotRef.current) {
ethicalSlotRef.current.innerHTML = "";
window.ethicalads?.load(ethicalSlotRef.current);
if (ref.current) {
ref.current.innerHTML = "";
window.ethicalads?.load(ref.current);
}
} catch {}
};
doLoad();
// Retry if slot still empty after 1.5s (race: ethicalads init vs load event)
loadSlot(ethicalSlotRef);
loadSlot(ethicalStickyRef);
// Retry if slots still empty after 1.5s
const t = window.setTimeout(() => {
if (ethicalSlotRef.current && !ethicalSlotRef.current.children.length) {
doLoad();
}
if (ethicalSlotRef.current && !ethicalSlotRef.current.children.length) loadSlot(ethicalSlotRef);
if (ethicalStickyRef.current && !ethicalStickyRef.current.children.length) loadSlot(ethicalStickyRef);
}, 1500);
return () => window.clearTimeout(t);
}, [ethicalAdsEnabled, ethicalAdsReady]);
@@ -1523,6 +1524,17 @@ export default function App() {
return (
<div className="app-container">
<h1 className="sr-only">XSLT Playground - Online XSLT Editor and Tester</h1>
{ethicalAdsEnabled && (
<div
ref={ethicalSlotRef}
id="xsltplayground-main"
className="ea-header-slot"
data-ea-publisher={ethicalAdsPublisher}
data-ea-type="text"
data-ea-style="fixedheader"
aria-label="Advertisement"
/>
)}
<div className="tabs">
<TabsNav
tabs={tabs}
@@ -2119,17 +2131,6 @@ export default function App() {
)}
{showResultPane && (
<>
{ethicalAdsEnabled && (
<div
ref={ethicalSlotRef}
id="xsltplayground-main"
className="result-ad"
data-ea-publisher={ethicalAdsPublisher}
data-ea-type="text"
data-ea-style="fixedheader"
aria-label="Advertisement"
/>
)}
{isRunning ? (
<div className="running-box" role="status" aria-live="polite">
<span className="running-dot" />
@@ -2240,30 +2241,43 @@ export default function App() {
<Icon name="refresh" />
</button>
</div>
<div className={`result-editor-wrap${isRunning ? " result--loading" : ""}`}>
{effectiveResultView === "render" && canRenderHtml ? (
<div className="result-render">
<iframe
title="Rendered HTML output"
srcDoc={result || "<!-- empty -->"}
sandbox=""
loading="lazy"
<div className="result-body">
<div className={`result-editor-wrap${isRunning ? " result--loading" : ""}`}>
{effectiveResultView === "render" && canRenderHtml ? (
<div className="result-render">
<iframe
title="Rendered HTML output"
srcDoc={result || "<!-- empty -->"}
sandbox=""
loading="lazy"
/>
</div>
) : (
<Editor
height="100%"
language="xml"
theme={editorTheme}
value={result}
onMount={(editor) => (resultEditorRef.current = editor)}
options={{
readOnly: true,
minimap: { enabled: false },
automaticLayout: true,
wordWrap: "bounded",
wordWrapBreakAfterCharacters: ' \t})]?|>'
}}
/>
</div>
) : (
<Editor
height="100%"
language="xml"
theme={editorTheme}
value={result}
onMount={(editor) => (resultEditorRef.current = editor)}
options={{
readOnly: true,
minimap: { enabled: false },
automaticLayout: true,
wordWrap: "bounded",
wordWrapBreakAfterCharacters: ' \t})]?|>'
}}
)}
</div>
{ethicalAdsEnabled && (
<div
ref={ethicalStickyRef}
id="xsltplayground-sticky"
className="ea-sticky-slot"
data-ea-publisher={ethicalAdsPublisher}
data-ea-type={ethicalAdType}
data-ea-style="stickybox"
aria-label="Advertisement"
/>
)}
</div>
+15 -23
View File
@@ -1033,33 +1033,25 @@ a:focus-visible {
color: #007acc;
}
.result-ad {
flex-shrink: 0;
border-bottom: 1px solid var(--border-color, #e0e0e0);
background: var(--bg-secondary, #fafafa);
overflow: hidden;
/* EthicalAds — fixedheader stays native (position:fixed, full width) */
.ea-header-slot {
display: block;
}
/* Use EthicalAds fixedheader layout inline — only neutralize fixed positioning */
.result-ad {
flex-shrink: 0;
/* EthicalAds — stickybox in result pane right column */
.result-body {
display: flex;
flex: 1;
min-height: 0;
overflow: hidden;
border-bottom: 1px solid var(--border-color, #e0e0e0);
}
.result-ad .ethical-ad,
.result-ad [data-ea-style="fixedheader"] {
position: relative !important;
top: auto !important;
left: auto !important;
right: auto !important;
bottom: auto !important;
z-index: auto !important;
width: 100% !important;
box-shadow: none !important;
border-radius: 0 !important;
border-left: none !important;
border-right: none !important;
border-top: none !important;
.result-body .result-editor-wrap {
flex: 1;
min-width: 0;
}
.ea-sticky-slot {
flex-shrink: 0;
width: 160px;
}
.icon-button {