Skip to content

Commit

Permalink
Add extensions page
Browse files Browse the repository at this point in the history
  • Loading branch information
Kalabasa committed Feb 24, 2024
1 parent 0c65373 commit 1330ccb
Show file tree
Hide file tree
Showing 13 changed files with 579 additions and 3 deletions.
330 changes: 330 additions & 0 deletions docs/extensions/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,330 @@
<!DOCTYPE html>
<html>
<head>
<title>htmz / Extensions</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../style.css" />
<link rel="icon" href="../favicon.png" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css"
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<style>
.header-row {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
}
h1 {
margin: 0;
font-size: 1.6rem;
}
h2 {
padding: 0;
border: none;
}
h2:first-child {
margin-top: 0;
}

.extensions-explorer {
display: flex;
gap: 1rem;
}

.extension-list {
flex: 1 1 20%;
list-style: none;
margin: 0;
padding: 0;
overflow: hidden;
}

.extension-item {
font-weight: bold;
border-bottom: solid 1px #0002;
}
.extension-item:focus-visible,
.extension-item:hover {
background: #fff;
}
.extension-item a {
display: block;
padding: 1rem;
}

.extension-pane {
flex: 1 1 80%;
}

iframe[name="extension-demo-frame"] {
width: 100%;
height: 15rem;
}

#extension-code-container pre {
margin: 0;
}
.extension-code-container-empty {
display: flex;
justify-content: center;
align-items: center;
min-height: 15rem;
}

.add-to-basket-row {
margin: 1rem 0;
}
.add-to-basket-row:has(:disabled) {
display: none;
}
#add-to-basket {
font: inherit;
font-weight: bold;
padding: 0.25rem 0.5rem;
background: #0bd;
color: #fff;
cursor: pointer;
}
#add-to-basket .decor {
display: inline-block;
font-family: monospace;
font-variant-ligatures: none;
vertical-align: middle;
opacity: 0.6;
width: 1ch;
clip-path: inset(0 0 0 0);
}
#add-to-basket:focus-visible .decor,
#add-to-basket:hover .decor {
animation: decorAnimation 1s steps(4) infinite;
}
@keyframes decorAnimation {
from {
transform: translateX(-200%);
clip-path: inset(0 -200% 0 200%);
}
}

#basket {
position: fixed;
top: 0;
bottom: 0;
left: 100%;
border-radius: 0;
transition: transform 0.2s;
}
#basket:has(#toggle-basket :checked) {
transform: translateX(-100%);
box-shadow: 0 0 2rem #0002;
}
#toggle-basket {
position: absolute;
right: calc(100% - 1rem);
font: inherit;
margin-top: 1rem;
padding: 0.5rem 2rem 0.5rem 1rem;
box-shadow: 0 0 2rem #0002;
cursor: pointer;
z-index: -1;
}
#toggle-basket input {
display: none;
}
.basket-content {
width: 60rem;
max-width: calc(100vw - 12rem);
height: 100%;
padding: 1rem;
overflow: auto;
overscroll-behavior: contain;
}
</style>
</head>
<body>
<div class="header-row">
<h1>htmz / Extensions 🍱</h1>
<a href="..">back to main page</a>
</div>

<section class="extensions-explorer">
<ul class="extension-list panel">
<li class="extension-item">
<a href="multitarget/" target="extension-demo-frame">multitarget</a>
</li>
<li class="extension-item">
<a href="no-history/" target="extension-demo-frame">no-history</a>
</li>
<li class="extension-item">
<a href="repeat-gets/" target="extension-demo-frame">repeat-gets</a>
</li>
<li class="extension-item">
<a href="unwrap-template/" target="extension-demo-frame"
>unwrap-template</a
>
</li>
</ul>

<div class="extension-pane">
<div id="extension-description-container"></div>

<h2 class="demo-header">Demo</h2>
<iframe
name="extension-demo-frame"
class="demo-result"
onload="window.onExtensionLoad?.(this)"
></iframe>

<h2 class="demo-header">Code</h2>
<div id="extension-code-container">
<div class="extension-code-container-empty panel">
No extension selected!
</div>
</div>

<!-- <div class="add-to-basket-row">
<button id="add-to-basket" class="panel" disabled>
<span class="decor" aria-hidden>=>=</span>
add to basket
<span class="decor" aria-hidden>>=></span>
</button>
or copy the snippet above.
</div> -->
</div>
</section>

<!-- <aside id="basket" class="panel">
<label id="toggle-basket" class="panel">
basket
<input type="checkbox" aria-label="Toggle basket" />
</label>
<div class="basket-content">
<pre id="basket-total" class="code wide"></pre>
</div>
</aside> -->

<script>
const basket = [];

const descriptionContainer = document.getElementById(
"extension-description-container"
);
const codeContainer = document.getElementById("extension-code-container");
const addToBasketButton = document.getElementById("add-to-basket");
const basketTotal = document.getElementById("basket-total");

setTimeout(() => onUpdateBasket());

function onExtensionLoad(frame) {
const code = frame.contentDocument.getElementById("extension-code");
const description = frame.contentDocument.getElementById(
"extension-description"
);

if (description) descriptionContainer.replaceChildren(description);
if (code) showSourceCode(code);
setUpAddToBasket();
}

