diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/aggregate.js | 2 | ||||
-rw-r--r-- | src/content/dependencies/transformContent.js | 89 | ||||
-rw-r--r-- | src/find.js | 8 | ||||
-rw-r--r-- | src/replacer.js | 170 | ||||
-rw-r--r-- | src/static/css/site.css | 23 | ||||
-rwxr-xr-x | src/upd8.js | 1 | ||||
-rw-r--r-- | src/urls-default.yaml | 2 | ||||
-rw-r--r-- | src/write/bind-utilities.js | 2 |
8 files changed, 220 insertions, 77 deletions
diff --git a/src/aggregate.js b/src/aggregate.js index cb806e89..d5ea2d73 100644 --- a/src/aggregate.js +++ b/src/aggregate.js @@ -604,7 +604,7 @@ export function showAggregate(topError, { headerPart += ` ${colors.dim(tracePart)}`; } - const head1 = level % 2 === 0 ? '\u21aa' : colors.dim('\u21aa'); + const head1 = '\u21aa'; const bar1 = ' '; const causePart = diff --git a/src/content/dependencies/transformContent.js b/src/content/dependencies/transformContent.js index 69ecf5a4..8ba052fd 100644 --- a/src/content/dependencies/transformContent.js +++ b/src/content/dependencies/transformContent.js @@ -1,5 +1,6 @@ import {basename} from 'node:path'; +import {logWarn} from '#cli'; import {bindFind} from '#find'; import {replacerSpec, parseContentNodes} from '#replacer'; @@ -62,20 +63,30 @@ export default { Object.values(replacerSpec) .map(description => description.link) .filter(Boolean)), + 'image', 'generateTextWithTooltip', 'generateTooltip', 'linkExternal', ], - extraDependencies: ['html', 'language', 'to', 'wikiData'], + extraDependencies: [ + 'html', + 'language', + 'niceShowAggregate', + 'to', + 'wikiData', + ], sprawl(wikiData, content) { const find = bindFind(wikiData); - const parsedNodes = parseContentNodes(content ?? ''); + const {result: parsedNodes, error} = + parseContentNodes(content ?? '', {errorMode: 'return'}); return { + error, + nodes: parsedNodes .map(node => { if (node.type !== 'tag') { @@ -189,6 +200,9 @@ export default { return { content, + error: + sprawl.error, + nodes: sprawl.nodes .map(node => { @@ -301,7 +315,12 @@ export default { }, }, - generate(data, relations, slots, {html, language, to}) { + generate(data, relations, slots, {html, language, niceShowAggregate, to}) { + if (data.error) { + logWarn`Error in content text.`; + niceShowAggregate(data.error); + } + let imageIndex = 0; let internalLinkIndex = 0; let externalLinkIndex = 0; @@ -360,9 +379,8 @@ export default { height && {height}, style && {style}, - align === 'center' && - !link && - {class: 'align-center'}, + align && !link && + {class: 'align-' + align}, pixelate && {class: 'pixelate'}); @@ -373,8 +391,8 @@ export default { {href: link}, {target: '_blank'}, - align === 'center' && - {class: 'align-center'}, + align && + {class: 'align-' + align}, {title: language.encapsulate('misc.external.opensInNewTab', capsule => @@ -424,8 +442,8 @@ export default { inline: false, data: html.tag('div', {class: 'content-image-container'}, - align === 'center' && - {class: 'align-center'}, + align && + {class: 'align-' + align}, image), }; @@ -437,22 +455,31 @@ export default { ? to('media.path', node.src.slice('media/'.length)) : node.src); - const {width, height, align, pixelate} = node; + const {width, height, align, inline, pixelate} = node; - const content = - html.tag('div', {class: 'content-video-container'}, - align === 'center' && - {class: 'align-center'}, + const video = + html.tag('video', + src && {src}, + width && {width}, + height && {height}, - html.tag('video', - src && {src}, - width && {width}, - height && {height}, + {controls: true}, - {controls: true}, + align && inline && + {class: 'align-' + align}, + + pixelate && + {class: 'pixelate'}); + + const content = + (inline + ? video + : html.tag('div', {class: 'content-video-container'}, + align && + {class: 'align-' + align}, + + video)); - pixelate && - {class: 'pixelate'})); return { type: 'processed-video', @@ -466,15 +493,14 @@ export default { ? to('media.path', node.src.slice('media/'.length)) : node.src); - const {align, inline} = node; + const {align, inline, nameless} = node; const audio = html.tag('audio', src && {src}, - align === 'center' && - inline && - {class: 'align-center'}, + align && inline && + {class: 'align-' + align}, {controls: true}); @@ -482,13 +508,14 @@ export default { (inline ? audio : html.tag('div', {class: 'content-audio-container'}, - align === 'center' && - {class: 'align-center'}, + align && + {class: 'align-' + align}, [ - html.tag('a', {class: 'filename'}, - src && {href: src}, - language.sanitize(basename(node.src))), + !nameless && + html.tag('a', {class: 'filename'}, + src && {href: src}, + language.sanitize(basename(node.src))), audio, ])); diff --git a/src/find.js b/src/find.js index d5ef400d..8f2170d4 100644 --- a/src/find.js +++ b/src/find.js @@ -391,7 +391,13 @@ function findMixedHelper(config) { }); } - return byDirectory[referenceType][directory]; + const match = byDirectory[referenceType][directory]; + + if (match) { + return match.thing; + } else { + return null; + } }, matchByName: diff --git a/src/replacer.js b/src/replacer.js index 0698eced..bf28061c 100644 --- a/src/replacer.js +++ b/src/replacer.js @@ -8,7 +8,7 @@ import * as marked from 'marked'; import * as html from '#html'; -import {escapeRegex, typeAppearance} from '#sugar'; +import {empty, escapeRegex, typeAppearance} from '#sugar'; import {matchMarkdownLinks} from '#wiki-data'; export const replacerSpec = { @@ -526,6 +526,7 @@ export function postprocessComments(inputNodes) { function postprocessHTMLTags(inputNodes, tagName, callback) { const outputNodes = []; + const errors = []; const lastNode = inputNodes.at(-1); @@ -593,10 +594,16 @@ function postprocessHTMLTags(inputNodes, tagName, callback) { return false; })(); - outputNodes.push( - callback(attributes, { - inline, - })); + try { + outputNodes.push( + callback(attributes, { + inline, + })); + } catch (caughtError) { + errors.push(new Error( + `Failed to process ${match[0]}`, + {cause: caughtError})); + } // No longer at the start of a line after the tag - there will at // least be text with only '\n' before the next of this tag that's @@ -619,15 +626,33 @@ function postprocessHTMLTags(inputNodes, tagName, callback) { outputNodes.push(node); } + if (!empty(errors)) { + throw new AggregateError( + errors, + `Errors postprocessing <${tagName}> tags`); + } + return outputNodes; } +function complainAboutMediaSrc(src) { + if (!src) { + throw new Error(`Missing "src" attribute`); + } + + if (src.startsWith('/media/')) { + throw new Error(`Start "src" with "media/", not "/media/"`); + } +} + export function postprocessImages(inputNodes) { return postprocessHTMLTags(inputNodes, 'img', (attributes, {inline}) => { const node = {type: 'image'}; node.src = attributes.get('src'); + complainAboutMediaSrc(node.src); + node.inline = attributes.get('inline') ?? inline; if (attributes.get('link')) node.link = attributes.get('link'); @@ -648,10 +673,13 @@ export function postprocessImages(inputNodes) { export function postprocessVideos(inputNodes) { return postprocessHTMLTags(inputNodes, 'video', - attributes => { + (attributes, {inline}) => { const node = {type: 'video'}; node.src = attributes.get('src'); + complainAboutMediaSrc(node.src); + + node.inline = attributes.get('inline') ?? inline; if (attributes.get('width')) node.width = parseInt(attributes.get('width')); if (attributes.get('height')) node.height = parseInt(attributes.get('height')); @@ -668,8 +696,12 @@ export function postprocessAudios(inputNodes) { const node = {type: 'audio'}; node.src = attributes.get('src'); + complainAboutMediaSrc(node.src); + node.inline = attributes.get('inline') ?? inline; + if (attributes.get('align')) node.align = attributes.get('align'); + if (attributes.get('nameless')) node.nameless = true; return node; }); @@ -821,54 +853,108 @@ export function postprocessExternalLinks(inputNodes) { return outputNodes; } -export function parseContentNodes(input) { +export function parseContentNodes(input, { + errorMode = 'throw', +} = {}) { if (typeof input !== 'string') { throw new TypeError(`Expected input to be string, got ${typeAppearance(input)}`); } - try { - let output = parseNodes(input, 0); - output = postprocessComments(output); - output = postprocessImages(output); - output = postprocessVideos(output); - output = postprocessAudios(output); - output = postprocessHeadings(output); - output = postprocessSummaries(output); - output = postprocessExternalLinks(output); - return output; - } catch (errorNode) { - if (errorNode.type !== 'error') { - throw errorNode; - } + let result = null, error = null; - const { - i, - data: {message}, - } = errorNode; + process: { + try { + result = parseNodes(input, 0); + } catch (caughtError) { + if (caughtError.type === 'error') { + const {i, data: {message}} = caughtError; - let lineStart = input.slice(0, i).lastIndexOf('\n'); - if (lineStart >= 0) { - lineStart += 1; - } else { - lineStart = 0; + let lineStart = input.slice(0, i).lastIndexOf('\n'); + if (lineStart >= 0) { + lineStart += 1; + } else { + lineStart = 0; + } + + let lineEnd = input.slice(i).indexOf('\n'); + if (lineEnd >= 0) { + lineEnd += i; + } else { + lineEnd = input.length; + } + + const line = input.slice(lineStart, lineEnd); + + const cursor = i - lineStart; + + error = + new SyntaxError( + `Parse error (at pos ${i}): ${message}\n` + + line + `\n` + + '-'.repeat(cursor) + '^'); + } else { + error = caughtError; + } + + // A parse error means there's no output to continue with at all, + // so stop here. + break process; } - let lineEnd = input.slice(i).indexOf('\n'); - if (lineEnd >= 0) { - lineEnd += i; - } else { - lineEnd = input.length; + const postprocessErrors = []; + + for (const postprocess of [ + postprocessComments, + postprocessImages, + postprocessVideos, + postprocessAudios, + postprocessHeadings, + postprocessSummaries, + postprocessExternalLinks, + ]) { + try { + result = postprocess(result); + } catch (caughtError) { + const error = + new Error( + `Error in step ${`"${postprocess.name}"`}`, + {cause: caughtError}); + + error[Symbol.for('hsmusic.aggregate.translucent')] = true; + + postprocessErrors.push(error); + } } - const line = input.slice(lineStart, lineEnd); + if (!empty(postprocessErrors)) { + error = + new AggregateError( + postprocessErrors, + `Errors postprocessing content text`); - const cursor = i - lineStart; + error[Symbol.for('hsmusic.aggregate.translucent')] = 'single'; + } + } + + if (errorMode === 'throw') { + if (error) { + throw error; + } else { + return result; + } + } else if (errorMode === 'return') { + if (!result) { + result = [{ + i: 0, + iEnd: input.length, + type: 'text', + data: input, + }]; + } - throw new SyntaxError([ - `Parse error (at pos ${i}): ${message}`, - line, - '-'.repeat(cursor) + '^', - ].join('\n')); + return {error, result}; + } else { + throw new Error(`Unknown errorMode ${errorMode}`); } } diff --git a/src/static/css/site.css b/src/static/css/site.css index fe16f5d2..f3e0cb2c 100644 --- a/src/static/css/site.css +++ b/src/static/css/site.css @@ -1840,12 +1840,20 @@ p.image-details.origin-details .origin-details { margin-bottom: 1.5em; } -a.align-center, img.align-center, audio.align-center { +.content-image-container.align-full { + width: 100%; +} + +a.align-center, img.align-center, audio.align-center, video.align-center { display: block; margin-left: auto; margin-right: auto; } +a.align-full, img.align-full, video.align-full { + width: 100%; +} + center { margin-top: 1em; margin-bottom: 1em; @@ -2650,6 +2658,7 @@ html[data-url-key="localized.listing"][data-url-value0="tags/network"] dl dt:las .content-video-container, .content-audio-container { width: fit-content; + max-width: 100%; background-color: var(--dark-color); border: 2px solid var(--primary-color); border-radius: 2.5px 2.5px 3px 3px; @@ -2659,6 +2668,7 @@ html[data-url-key="localized.listing"][data-url-value0="tags/network"] dl dt:las .content-video-container video, .content-audio-container audio { display: block; + max-width: 100%; } .content-video-container.align-center, @@ -2667,6 +2677,11 @@ html[data-url-key="localized.listing"][data-url-value0="tags/network"] dl dt:las margin-right: auto; } +.content-video-container.align-full, +.content-audio-container.align-full { + width: 100%; +} + .content-audio-container .filename { color: white; font-family: monospace; @@ -2733,6 +2748,12 @@ img { object-fit: cover; } +p > img { + max-width: 100%; + object-fit: contain; + height: auto; +} + .image-inner-area::after { content: ""; display: block; diff --git a/src/upd8.js b/src/upd8.js index 40a25dfb..145a4f43 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -3207,6 +3207,7 @@ async function main() { developersComment, languages, missingImagePaths, + niceShowAggregate, thumbsCache, urlSpec, urls, diff --git a/src/urls-default.yaml b/src/urls-default.yaml index 74225efd..cbdd8a23 100644 --- a/src/urls-default.yaml +++ b/src/urls-default.yaml @@ -11,7 +11,7 @@ yamlAliases: # part of a build. This is so that multiple builds of a wiki can coexist # served from the same server / file system root: older builds' HTML files # refer to earlier values of STATIC_VERSION, avoiding name collisions. - - &staticVersion 5p1 + - &staticVersion 5p2 data: prefix: 'data/' diff --git a/src/write/bind-utilities.js b/src/write/bind-utilities.js index d55ab215..afbf8b2f 100644 --- a/src/write/bind-utilities.js +++ b/src/write/bind-utilities.js @@ -24,6 +24,7 @@ export function bindUtilities({ language, languages, missingImagePaths, + niceShowAggregate, pagePath, pagePathStringFromRoot, thumbsCache, @@ -42,6 +43,7 @@ export function bindUtilities({ language, languages, missingImagePaths, + niceShowAggregate, pagePath, pagePathStringFromRoot, thumb, |