7 Commits 60f007a20d ... 0bf5694063

Autor SHA1 Nachricht Datum
  (quasar) nebula 0bf5694063 html: disallow content for imaginary-sibling vor 1 Woche
  (quasar) nebula d2eeb0c79f html: factor out contentful conditions in content setter vor 1 Woche
  (quasar) nebula 9e9ad40859 html: fix bad content check in chunkwrap setter vor 1 Woche
  (quasar) nebula a7c4019873 content: generateArtistNavLinks: use dots vor 1 Woche
  (quasar) nebula 011bd8f549 content: generateDotSwitcherTemplate: observe onlyIfSiblings vor 1 Woche
  (quasar) nebula bbfe965da8 content: generateDotSwitcherTemplate: blank if no options vor 1 Woche
  (quasar) nebula dda59b812f html: metatag('imaginary-sibling') vor 1 Woche

+ 65 - 71
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'),
+              }),
+          ],
+        }),
+    },
+  ],
 };

+ 9 - 1
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]: ''},
 
@@ -26,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,
+            ]))),
 };

+ 46 - 8
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}, content);
+
     default:
       throw new Error(`Unknown metatag "${identifier}"`);
   }
@@ -392,16 +401,22 @@ 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`);
     }
 
+    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)
@@ -440,6 +455,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;
     }
@@ -544,7 +563,7 @@ export class Tag {
     this.#setAttributeFlag(chunkwrap, value);
 
     try {
-      this.content = content;
+      this.content = this.content;
     } catch (error) {
       this.#setAttributeFlag(chunkwrap, false);
       throw error;
@@ -555,6 +574,20 @@ export class Tag {
     return this.#getAttributeFlag(chunkwrap);
   }
 
+  set imaginarySibling(value) {
+    this.#setAttributeFlag(imaginarySibling, value);
+
+    try {
+      this.content = this.content;
+    } catch (error) {
+      this.#setAttributeFlag(imaginarySibling, false);
+    }
+  }
+
+  get imaginarySibling() {
+    return this.#getAttributeFlag(imaginarySibling);
+  }
+
   toString() {
     if (this.onlyIfContent && isBlank(this.content)) {
       return '';
@@ -652,6 +685,11 @@ export class Tag {
       const nonTemplateItem =
         Template.resolve(item);
 
+      if (nonTemplateItem instanceof Tag && nonTemplateItem.imaginarySibling) {
+        seenSiblingIndependentContent = true;
+        continue;
+      }
+
       let itemContent;
       try {
         itemContent = nonTemplateItem.toString();