From 776abf8d697716902692f357c6f179c1e681369f Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 8 Apr 2023 16:54:39 -0300 Subject: html: drastically simplify template/slot system --- .../dependencies/generateAdditionalFilesList.js | 67 ++++++++++++++++--- .../generateAlbumAdditionalFilesList.js | 24 +++---- src/content/dependencies/generateAlbumInfoPage.js | 16 ++--- .../dependencies/generateAlbumInfoPageContent.js | 27 +++++--- src/content/dependencies/generateContentHeading.js | 26 +++++--- src/content/dependencies/generateCoverArtwork.js | 49 +++++++++----- src/content/dependencies/generatePageLayout.js | 65 +++++++++++------- src/content/dependencies/image.js | 76 ++++++++++++++-------- src/content/dependencies/index.js | 10 +++ .../dependencies/linkAlbumAdditionalFile.js | 6 +- src/content/dependencies/linkTemplate.js | 50 ++++++++------ src/content/dependencies/linkThing.js | 42 ++++++++---- 12 files changed, 310 insertions(+), 148 deletions(-) (limited to 'src/content') diff --git a/src/content/dependencies/generateAdditionalFilesList.js b/src/content/dependencies/generateAdditionalFilesList.js index c51435a4..eb9fc8b0 100644 --- a/src/content/dependencies/generateAdditionalFilesList.js +++ b/src/content/dependencies/generateAdditionalFilesList.js @@ -18,15 +18,61 @@ export default { html, language, }) { - return html.template(slot => - slot('additionalFileLinks', ([fileLinks]) => - slot('additionalFileSizes', ([fileSizes]) => { - if (!fileSizes) { + const fileKeys = data.additionalFiles.flatMap(({files}) => files); + const validateFileMapping = (v, validateValue) => { + return value => { + v.isObject(value); + + // It's OK to skip values for files, but if keys are provided for files + // which don't exist, that's an error. + + const unexpectedKeys = + Object.keys(value).filter(key => !fileKeys.includes(key)) + + if (!empty(unexpectedKeys)) { + throw new TypeError(`Unexpected file keys: ${unexpectedKeys.join(', ')}`); + } + + const valueErrors = []; + for (const [fileKey, fileValue] of Object.entries(value)) { + if (fileValue === null) { + continue; + } + + try { + validateValue(fileValue); + } catch (error) { + error.message = `(${fileKey}) ` + error.message; + valueErrors.push(error); + } + } + + if (!empty(valueErrors)) { + throw new AggregateError(valueErrors, `Errors validating values`); + } + }; + }; + + return html.template({ + annotation: 'generateAdditionalFilesList', + + slots: { + fileLinks: { + validate: v => validateFileMapping(v, v.isHTML), + }, + + fileSizes: { + validate: v => validateFileMapping(v, v.isWholeNumber), + }, + }, + + content(slots) { + if (!slots.fileSizes) { return html.blank(); } const filesWithLinks = new Set( - Object.entries(fileLinks) + Object.entries(slots.fileLinks) .filter(([key, value]) => value) .map(([key]) => key)); @@ -60,15 +106,16 @@ export default { html.tag('ul', files.map(file => html.tag('li', - (fileSizes[file] + (slots.fileSizes[file] ? language.$('releaseInfo.additionalFiles.file.withSize', { - file: fileLinks[file], - size: language.formatFileSize(fileSizes[file]), + file: slots.fileLinks[file], + size: language.formatFileSize(slots.fileSizes[file]), }) : language.$('releaseInfo.additionalFiles.file', { - file: fileLinks[file], + file: slots.fileLinks[file], })))))), ])); - }))); + }, + }); }, }; diff --git a/src/content/dependencies/generateAlbumAdditionalFilesList.js b/src/content/dependencies/generateAlbumAdditionalFilesList.js index d45fb583..04e6a5f1 100644 --- a/src/content/dependencies/generateAlbumAdditionalFilesList.js +++ b/src/content/dependencies/generateAlbumAdditionalFilesList.js @@ -40,16 +40,18 @@ export default { urls, }) { return relations.additionalFilesList - .slot('additionalFileLinks', relations.additionalFileLinks) - .slot('additionalFileSizes', - Object.fromEntries(data.fileLocations.map(file => [ - file, - (data.showFileSizes - ? getSizeOfAdditionalFile( - urls - .from('media.root') - .to('media.albumAdditionalFile', data.albumDirectory, file)) - : 0), - ]))); + .slots({ + additionalFileLinks: relations.additionalFileLinks, + additionalFileSizes: + Object.fromEntries(data.fileLocations.map(file => [ + file, + (data.showFileSizes + ? getSizeOfAdditionalFile( + urls + .from('media.root') + .to('media.albumAdditionalFile', data.albumDirectory, file)) + : 0), + ])), + }); }, }; diff --git a/src/content/dependencies/generateAlbumInfoPage.js b/src/content/dependencies/generateAlbumInfoPage.js index dcd8589c..f0a23259 100644 --- a/src/content/dependencies/generateAlbumInfoPage.js +++ b/src/content/dependencies/generateAlbumInfoPage.js @@ -36,8 +36,6 @@ export default { generate(data, relations, { language, }) { - // page.title = language.$('albumPage.title', {album: data.name}); - // page.themeColor = data.color; // page.styleRules = [ @@ -45,12 +43,14 @@ export default { // relations.colorStyleRules, // ]; - // page.socialEmbed = relations.socialEmbed; - return relations.layout - .slot('title', language.$('albumPage.title', {album: data.name})) - .slot('cover', relations.content.cover) - .slot('mainContent', relations.content.main.content) - .slot('socialEmbed', relations.socialEmbed); + .slots({ + title: language.$('albumPage.title', {album: data.name}), + + cover: relations.content.cover, + mainContent: relations.content.main.content, + + // socialEmbed: relations.socialEmbed, + }); }, }; diff --git a/src/content/dependencies/generateAlbumInfoPageContent.js b/src/content/dependencies/generateAlbumInfoPageContent.js index a17a33f1..fd66f6b0 100644 --- a/src/content/dependencies/generateAlbumInfoPageContent.js +++ b/src/content/dependencies/generateAlbumInfoPageContent.js @@ -117,8 +117,10 @@ export default { const content = {}; content.cover = relations.cover - .slot('path', ['media.albumCover', data.coverArtDirectory, data.coverArtFileExtension]) - .slot('alt', language.$('misc.alt.trackCover')); + .slots({ + path: ['media.albumCover', data.coverArtDirectory, data.coverArtFileExtension], + alt: language.$('misc.alt.trackCover') + }); content.main = { headingMode: 'sticky', @@ -213,20 +215,25 @@ export default { relations.additionalFilesList && [ relations.additionalFilesHeading - .slot('id', 'additional-files') - .slot('title', - language.$('releaseInfo.additionalFiles.heading', { - additionalFiles: - language.countAdditionalFiles(data.numAdditionalFiles, {unit: true}), - })), + .slots({ + id: 'additional-files', + + title: + language.$('releaseInfo.additionalFiles.heading', { + additionalFiles: + language.countAdditionalFiles(data.numAdditionalFiles, {unit: true}), + }), + }), relations.additionalFilesList, ], data.artistCommentary && [ relations.artistCommentaryHeading - .slot('id', 'artist-commentary') - .slot('title', language.$('releaseInfo.artistCommentary')), + .slots({ + id: 'artist-commentary', + title: language.$('releaseInfo.artistCommentary') + }), html.tag('blockquote', transformMultiline(data.artistCommentary)), diff --git a/src/content/dependencies/generateContentHeading.js b/src/content/dependencies/generateContentHeading.js index baa52080..f5e4bd00 100644 --- a/src/content/dependencies/generateContentHeading.js +++ b/src/content/dependencies/generateContentHeading.js @@ -4,13 +4,23 @@ export default { ], generate({html}) { - return html.template(slot => - html.tag('p', - { - class: 'content-heading', - id: slot('id'), - tabindex: '0', - }, - slot('title'))); + return html.template({ + annotation: 'generateContentHeading', + + slots: { + title: {type: 'html'}, + id: {type: 'string'}, + }, + + content(slots) { + return html.tag('p', + { + class: 'content-heading', + id: slots.id, + tabindex: '0', + }, + slots.content); + }, + }); } } diff --git a/src/content/dependencies/generateCoverArtwork.js b/src/content/dependencies/generateCoverArtwork.js index 62fc3566..2d18fed3 100644 --- a/src/content/dependencies/generateCoverArtwork.js +++ b/src/content/dependencies/generateCoverArtwork.js @@ -23,21 +23,38 @@ export default { }, generate(relations, {html, language}) { - return html.template(slot => - html.tag('div', {id: 'cover-art-container'}, [ - relations.image - .slot('path', slot('path')) - .slot('alt', slot('alt')) - .slot('thumb', 'medium') - .slot('id', 'cover-art') - .slot('link', true) - .slot('square', true), - - !empty(relations.tagLinks) && - html.tag('p', - language.$('releaseInfo.artTags.inline', { - tags: language.formatUnitList(relations.tagLinks), - })), - ])); + return html.template({ + annotation: 'generateCoverArtwork', + + slots: { + path: { + validate: v => v.validateArrayItems(v.isString), + }, + + alt: { + type: 'string', + }, + }, + + content(slots) { + return html.tag('div', {id: 'cover-art-container'}, [ + relations.image + .slots({ + path: slots.path, + alt: slots.alt, + thumb: 'medium', + id: 'cover-art', + link: true, + square: true, + }), + + !empty(relations.tagLinks) && + html.tag('p', + language.$('releaseInfo.artTags.inline', { + tags: language.formatUnitList(relations.tagLinks), + })), + ]); + }, + }); }, }; diff --git a/src/content/dependencies/generatePageLayout.js b/src/content/dependencies/generatePageLayout.js index b27d487b..f36a7bb5 100644 --- a/src/content/dependencies/generatePageLayout.js +++ b/src/content/dependencies/generatePageLayout.js @@ -1,5 +1,3 @@ -import {empty} from '../../util/sugar.js'; - export default { extraDependencies: [ 'html', @@ -13,41 +11,63 @@ export default { language, to, }) { - return html.template(slot => - slot('title', ([...title]) => - slot('headingMode', ([headingMode = 'static']) => { + return html.template({ + annotation: 'generatePageLayout', + + slots: { + title: {type: 'html'}, + cover: {type: 'html'}, + + mainContent: {type: 'html'}, + socialEmbed: {type: 'html'}, + + headingMode: { + validate: v => v.is('sticky', 'static'), + default: 'static', + }, + + mainClasses: { + validate: v => v.arrayOf(v.isString), + default: [], + }, + }, + + content(slots) { let titleHTML = null; - if (!empty(title)) { - if (headingMode === 'sticky') { - /* - generateStickyHeadingContainer({ - coverSrc: cover.src, - coverAlt: cover.alt, - coverArtTags: cover.artTags, - title, - }) - */ - } else if (headingMode === 'static') { - titleHTML = html.tag('h1', title); + if (!html.isBlank(slots.title)) { + switch (slots.headingMode) { + case 'sticky': + /* + generateStickyHeadingContainer({ + coverSrc: cover.src, + coverAlt: cover.alt, + coverArtTags: cover.artTags, + title, + }) + */ + break; + case 'static': + titleHTML = html.tag('h1', slots.title); + break; } } const mainHTML = html.tag('main', { id: 'content', - class: slot('mainClass'), + class: slots.mainClasses, }, [ titleHTML, - slot('cover'), + slots.cover, html.tag('div', { [html.onlyIfContent]: true, class: 'main-content-container', }, - slot('mainContent')), + slots.mainContent), ]); const layoutHTML = [ @@ -135,7 +155,7 @@ export default { */ - // slot('socialEmbed'), + // slots.socialEmbed, html.tag('link', { rel: 'stylesheet', @@ -176,6 +196,7 @@ export default { ]); return documentHTML; - }))); + }, + }); }, }; diff --git a/src/content/dependencies/image.js b/src/content/dependencies/image.js index 1f904377..1960fb0a 100644 --- a/src/content/dependencies/image.js +++ b/src/content/dependencies/image.js @@ -31,39 +31,60 @@ export default { thumb, to, }) { - return html.template(slot => - slot('src', ([src]) => - slot('path', ([...path]) => - slot('thumb', ([thumbKey = '']) => - slot('link', ([link = false]) => - slot('lazy', ([lazy = false]) => - slot('square', ([willSquare = false]) => { + return html.template({ + annotation: 'image', + + slots: { + src: { + type: 'string', + }, + + path: { + validate: v => v.validateArrayItems(v.isString), + }, + + thumb: {type: 'string'}, + + link: {type: 'boolean', default: false}, + lazy: {type: 'boolean', default: false}, + square: {type: 'boolean', default: false}, + + id: {type: 'string'}, + alt: {type: 'string'}, + width: {type: 'number'}, + height: {type: 'number'}, + + missingSourceContent: {type: 'html'}, + }, + + content(slots) { let originalSrc; - if (src) { - originalSrc = src; - } else if (!empty(path)) { - originalSrc = to(...path); + if (slots.src) { + originalSrc = slots.src; + } else if (!empty(slots.path)) { + originalSrc = to(...slots.path); } else { originalSrc = ''; } const thumbSrc = originalSrc && - (thumbKey - ? thumb[thumbKey](originalSrc) + (slots.thumb + ? thumb[slots.thumb](originalSrc) : originalSrc); - const willLink = typeof link === 'string' || link; + const willLink = typeof slots.link === 'string' || slots.link; const willReveal = originalSrc && !empty(data.contentWarnings); + const willSquare = slots.square; - const idOnImg = willLink ? null : slot('id'); - const idOnLink = willLink ? slot('id') : null; + const idOnImg = willLink ? null : slots.id; + const idOnLink = willLink ? slots.id : null; if (!originalSrc) { return prepare( html.tag('div', {class: 'image-text-area'}, - slot('missingSourceContent'))); + slots.missingSourceContent)); } let fileSize = null; @@ -90,13 +111,11 @@ export default { ]; } - const className = slot('class'); const imgAttributes = { id: idOnImg, - class: className, - alt: slot('alt'), - width: slot('width'), - height: slot('height'), + alt: slots.alt, + width: slots.width, + height: slots.height, 'data-original-size': fileSize, }; @@ -108,14 +127,14 @@ export default { src: thumbSrc, })); - if (lazy) { + if (slots.lazy) { return html.tags([ html.tag('noscript', nonlazyHTML), prepare( html.tag('img', { ...imgAttributes, - class: [className, 'lazy'], + class: 'lazy', 'data-original': thumbSrc, }), true), @@ -166,8 +185,8 @@ export default { ], href: - (typeof link === 'string' - ? link + (typeof slots.link === 'string' + ? slots.link : originalSrc), }, wrapped); @@ -175,6 +194,7 @@ export default { return wrapped; } - }))))))); - }, + }, + }); + } }; diff --git a/src/content/dependencies/index.js b/src/content/dependencies/index.js index c2d88f64..f82062f7 100644 --- a/src/content/dependencies/index.js +++ b/src/content/dependencies/index.js @@ -1,6 +1,7 @@ import chokidar from 'chokidar'; import EventEmitter from 'events'; import * as path from 'path'; +import {ESLint} from 'eslint'; import {fileURLToPath} from 'url'; import contentFunction from '../../content-function.js'; @@ -35,6 +36,8 @@ export function watchContentDependencies({ close, }); + const eslint = new ESLint(); + // Watch adjacent files const metaPath = fileURLToPath(import.meta.url); const metaDirname = path.dirname(metaPath); @@ -129,6 +132,13 @@ export function watchContentDependencies({ let error = null; main: { + const eslintResults = await eslint.lintFiles([filePath]); + const eslintFormatter = await eslint.loadFormatter('stylish'); + const eslintResultText = eslintFormatter.format(eslintResults); + if (eslintResultText.trim().length) { + console.log(eslintResultText); + } + let spec; try { spec = (await import(cachebust(filePath))).default; diff --git a/src/content/dependencies/linkAlbumAdditionalFile.js b/src/content/dependencies/linkAlbumAdditionalFile.js index d1cca914..27c0ba9c 100644 --- a/src/content/dependencies/linkAlbumAdditionalFile.js +++ b/src/content/dependencies/linkAlbumAdditionalFile.js @@ -18,7 +18,9 @@ export default { generate(data, relations) { return relations.linkTemplate - .slot('path', ['media.albumAdditionalFile', data.albumDirectory, data.file]) - .slot('content', data.file); + .slots({ + path: ['media.albumAdditionalFile', data.albumDirectory, data.file], + content: data.file, + }); }, }; diff --git a/src/content/dependencies/linkTemplate.js b/src/content/dependencies/linkTemplate.js index acac99be..b87f3180 100644 --- a/src/content/dependencies/linkTemplate.js +++ b/src/content/dependencies/linkTemplate.js @@ -14,15 +14,25 @@ export default { html, to, }) { - return html.template(slot => - slot('color', ([color]) => - slot('hash', ([hash]) => - slot('href', ([href]) => - slot('path', ([...path]) => { + return html.template({ + annotation: 'linkTemplate', + + slots: { + href: {type: 'string'}, + path: {validate: v => v.validateArrayItems(v.isString)}, + hash: {type: 'string'}, + + attributes: {validate: v => v.isAttributes}, + color: {validate: v => v.isColor}, + content: {type: 'html'}, + }, + + content(slots) { + let href = slots.href; let style; - if (!href && !empty(path)) { - href = to(...path); + if (!href && !empty(slots.path)) { + href = to(...slots.path); } if (appendIndexHTML) { @@ -34,23 +44,23 @@ export default { } } - if (hash) { - href += (hash.startsWith('#') ? '' : '#') + hash; + if (slots.hash) { + href += (slots.hash.startsWith('#') ? '' : '#') + slots.hash; } - if (color) { - const {primary, dim} = getColors(color); + if (slots.color) { + const {primary, dim} = getColors(slots.color); style = `--primary-color: ${primary}; --dim-color: ${dim}`; } - return slot('attributes', ([attributes]) => - html.tag('a', - { - ...attributes ?? {}, - href, - style, - }, - slot('content'))); - }))))); + return html.tag('a', + { + ...slots.attributes ?? {}, + href, + style, + }, + slots.content); + }, + }); }, } diff --git a/src/content/dependencies/linkThing.js b/src/content/dependencies/linkThing.js index ebff6761..70c86fc4 100644 --- a/src/content/dependencies/linkThing.js +++ b/src/content/dependencies/linkThing.js @@ -1,5 +1,3 @@ -import {empty} from '../../util/sugar.js'; - export default { contentDependencies: [ 'linkTemplate', @@ -30,22 +28,40 @@ export default { generate(data, relations, {html}) { const path = [data.pathKey, data.directory]; - return html.template(slot => - slot('content', ([...content]) => - slot('preferShortName', ([preferShortName]) => { - if (empty(content)) { + return html.template({ + annotation: 'linkThing', + + slots: { + content: relations.linkTemplate.getSlotDescription('content'), + preferShortName: {type: 'boolean', default: false}, + + color: relations.linkTemplate.getSlotDescription('color'), + attributes: relations.linkTemplate.getSlotDescription('attributes'), + hash: relations.linkTemplate.getSlotDescription('hash'), + }, + + content(slots) { + let content = slots.content; + + if (html.isBlank(content)) { content = - (preferShortName + (slots.preferShortName ? data.nameShort ?? data.name : data.name); } + const color = slots.color ?? data.color ?? null; + return relations.linkTemplate - .slot('path', path) - .slot('color', slot('color', data.color)) - .slot('attributes', slot('attributes', {})) - .slot('hash', slot('hash')) - .slot('content', content); - }))); + .slots({ + path, + content, + color, + + attributes: slots.attributes, + hash: slots.hash, + }); + }, + }); }, } -- cgit 1.3.0-6-gf8a5