|
<!doctype html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> |
|
<title>HTML Reference</title> |
|
<link rel="icon" type="image/svg+xml" href="https://favicon.potherca.workers.dev/38"> |
|
<link rel="preconnect" href="https://fonts.googleapis.com"> |
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&family=Noto+Sans+Mono&family=Noto+Sans&display=swap" rel="stylesheet"> |
|
|
|
<style> |
|
/*=======( Variables )====================================================*/ |
|
:root { |
|
--background-color: Canvas; |
|
--text-color: CanvasText; |
|
} |
|
|
|
/*=======( General Styling )==============================================*/ |
|
a { |
|
color: inherit; |
|
} |
|
|
|
body { |
|
background-color: var(--background-color); |
|
color-scheme: light dark; |
|
color: var(--text-color); |
|
font-family: 'Noto Sans', sans-serif; |
|
line-height: 1.5; |
|
margin: 0; |
|
padding: 0; |
|
scroll-behavior: smooth; |
|
} |
|
|
|
/*=======( Footer Styling )===============================================*/ |
|
footer { |
|
padding: 2rem; |
|
text-align: center; |
|
} |
|
|
|
/*=======( Header Styling )===============================================*/ |
|
header { |
|
position: sticky; |
|
top: 0; |
|
z-index: 10; |
|
background: linear-gradient(to bottom, var(--background-color) 50%, rgba(0, 0, 0, 0) 100%); |
|
} |
|
|
|
/*-------( Title )--------------------------------------------------------*/ |
|
h1 { |
|
text-align: center; |
|
} |
|
|
|
/*=======( Main Styling )=================================================*/ |
|
/*-------( Elements List )------------------------------------------------*/ |
|
.elements { |
|
list-style: none; |
|
margin: 0 auto; |
|
max-width: 80rem; |
|
padding: 0 2rem; |
|
} |
|
|
|
.element { |
|
border-radius: 0.25rem; |
|
border: 1px solid #ccc; |
|
left: 4em; |
|
margin: 0.85rem 0; |
|
max-width: calc(100% - 8em); |
|
padding: 0.35rem; |
|
position: relative; |
|
} |
|
.element:hover { |
|
box-shadow: 0 0 2em #ee8; |
|
} |
|
|
|
.element:hover .tag-anchor { |
|
visibility: visible; |
|
} |
|
|
|
.tag-anchor { |
|
display: block; |
|
position: absolute; |
|
right: 0; |
|
text-decoration: none; |
|
top: 0; |
|
visibility: hidden; |
|
} |
|
|
|
/*-------( Individual Tags )----------------------------------------------*/ |
|
.tag::after { |
|
content: '>'; |
|
} |
|
|
|
.tag::before { |
|
content: '<'; |
|
} |
|
|
|
.tag * { |
|
display: inline; |
|
} |
|
|
|
.no-categories { |
|
display: none; |
|
} |
|
|
|
.element:hover .tag-categories, |
|
.element:hover .tag-children, |
|
.element:hover .tag-parents, |
|
.element:hover .no-children, |
|
.element:hover .no-parents { |
|
height: initial; |
|
} |
|
|
|
.element:hover .tag-categories::before, |
|
.element:hover .tag-children::before, |
|
.element:hover .tag-parents::before, |
|
.element:hover .no-children::before, |
|
.element:hover .no-parents::before, |
|
.tag-categories, |
|
.tag-children, |
|
.tag-parents, |
|
.no-children, |
|
.no-parents { |
|
height: 0; |
|
overflow: hidden; |
|
border: none; |
|
} |
|
|
|
.tag-categories, |
|
.tag-children, |
|
.tag-parents, |
|
.no-children, |
|
.no-parents { |
|
margin: 0; |
|
text-indent: 2rem; |
|
} |
|
|
|
.tag-children::before, |
|
.tag-parents::before, |
|
.no-children::before, |
|
.no-parents::before { |
|
background-color: #5c5; |
|
border-radius: 100%; |
|
border: 1px solid black; |
|
color: #fff; |
|
content: " "; |
|
display: block; |
|
font-family: "Noto Sans Mono", monospace; |
|
font-size: 0.75rem; |
|
font-weight: bold; |
|
height: 1.35em; |
|
line-height: 1.35em; |
|
position: absolute; |
|
text-align: center; |
|
text-indent: 0; |
|
text-shadow: 0 0 0.1em #000; |
|
top: 1.5em; |
|
width: 1.35em; |
|
} |
|
|
|
.no-children::before, |
|
.no-parents::before { |
|
background-color: #c55; |
|
} |
|
|
|
.tag-children::before, .no-children::before { |
|
content: "C"; |
|
left: -1.35rem; |
|
} |
|
|
|
.tag-parents::before, .no-parents::before { |
|
content: "P"; |
|
left: -2.7rem; |
|
} |
|
|
|
/*.......( Tag Attributes )...............................................*/ |
|
.tag-attributes { |
|
display: inline-block; |
|
list-style: none; |
|
margin: 0 0 0 1rem; |
|
padding: 0; |
|
text-align: justify; |
|
} |
|
|
|
.tag-attribute::after { |
|
content: '=""'; |
|
opacity: 0.35; |
|
} |
|
|
|
/*.......( Tag Description )..............................................*/ |
|
.tag-description { |
|
font-style: italic; |
|
} |
|
|
|
/*-------( Category Tooltip )------------------------------------------------*/ |
|
.category-anchor { |
|
cursor: help; |
|
text-decoration: underline; |
|
text-decoration-style: dotted; |
|
text-decoration-thickness: 1px; |
|
} |
|
|
|
/*-------( Tag Highlight )------------------------------------------------*/ |
|
.highlight { |
|
border-color: #ee8; |
|
border-radius: 0.25rem; |
|
box-shadow: 0 0 2em #ee8; |
|
scroll-margin-top: 5rem; |
|
} |
|
|
|
/*-------( Category List )------------------------------------------------*/ |
|
.category-elements { |
|
list-style: none; |
|
text-align: justify; |
|
} |
|
.category-element { |
|
display: inline-block; |
|
margin: 0 0.35rem; |
|
} |
|
|
|
.category-element:hover a { |
|
text-decoration: underline |
|
} |
|
.category-element a { |
|
text-decoration: none; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<header> |
|
<h1>HTML Reference</h1> |
|
</header> |
|
<main> |
|
<section> |
|
<h2>HTML Elements</h2> |
|
<ul data-js="elements" class="elements"></ul> |
|
</section> |
|
<section> |
|
<h2>Categories</h2> |
|
<ul data-js="categories" class="categories"></ul> |
|
</section> |
|
</main> |
|
<footer></footer> |
|
|
|
<script> |
|
function addLinks(subject, tagList, categoryList) { |
|
let html = '' |
|
|
|
subject.sort().forEach((item, index) => { |
|
if (index > 0) { |
|
html += ', ' |
|
} |
|
|
|
if (tagList.has(item)) { |
|
html += `<a href="#${item}"><${item}></a>` |
|
} else if (typeof categoryList[item] !== 'undefined') { |
|
html += `<a class="category-anchor" href="#${item}" title="<${categoryList[item].join('> <')}>">${item}</a>` |
|
} else { |
|
html += item |
|
} |
|
}) |
|
|
|
return html |
|
} |
|
|
|
function buildHtml({elements, tagList, categoryList}) { |
|
const elementsHtml = [] |
|
const categoryHtml = [] |
|
|
|
elements.forEach(tag => { |
|
let attributeHtml = '' |
|
if (tag.attributes.length > 0) { |
|
attributeHtml = `<ul class="tag-attributes"><li class="tag-attribute"> ${tag.attributes.join('</li><li class="tag-attribute"> ')}</li></ul>` |
|
} |
|
|
|
const categories = tag.categories.filter(i => i !== 'none') |
|
const children = tag.children.filter(i => i !== 'none') |
|
const parents = tag.parents.filter(i => i !== 'none') |
|
|
|
let categoriesHtml = '' |
|
let childrenHtml = '' |
|
let parentsHtml = '' |
|
|
|
if (categories.length > 0) { |
|
categoriesHtml = `Categories: ${addLinks(categories, tagList, categoryList)}` |
|
} |
|
|
|
if (children.length > 0) { |
|
childrenHtml = `Children allowed: ${addLinks(children, tagList, categoryList)}` |
|
/* Some elements are described as "transparent", meaning it is derived from its parent element. */ |
|
if (parents.length > 0) { |
|
childrenHtml = childrenHtml.replace(/transparent/g, `<span class="tag-transparent">${addLinks(parents, tagList, categoryList)}</span>`) |
|
} |
|
} |
|
|
|
if (parents.length > 0) { |
|
parentsHtml = `Child of: ${addLinks(parents, tagList, categoryList)}` |
|
} |
|
|
|
elementsHtml.push(` |
|
<li id="${tag.name}" class="element"> |
|
<hgroup class="tag"> |
|
<h2 class="tag-name">${tag.name}</h2> |
|
${attributeHtml} |
|
</hgroup> |
|
<a |
|
class="tag-anchor" |
|
href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/${tag.name}" |
|
target="_blank" |
|
title="Go to MDN documentation for <${tag.name}>" |
|
>MDN ↗️</a> |
|
<p class="tag-description">${tag.description}</p> |
|
<p class="${parents.length > 0 ? 'tag-parents' : 'no-parents'}">${parentsHtml}</p> |
|
<p class="${children.length > 0 ? 'tag-children' : 'no-children'}">${childrenHtml}</p> |
|
<p class="${categories.length > 0 ? 'tag-categories' : 'no-categories'}">${categoriesHtml}</p> |
|
</li> |
|
`) |
|
}) |
|
|
|
Object.entries(categoryList).forEach(([category, elements]) => { |
|
elements.sort() |
|
const items = ` |
|
<li id="${category}" class="category"> |
|
<h3 class="category-name">${category}</h3> |
|
<ul class="category-elements"> |
|
${elements.map(element => `<li class="category-element"><a href="#${element}"><${element}></a></li>`).join('')} |
|
</ul> |
|
</li> |
|
` |
|
categoryHtml.push(items) |
|
}) |
|
|
|
return { |
|
categories: categoryHtml, |
|
elements: elementsHtml, |
|
} |
|
} |
|
|
|
function handleHashChange(event) { |
|
document.querySelectorAll('.highlight').forEach(element => { |
|
element.classList.remove('highlight') |
|
}) |
|
const hash = document.location.hash |
|
if (hash) { |
|
const element = document.querySelector(hash) |
|
if (element) { |
|
element.classList.add('highlight') |
|
element.scrollIntoView({behavior: 'smooth', block: 'center'}) |
|
} |
|
} |
|
} |
|
|
|
function parseString(string, delimiter = ';') { |
|
return string |
|
.split(delimiter) |
|
.sort() |
|
.map(s => s.trim()) |
|
.filter(Boolean) |
|
} |
|
|
|
function parseTable(table) { |
|
const categoryList = {} |
|
const elements = [] |
|
const tagList = new Set() |
|
|
|
table.querySelectorAll('tbody tr').forEach(row => { |
|
const rows = [...row.querySelectorAll('th, td')].map(td => td.textContent.replace(/\*+/g, '').trim()) |
|
|
|
if (rows[0].toLowerCase() !== 'autonomous custom elements') { |
|
const name = rows[0].split(' ').pop() |
|
const categories = parseString(rows[2]) |
|
|
|
tagList.add(name) |
|
|
|
categories.forEach(category => { |
|
if (categoryList[category] === undefined) { |
|
categoryList[category] = [] |
|
} |
|
categoryList[category].push(name) |
|
}) |
|
|
|
elements.push({ |
|
attributes: parseString(rows[5].replace(/^globals;?/, '')), |
|
categories, |
|
children: parseString(rows[4] |
|
.replace(/^empty/, '') |
|
.replace(/ elements/, '') |
|
.replace(/ one /, ''), |
|
), |
|
description: rows[1], |
|
interface: rows[6], |
|
name, |
|
parents: parseString(rows[3]), |
|
}) |
|
} |
|
}) |
|
|
|
return { |
|
categoryList, |
|
elements, |
|
tagList, |
|
} |
|
} |
|
|
|
const fetchOptions = { |
|
headers: {'Authorization': 'BybpTN5cK4JCREbh'}, |
|
} |
|
|
|
const container = document.querySelector('[data-js="elements"]') |
|
fetch('https://curse.potherca.workers.dev/https://html.spec.whatwg.org/multipage/indices.html', fetchOptions) |
|
.then(response => response.ok ? response.text() : Promise.reject(response)) |
|
.then(text => new DOMParser().parseFromString(text, 'text/html')) |
|
.then(document => document.querySelector('table')) |
|
.then(parseTable) |
|
.then(buildHtml) |
|
.then(html => { |
|
container.insertAdjacentHTML('beforeend', html.elements.join('')) |
|
document.querySelector('[data-js="categories"]').insertAdjacentHTML('beforeend', html.categories.join('')) |
|
window.addEventListener('hashchange', handleHashChange) |
|
handleHashChange() |
|
}) |
|
.catch(error => { |
|
container.insertAdjacentHTML('afterbegin', `<li class="error">${error}</li>`) |
|
console.error(error) |
|
}) |
|
</script> |
|
</body> |
|
</html> |