forked from Kalabasa/htmz
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
579 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
Oops, something went wrong.