summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author(quasar) nebula <towerofnix@gmail.com>2021-05-04 21:38:23 -0300
committer(quasar) nebula <towerofnix@gmail.com>2021-05-04 21:38:23 -0300
commitdfeb496e8593e28049617af32742a967e94cddff (patch)
tree5427d6f393d4ee79901b0af2325f91a421b05341
parent6deb774fdab0bfdacfad8010ad742be3f3d6ef7f (diff)
relatively acceptable HTML utility shenanigans
-rw-r--r--upd8-util.js5
-rwxr-xr-xupd8.js217
2 files changed, 150 insertions, 72 deletions
diff --git a/upd8-util.js b/upd8-util.js
index abeed6c..4c4186f 100644
--- a/upd8-util.js
+++ b/upd8-util.js
@@ -416,3 +416,8 @@ module.exports.promisifyProcess = function(proc, showLogging = true) {
// Stolen from jq! Which pro8a8ly stole the concept from other places. Nice.
module.exports.withEntries = (obj, fn) => Object.fromEntries(fn(Object.entries(obj)));
+
+// Nothin' more to it than what it says. Runs a function in-place. Provides an
+// altern8tive syntax to the usual IIFEs (e.g. (() => {})()) when you want to
+// open a scope and run some statements while inside an existing expression.
+module.exports.call = fn => fn();
diff --git a/upd8.js b/upd8.js
index 68d95ba..e17005d 100755
--- a/upd8.js
+++ b/upd8.js
@@ -107,6 +107,7 @@ const unlink = util.promisify(fs.unlink);
const {
cacheOneArg,
+ call,
chunkByConditions,
chunkByProperties,
curry,
@@ -183,6 +184,99 @@ let queueSize;
let languages;
+const html = {
+ // Non-comprehensive. ::::P
+ selfClosingTags: ['br', 'img'],
+
+ // Pass to tag() as an attri8utes key to make tag() return a 8lank string
+ // if the provided content is empty. Useful for when you'll only 8e showing
+ // an element according to the presence of content that would 8elong there.
+ onlyIfContent: Symbol(),
+
+ tag(tagName, ...args) {
+ const selfClosing = html.selfClosingTags.includes(tagName);
+
+ let openTag;
+ let content;
+ let attrs;
+
+ if (typeof args[0] === 'object' && !Array.isArray(args[0])) {
+ attrs = args[0];
+ content = args[1];
+ } else {
+ content = args[0];
+ }
+
+ if (selfClosing && content) {
+ throw new Error(`Tag <${tagName}> is self-closing but got content!`);
+ }
+
+ if (attrs?.[html.onlyIfContent] && !content) {
+ return '';
+ }
+
+ if (attrs) {
+ const attrString = html.attributes(args[0]);
+ if (attrString) {
+ openTag = `${tagName} ${attrString}`;
+ }
+ }
+
+ if (!openTag) {
+ openTag = tagName;
+ }
+
+ if (Array.isArray(content)) {
+ content = content.filter(Boolean).join('\n');
+ }
+
+ if (content) {
+ if (content.includes('\n')) {
+ return fixWS`
+ <${openTag}>
+ ${content}
+ </${tagName}>
+ `;
+ } else {
+ return `<${openTag}>${content}</${tagName}>`;
+ }
+ } else {
+ if (selfClosing) {
+ return `<${openTag}>`;
+ } else {
+ return `<${openTag}></${tagName}>`;
+ }
+ }
+ },
+
+ escapeAttributeValue(value) {
+ return value
+ .replaceAll('"', '&quot;')
+ .replaceAll("'", '&apos;');
+ },
+
+ attributes(attribs) {
+ return Object.entries(attribs)
+ .map(([ key, val ]) => {
+ if (!val)
+ return [key, val];
+ else if (typeof val === 'string' || typeof val === 'boolean')
+ return [key, val];
+ else if (typeof val === 'number')
+ return [key, val.toString()];
+ else if (Array.isArray(val))
+ return [key, val.join(' ')];
+ else
+ throw new Error(`Attribute value for ${key} should be primitive or array, got ${typeof val}`);
+ })
+ .filter(([ key, val ]) => val)
+ .map(([ key, val ]) => (typeof val === 'boolean'
+ ? `${key}`
+ : `${key}="${html.escapeAttributeValue(val)}"`))
+ .join(' ');
+ }
+};
+
const urlSpec = {
data: {
prefix: 'data/',
@@ -276,11 +370,12 @@ const linkHelper = (hrefFn, {color = true, attr = null} = {}) =>
class: className = '',
hash = ''
}) => (
- `<a href="${hrefFn(thing, {to}) + (hash ? (hash.startsWith('#') ? '' : '#') + hash : '')}" ${attributes({
+ html.tag('a', {
...attr ? attr(thing) : {},
+ href: hrefFn(thing, {to}) + (hash ? (hash.startsWith('#') ? '' : '#') + hash : ''),
style: color ? getLinkThemeString(thing) : '',
class: className
- })}>${text || thing.name}</a>`
+ }, text || thing.name)
);
const linkDirectory = (key, {expose = null, attr = null, ...conf} = {}) =>
@@ -2031,19 +2126,6 @@ function stringifyArtistData() {
}, stringifyIndent);
}
-function escapeAttributeValue(value) {
- return value
- .replaceAll('"', '&quot;')
- .replaceAll("'", '&apos;');
-}
-
-function attributes(attribs) {
- return Object.entries(attribs)
- .filter(([ key, val ]) => val !== '')
- .map(([ key, val ]) => `${key}="${escapeAttributeValue(val)}"`)
- .join(' ');
-}
-
function img({
src = '',
alt = '',
@@ -2063,7 +2145,7 @@ function img({
const originalSrc = src;
const thumbSrc = thumbKey ? thumb[thumbKey](src) : src;
- const imgAttributes = attributes({
+ const imgAttributes = html.attributes({
id: link ? '' : id,
class: className,
alt,
@@ -2083,36 +2165,35 @@ function img({
return nonlazyHTML;
}
- function wrap(html, hide = false) {
- html = fixWS`
- <div class="image-inner-area">${html}</div>
- `;
+ function wrap(input, hide = false) {
+ let wrapped = input;
- html = fixWS`
- <div class="image-container">${html}</div>
- `;
+ wrapped = `<div class="image-inner-area">${wrapped}</div>`;
+ wrapped = `<div class="image-container">${wrapped}</div>`;
if (reveal) {
- html = fixWS`
+ wrapped = fixWS`
<div class="reveal">
- ${html}
+ ${wrapped}
<span class="reveal-text">${reveal}</span>
</div>
`;
}
if (willSquare) {
- html = fixWS`<div ${classes('square', hide && !willLink && 'js-hide')}><div class="square-content">${html}</div></div>`;
+ wrapped = html.tag('div', {class: 'square-content'}, wrapped);
+ wrapped = html.tag('div', {class: ['square', hide && !willLink && 'js-hide']}, wrapped);
}
if (willLink) {
- html = `<a ${classes('box', hide && 'js-hide')} ${attributes({
+ wrapped = html.tag('a', {
id,
+ class: ['box', hide && 'js-hide'],
href: typeof link === 'string' ? link : originalSrc
- })}>${html}</a>`;
+ }, wrapped);
}
- return html;
+ return wrapped;
}
}
@@ -2420,29 +2501,27 @@ writePage.html = (pageFn, {paths, strings, to}) => {
navLinkParts.push(part);
}
- const navContentHTML = [
- links.length && fixWS`
- <h2 class="highlight-last-link">
- ${navLinkParts.join('\n')}
- </h2>
- `,
+ const navHTML = html.tag('nav', {
+ [html.onlyIfContent]: true,
+ id: 'header',
+ class: nav.classes
+ }, [
+ links.length && html.tag('h2', {class: 'highlight-last-link'}, navLinkParts),
nav.content
- ].filter(Boolean).join('\n');
-
- const navHTML = navContentHTML && fixWS`
- <nav id="header" ${classes(...nav.classes || [])}>
- ${navContentHTML}
- </nav>
- `;
+ ]);
- const bannerHTML = banner.position && banner.src && fixWS`
- <div id="banner" ${classes(...banner.classes || [])}>
- <img ${attributes({
- src: banner.src,
- alt: banner.alt
- })} width="1100" height="200">
- </div>
- `;
+ const bannerHTML = banner.position && banner.src && html.tag('div',
+ {
+ id: 'banner',
+ class: banner.classes
+ },
+ html.tag('img', {
+ src: banner.src,
+ alt: banner.alt,
+ width: 1100,
+ height: 200
+ })
+ );
const layoutHTML = [
navHTML,
@@ -2490,7 +2569,7 @@ writePage.html = (pageFn, {paths, strings, to}) => {
return filterEmptyLines(fixWS`
<!DOCTYPE html>
- <html ${attributes({
+ <html ${html.attributes({
lang: strings.code,
'data-rebase-localized': to('localized.root'),
'data-rebase-shared': to('shared.root'),
@@ -2501,7 +2580,7 @@ writePage.html = (pageFn, {paths, strings, to}) => {
<title>${title}</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
- ${Object.entries(meta).filter(([ key, value ]) => value).map(([ key, value ]) => `<meta ${key}="${escapeAttributeValue(value)}">`).join('\n')}
+ ${Object.entries(meta).filter(([ key, value ]) => value).map(([ key, value ]) => `<meta ${key}="${html.escapeAttributeValue(value)}">`).join('\n')}
${canonical && `<link rel="canonical" href="${canonical}">`}
<link rel="stylesheet" href="${to('shared.staticFile', `site.css?${CACHEBUST}`)}">
${(theme || stylesheet) && fixWS`
@@ -2512,7 +2591,7 @@ writePage.html = (pageFn, {paths, strings, to}) => {
`}
<script src="${to('shared.staticFile', `lazy-loading.js?${CACHEBUST}`)}"></script>
</head>
- <body ${attributes({style: body.style || ''})}>
+ <body ${html.attributes({style: body.style || ''})}>
<div id="page-container">
${mainHTML && fixWS`
<div id="skippers">
@@ -3271,25 +3350,19 @@ function writeTrackPage(track) {
.flatMap(track => track.flashes.map(flash => ({flash, as: track}))));
}
- const generateTrackList = (tracks, {strings, to}) => fixWS`
- <ul>
- ${tracks.map(track =>
- // vim doesnt like this code much lol
- (({
- line = strings('trackList.item.withArtists', {
- track: strings.link.track(track, {to}),
- by: `<span class="by">${strings('trackList.item.withArtists.by', {
- artists: getArtistString(track.artists, {strings, to})
- })}</span>`
- })
- }) => (
- (track.aka
- ? `<li class="rerelease">${strings('trackList.item.rerelease', {track: line})}</li>`
- : `<li>${line}</li>`)
- ))({})
- ).join('\n')}
- </ul>
- `;
+ const generateTrackList = (tracks, {strings, to}) => html.tag('ul',
+ tracks.map(track => {
+ const line = strings('trackList.item.withArtists', {
+ track: strings.link.track(track, {to}),
+ by: `<span class="by">${strings('trackList.item.withArtists.by', {
+ artists: getArtistString(track.artists, {strings, to})
+ })}</span>`
+ });
+ return (track.aka
+ ? `<li class="rerelease">${strings('trackList.item.rerelease', {track: line})}</li>`
+ : `<li>${line}</li>`);
+ })
+ );
const hasCommentary = track.commentary || otherReleases.some(t => t.commentary);
const generateCommentary = ({strings, to}) => transformMultiline(