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

Feat: runnable examples on the reference pages via an embedded mode

The 230 function pages linked out to the playground; now they can run their
own example in place. ?embed=1 renders the app without ads or workspace
chrome, plus a link out to the full editor.

The iframe is only created on click: embedding it eagerly on every reference
page would pull the editor bundle into 230 page loads and undo the LCP work.

Also adds ?template=<id> so a URL can open a starter workspace directly, and
an XPath tester template.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KZntQUfw463NW4ftgSjWw5
This commit is contained in:
2026-07-25 15:31:18 +00:00
parent a70b4f05ca
commit ebce292fa7
6 changed files with 141 additions and 3 deletions
+38 -2
View File
@@ -13,7 +13,7 @@ import {
detectVersionUpgradeHint,
findErrorReference,
} from "./lib/workspaceUtils";
import { templateToWorkspace } from "./lib/templates";
import { templateToWorkspace, findTemplate } from "./lib/templates";
/* global __APP_VERSION__, __GIT_COMMIT__ */
@@ -416,6 +416,17 @@ function defaultTab(overrides = {}) {
};
}
// Embedded mode (?embed=1): the app is rendered inside an iframe on the blog's
// reference pages, so it drops the ads and workspace chrome and shows just the
// editor plus a way out to the full app.
const IS_EMBED = (() => {
try {
return new URLSearchParams(window.location.search).get("embed") === "1";
} catch {
return false;
}
})();
const MAX_WORKSPACES = 3;
const WORKSPACE_EXPORT_VERSION = 1;
const RESULT_HEIGHT_KEY = "resultPaneHeight";
@@ -675,6 +686,16 @@ export default function App() {
);
}
}
// ?template=<id> opens a starter workspace directly — this is what the
// /xpath-tester/ and /xml-to-json/ landing pages link to.
try {
const templateId = new URLSearchParams(window.location.search).get("template");
const template = templateId ? findTemplate(templateId) : null;
if (template) {
initialTabs = [defaultTab(templateToWorkspace(template))];
}
} catch {}
let initialActive = initialTabs[0]?.id;
try {
const sAct = localStorage.getItem("active");
@@ -821,6 +842,7 @@ export default function App() {
typeof window !== "undefined" &&
/^(localhost|127(?:\\.[0-9]+){3}|mac)$/i.test(window.location.hostname);
const ethicalAdsEnabled =
!IS_EMBED &&
Boolean(ethicalAdsPublisher) &&
(!isLocalhost || env.VITE_ETHICALADS_DEV === "true");
const ethicalAdVariant = "stickybox";
@@ -1902,7 +1924,7 @@ export default function App() {
return (
<div className="app-container">
<div className={`app-container${IS_EMBED ? " app-container--embed" : ""}`}>
<h1 className="sr-only">XSLT Playground - Online XSLT Editor and Tester</h1>
{ethicalAdsEnabled && (
<div
@@ -1915,6 +1937,19 @@ export default function App() {
aria-label="Advertisement"
/>
)}
{IS_EMBED && (
<div className="embed-bar">
<span>XSLT Playground</span>
<a
href={typeof window !== "undefined" ? buildShareUrl(activeTab) : "https://xsltplayground.com/"}
target="_blank"
rel="noopener noreferrer"
>
Open in the full editor
</a>
</div>
)}
{!IS_EMBED && (
<div className="tabs">
<TabsNav
tabs={tabs}
@@ -1967,6 +2002,7 @@ export default function App() {
/>
</div>
</div>
)}
<div className="main">
{paramsCollapsed ? (
<div className="params-collapsed">
+27
View File
@@ -156,8 +156,35 @@ export const TEMPLATES = [
{ name: "country", value: "ES", open: true },
],
},
{
id: "xpath-tester",
title: "XPath tester",
description: "Evaluate an XPath expression against XML and list every match.",
version: "3.0",
xslt: `<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<!-- Put the expression you want to test here. -->
<xsl:variable name="expression" select="//order[@country = 'ES']"/>
<xsl:template match="/">
<matches count="{count($expression)}">
<xsl:for-each select="$expression">
<match position="{position()}">
<xsl:copy-of select="."/>
</match>
</xsl:for-each>
</matches>
</xsl:template>
</xsl:stylesheet>`,
params: [{ name: "input", value: ORDERS_XML, open: true }],
},
];
export function findTemplate(id) {
return TEMPLATES.find((t) => t.id === id) || null;
}
// Strip the presentation-only fields before handing a template to defaultTab().
export function templateToWorkspace(template) {
return {
+33
View File
@@ -2205,3 +2205,36 @@ a:focus-visible {
border-color: #2b3645;
color: #e6edf3;
}
/* Embedded mode (?embed=1) — the app inside an iframe on the reference pages. */
.app-container--embed {
padding: 0;
}
.embed-bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
padding: 0.35rem 0.6rem;
background: #f0f6ff;
border-bottom: 1px solid #cfdcf4;
font-size: 0.78rem;
font-weight: 600;
color: #21426c;
}
.embed-bar a {
color: #007acc;
text-decoration: none;
}
.embed-bar a:hover {
text-decoration: underline;
}
:root[data-theme="dark"] .embed-bar {
background: #161c24;
border-color: #2b3645;
color: #e6edf3;
}
+1
View File
@@ -54,5 +54,6 @@
<p class="muted">Repo: <a href="https://github.com/alexandrev/xslt-lab">alexandrev/xslt-lab</a></p>
</div>
</footer>
{{ block "scripts" . }}{{ end }}
</body>
</html>
+29 -1
View File
@@ -30,7 +30,12 @@
{{- end }}
<div class="ref-try">
{{ if .Params.tryUrl }}
<a href="{{ .Params.tryUrl }}" class="button" target="_blank" rel="noopener noreferrer">Open live example in XSLT Playground →</a>
{{- /* The playground runs inline in an iframe, but only once the reader asks
for it: embedding it eagerly on 230 reference pages would load the
whole editor bundle on every page view and wreck the LCP work. */ -}}
<button type="button" class="button ref-run" data-run-url="{{ .Params.tryUrl }}">▶ Run this example here</button>
<a href="{{ .Params.tryUrl }}" class="button button--ghost" target="_blank" rel="noopener noreferrer">Open in the full editor →</a>
<div class="ref-embed" hidden></div>
{{ else }}
<a href="https://xsltplayground.com/" class="button" target="_blank" rel="noopener noreferrer">Try it in XSLT Playground →</a>
{{ end }}
@@ -39,3 +44,26 @@
{{ partial "related-posts.html" . }}
</article>
{{ end }}
{{ define "scripts" }}
<script>
// Lazily swap the button for an embedded playground running this page's example.
document.querySelectorAll(".ref-run").forEach(function (btn) {
btn.addEventListener("click", function () {
var host = btn.parentElement.querySelector(".ref-embed");
if (!host || host.dataset.loaded) return;
var url = btn.dataset.runUrl;
url += (url.indexOf("?") === -1 ? "?" : "&") + "embed=1";
var frame = document.createElement("iframe");
frame.src = url;
frame.title = "XSLT Playground — live example";
frame.loading = "lazy";
frame.className = "ref-embed-frame";
host.appendChild(frame);
host.hidden = false;
host.dataset.loaded = "1";
btn.disabled = true;
btn.textContent = "▶ Running below";
});
});
</script>
{{ end }}
+13
View File
@@ -494,3 +494,16 @@ code {
.post-list li { flex-direction: column; align-items: flex-start; }
.ref-index-list a { min-width: unset; }
}
/* Runnable example embedded on reference pages (loaded on click only). */
.ref-try { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
.button--ghost { background: transparent; border: 1px solid currentColor; }
.ref-run[disabled] { opacity: 0.6; cursor: default; }
.ref-embed { width: 100%; margin-top: 0.75rem; }
.ref-embed-frame {
width: 100%;
height: 520px;
border: 1px solid #cfdcf4;
border-radius: 8px;
background: #fff;
}