function showSourceCode(code) {
const codeElement = document.createElement("pre");
codeElement.classList.add("code");
codeElement.classList.add("wide");
codeElement.textContent = formatSourceCode(code.innerHTML);
hljs?.highlightElement(codeElement);
codeContainer.replaceChildren(codeElement);
}

function formatSourceCode(text) {
const indent = text.match(/^[\n\r]\s*/)?.[0];
return text.replaceAll(indent, "\n").trim();
}

function setUpAddToBasket() {
if (!addToBasketButton) return; // basket feature is disabled

addToBasketButton.disabled = false;
addToBasketButton.onclick = () => {
const match = codeContainer.textContent.match(
/function htmz\(frame\)\s*\{(.*?)setTimeout\(\(\)\s*=>\s*\{?(.*?)document\s*\.querySelector\(frame\.contentWindow\.location\.hash\s*\|\|\s*null\)\s*\?\.replaceWith\(\.\.\.frame\.contentDocument\.body\.children\);?(.*?)\}?\);\s*\}\s*<\/script>/ms
);

if (!match) {
alert(
"Error parsing extension code! You have to manually copy the snippet."
);
return;
}

const parts = match
.slice(1)
.map(
(part) =>
part.match(/^\s*\/\/ ?\-+8<\-+.+?\/\/ ?\-+>8\-+\s*$/ms)?.[0]
);
const [preTimeout, pre, post] = parts;
addToBasket({ preTimeout, pre, post });
};
}

function addToBasket(extension) {
basket.push(extension);
onUpdateBasket();
}

function onUpdateBasket() {
if (!addToBasketButton) return; // basket feature is disabled

basketTotal.textContent = buildSnippet(
formatSnippets(basket.map((ext) => ext.preTimeout ?? "")),
formatSnippets(basket.map((ext) => ext.pre ?? "")),
formatSnippets(basket.map((ext) => ext.post ?? ""))
);

delete basketTotal.dataset.highlighted;
hljs?.highlightElement(basketTotal);
}

function formatSnippets(snippets) {
return snippets
.map((snippet) =>
snippet
.split("\n")
.filter((line) => /\S/.test(line))
.map((line, index, lines) =>
index >= 3 && index < lines.length - 2
? line.replace(/\/\/.*/, "")
: line
)
.filter((line) => /\S/.test(line))
.join("\n")
)
.filter((snippet) => snippet);
}

function buildSnippet(preTimeout, pre, post) {
// prettier-ignore
return [
`\
<script>
function htmz(frame) {`,
...preTimeout,
` setTimeout(() => {`,
...pre,
`\
document
.querySelector(frame.contentWindow.location.hash || null)
?.replaceWith(...frame.contentDocument.body.children);`,
...post,
`\
});
}
</${"script"}>
<iframe hidden name="htmz" onload="window.htmz(this)"></iframe>`
].join("\n\n");
}
</script>
</body>
</html>
5 changes: 5 additions & 0 deletions docs/extensions/multitarget/content.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<p id="content">
<span id="alert">Operation successful!</span>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Hoc est exemplum
multitarget, vel "update extra ordinem" ut vocant.
</p>
57 changes: 57 additions & 0 deletions docs/extensions/multitarget/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<div id="extension-code">
<script>
function htmz(frame) {
setTimeout(() => {
// ---------------------------------8<-----------------------------------
// Multitarget (a.k.a out of band updates)
// ----------------------------------------------------------------------
// As the HTML spec says that element IDs must be unique,
// this extension replaces elements that have corresponding elements in
// the response with the same IDs.
const mainTargetConsumed = !!frame.contentDocument.querySelector(
frame.contentWindow.location.hash || null
);
const elements = [...frame.contentDocument.querySelectorAll("[id]")];
// reverse order so we extract children before parents
for (const element of elements.reverse()) {
document.getElementById(element.id)?.replaceWith(element);
}
if (mainTargetConsumed) return;
// --------------------------------->8-----------------------------------

document
.querySelector(frame.contentWindow.location.hash || null)
?.replaceWith(...frame.contentDocument.body.children);
});
}
</script>
<iframe hidden name="htmz" onload="window.htmz(this)"></iframe>
</div>

<div id="extension-description">
<h2>multitarget</h2>
<p>
This extension allows you to target multiple elements in a single request.
</p>
</div>

<a href="content.html#content" target="htmz">Load content</a>
<div id="content"></div>
<div id="alert"></div>

<style>
#alert:not(:empty) {
position: absolute;
bottom: 0;
left: 0;
margin: 0.5rem;
padding: 0.5rem;
background: #afa;
animation: alert 0.5s cubic-bezier(0.4, 1.5, 0.4, 1);
}
@keyframes alert {
from {
transform: scale(0.0001);
}
}
</style>
3 changes: 3 additions & 0 deletions docs/extensions/no-history/cat.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div id="content">
<strong>Felis</strong> est animalis elegantissimum et misteriosum. Felis est quadrupes, sed parvus et gracilis. Habent corpora flexilia et caudam longam. Oculi eorum sunt acuti et vigilantes, quae adiuvant eos in venatione. Felium natura est curiosa et independens, saepe explorans loca nova et exspectans tempus opportunitatis ad venandum. Multae sunt species felium, ab domesticis ad agrestes, et varietas colorum eorum est mirabilis. In multis culturis, felis est considerata animal domesticum, adfertque suavitatem et companiam in domum hominum.
</div>
Loading

0 comments on commit 1330ccb

Please sign in to comment.