From dda59b812f86bd2e92ad8c471300e055b38db166 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 15 Nov 2024 18:35:52 -0400 Subject: [PATCH 1/7] html: metatag('imaginary-sibling') --- src/util/html.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/util/html.js b/src/util/html.js index 73e088546..0d667f6cf 100644 --- a/src/util/html.js +++ b/src/util/html.js @@ -105,6 +105,12 @@ export const blockwrap = Symbol(); // considered wrappable units, not the entire element! export const chunkwrap = Symbol(); +// Don't pass this directly, use html.metatag('imaginary-sibling') instead. +// A tag without any content, which is completely ignored when serializing, +// but makes siblings with [onlyIfSiblings] feel less shy and show up on +// their own, even without a non-blank (and non-onlyIfSiblings) sibling. +export const imaginarySibling = Symbol(); + // Recursive helper function for isBlank, which basically flattens an array // and returns as soon as it finds any content - a non-blank case - and doesn't // traverse templates of its own accord. If it doesn't find directly non-blank @@ -322,6 +328,9 @@ export function metatag(identifier, ...args) { case 'chunkwrap': return new Tag(null, {[chunkwrap]: true, ...opts}, content); + case 'imaginary-sibling': + return new Tag(null, {[imaginarySibling]: true}); + default: throw new Error(`Unknown metatag "${identifier}"`); } @@ -440,6 +449,10 @@ export class Tag { // something about this tag's own content or attributes. It does *not* // account for [html.onlyIfSiblings]! + if (this.imaginarySibling) { + return true; + } + if (this.onlyIfContent && isBlank(this.content)) { return true; } @@ -555,6 +568,14 @@ export class Tag { return this.#getAttributeFlag(chunkwrap); } + set imaginarySibling(value) { + this.#setAttributeFlag(imaginarySibling, value); + } + + get imaginarySibling() { + return this.#getAttributeFlag(imaginarySibling); + } + toString() { if (this.onlyIfContent && isBlank(this.content)) { return ''; @@ -652,6 +673,11 @@ export class Tag { const nonTemplateItem = Template.resolve(item); + if (nonTemplateItem instanceof Tag && nonTemplateItem.imaginarySibling) { + seenSiblingIndependentContent = true; + continue; + } + let itemContent; try { itemContent = nonTemplateItem.toString(); From bbfe965da8d1d5a416c92c19f30cfd90194c5c1a Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 15 Nov 2024 18:38:13 -0400 Subject: [PATCH 2/7] content: generateDotSwitcherTemplate: blank if no options Options were already [html.onlyIfContent], so this seems like a bit of an oversight (it's possible for *none* of the options to have content). --- src/content/dependencies/generateDotSwitcherTemplate.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/content/dependencies/generateDotSwitcherTemplate.js b/src/content/dependencies/generateDotSwitcherTemplate.js index 745a14711..253c231f2 100644 --- a/src/content/dependencies/generateDotSwitcherTemplate.js +++ b/src/content/dependencies/generateDotSwitcherTemplate.js @@ -16,6 +16,7 @@ export default { generate: (slots, {html}) => html.tag('span', {class: 'dot-switcher'}, + {[html.onlyIfContent]: true}, {[html.noEdgeWhitespace]: true}, {[html.joinChildren]: ''}, From 011bd8f5494ae0cb7400240c469e729a1818bd34 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 15 Nov 2024 18:41:57 -0400 Subject: [PATCH 3/7] content: generateDotSwitcherTemplate: observe onlyIfSiblings --- src/content/dependencies/generateDotSwitcherTemplate.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/content/dependencies/generateDotSwitcherTemplate.js b/src/content/dependencies/generateDotSwitcherTemplate.js index 253c231f2..222059229 100644 --- a/src/content/dependencies/generateDotSwitcherTemplate.js +++ b/src/content/dependencies/generateDotSwitcherTemplate.js @@ -27,8 +27,15 @@ export default { html.tag('span', {[html.onlyIfContent]: true}, + html.resolve(option, {normalize: 'tag'}) + .onlyIfSiblings && + {[html.onlyIfSiblings]: true}, + index === slots.initialOptionIndex && {class: 'current'}, - option))), + [ + html.metatag('imaginary-sibling'), + option, + ]))), }; From a7c40198735f2b2a3d74fa3c3ad0d1703c00342d Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 15 Nov 2024 18:42:45 -0400 Subject: [PATCH 4/7] content: generateArtistNavLinks: use dots Missed this one before, whoops. --- .../dependencies/generateArtistNavLinks.js | 136 +++++++++--------- 1 file changed, 65 insertions(+), 71 deletions(-) diff --git a/src/content/dependencies/generateArtistNavLinks.js b/src/content/dependencies/generateArtistNavLinks.js index 527e47411..1b4b6eca0 100644 --- a/src/content/dependencies/generateArtistNavLinks.js +++ b/src/content/dependencies/generateArtistNavLinks.js @@ -2,43 +2,44 @@ import {empty} from '#sugar'; export default { contentDependencies: [ + 'generateInterpageDotSwitcher', 'linkArtist', 'linkArtistGallery', ], extraDependencies: ['html', 'language', 'wikiData'], - sprawl({wikiInfo}) { - return { - enableListings: wikiInfo.enableListings, - }; - }, + sprawl: ({wikiInfo}) => ({ + enableListings: + wikiInfo.enableListings, + }), - relations(relation, sprawl, artist) { - const relations = {}; + query: (_sprawl, artist) => ({ + hasGallery: + !empty(artist.albumCoverArtistContributions) || + !empty(artist.trackCoverArtistContributions), + }), - relations.artistMainLink = - relation('linkArtist', artist); + relations: (relation, query, _sprawl, artist) => ({ + switcher: + relation('generateInterpageDotSwitcher'), - relations.artistInfoLink = - relation('linkArtist', artist); + artistMainLink: + relation('linkArtist', artist), - if ( - !empty(artist.albumCoverArtistContributions) || - !empty(artist.trackCoverArtistContributions) - ) { - relations.artistGalleryLink = - relation('linkArtistGallery', artist); - } + artistInfoLink: + relation('linkArtist', artist), - return relations; - }, + artistGalleryLink: + (query.hasGallery + ? relation('linkArtistGallery', artist) + : null), + }), - data(sprawl) { - return { - enableListings: sprawl.enableListings, - }; - }, + data: (_query, sprawl) => ({ + enableListings: + sprawl.enableListings, + }), slots: { showExtraLinks: {type: 'boolean', default: false}, @@ -48,53 +49,46 @@ export default { }, }, - generate(data, relations, slots, {html, language}) { - const infoLink = - relations.artistInfoLink?.slots({ - attributes: {class: slots.currentExtra === null && 'current'}, - content: language.$('misc.nav.info'), - }); - - const {content: extraLinks = []} = - slots.showExtraLinks && - {content: [ - relations.artistGalleryLink?.slots({ - attributes: {class: slots.currentExtra === 'gallery' && 'current'}, - content: language.$('misc.nav.gallery'), - }), - ]}; - - const mostAccentLinks = [ - ...extraLinks, - ].filter(Boolean); - - // Don't show the info accent link all on its own. - const allAccentLinks = - (empty(mostAccentLinks) - ? [] - : [infoLink, ...mostAccentLinks]); - - const accent = - (empty(allAccentLinks) - ? html.blank() - : `(${language.formatUnitList(allAccentLinks)})`); - - return [ - {auto: 'home'}, - - data.enableListings && - { - path: ['localized.listingIndex'], - title: language.$('listingIndex.title'), - }, + generate: (data, relations, slots, {html, language}) => [ + {auto: 'home'}, + data.enableListings && { - accent, - html: - language.$('artistPage.nav.artist', { - artist: relations.artistMainLink, - }), + path: ['localized.listingIndex'], + title: language.$('listingIndex.title'), }, - ]; - }, + + { + html: + language.$('artistPage.nav.artist', { + artist: relations.artistMainLink, + }), + + accent: + relations.switcher.slots({ + links: [ + relations.artistInfoLink.slots({ + attributes: [ + slots.currentExtra === null && + {class: 'current'}, + + {[html.onlyIfSiblings]: true}, + ], + + content: language.$('misc.nav.info'), + }), + + slots.showExtraLinks && + relations.artistGalleryLink?.slots({ + attributes: [ + slots.currentExtra === 'gallery' && + {class: 'current'}, + ], + + content: language.$('misc.nav.gallery'), + }), + ], + }), + }, + ], }; From 9e9ad40859181e40857818b2c72d97b752ee7f27 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 15 Nov 2024 18:50:17 -0400 Subject: [PATCH 5/7] html: fix bad content check in chunkwrap setter --- src/util/html.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/html.js b/src/util/html.js index 0d667f6cf..35703d4a2 100644 --- a/src/util/html.js +++ b/src/util/html.js @@ -557,7 +557,7 @@ export class Tag { this.#setAttributeFlag(chunkwrap, value); try { - this.content = content; + this.content = this.content; } catch (error) { this.#setAttributeFlag(chunkwrap, false); throw error; From d2eeb0c79fdb891b8489d3d0d6f6656e0e26cc4a Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 15 Nov 2024 18:52:50 -0400 Subject: [PATCH 6/7] html: factor out contentful conditions in content setter So not really factored out much at all, but eh. --- src/util/html.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/util/html.js b/src/util/html.js index 35703d4a2..a21d373f2 100644 --- a/src/util/html.js +++ b/src/util/html.js @@ -401,13 +401,15 @@ export class Tag { } set content(value) { - if ( - this.selfClosing && - !(value === null || - value === undefined || - !value || - Array.isArray(value) && value.filter(Boolean).length === 0) - ) { + const contentful = + value !== null && + value !== undefined && + value && + (Array.isArray(value) + ? !empty(value.filter(Boolean)) + : true); + + if (this.selfClosing && contentful) { throw new Error(`Tag <${this.tagName}> is self-closing but got content`); } From 0bf5694063669734475b39eae393e3c3ee364fbe Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 15 Nov 2024 18:54:01 -0400 Subject: [PATCH 7/7] html: disallow content for imaginary-sibling That means it's a throw, instead of silently dropping the content, when you pass it through html.metatag('imaginary-sibling'). --- src/util/html.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/util/html.js b/src/util/html.js index a21d373f2..85464b72f 100644 --- a/src/util/html.js +++ b/src/util/html.js @@ -329,7 +329,7 @@ export function metatag(identifier, ...args) { return new Tag(null, {[chunkwrap]: true, ...opts}, content); case 'imaginary-sibling': - return new Tag(null, {[imaginarySibling]: true}); + return new Tag(null, {[imaginarySibling]: true}, content); default: throw new Error(`Unknown metatag "${identifier}"`); @@ -413,6 +413,10 @@ export class Tag { throw new Error(`Tag <${this.tagName}> is self-closing but got content`); } + if (this.imaginarySibling && contentful) { + throw new Error(`html.metatag('imaginary-sibling') can't have content`); + } + const contentArray = (Array.isArray(value) ? value.flat(Infinity).filter(Boolean) @@ -572,6 +576,12 @@ export class Tag { set imaginarySibling(value) { this.#setAttributeFlag(imaginarySibling, value); + + try { + this.content = this.content; + } catch (error) { + this.#setAttributeFlag(imaginarySibling, false); + } } get imaginarySibling() {