« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/content
diff options
context:
space:
mode:
Diffstat (limited to 'src/content')
-rw-r--r--src/content/dependencies/generateAlbumGalleryTrackGrid.js4
-rw-r--r--src/content/dependencies/generateArtTagGalleryPage.js32
-rw-r--r--src/content/dependencies/generateCoverArtwork.js11
-rw-r--r--src/content/dependencies/generateCoverArtworkArtTagDetails.js59
-rw-r--r--src/content/dependencies/generateCoverArtworkOriginDetails.js137
-rw-r--r--src/content/dependencies/generateGroupInfoPageAlbumsListItem.js3
-rw-r--r--src/content/dependencies/generatePageLayout.js38
-rw-r--r--src/content/dependencies/generateSearchSidebarBox.js20
-rw-r--r--src/content/dependencies/generateTrackInfoPage.js12
-rw-r--r--src/content/dependencies/generateTrackListItem.js3
-rw-r--r--src/content/dependencies/linkExternal.js9
-rw-r--r--src/content/dependencies/listTracksWithLyrics.js2
-rw-r--r--src/content/dependencies/transformContent.js99
13 files changed, 331 insertions, 98 deletions
diff --git a/src/content/dependencies/generateAlbumGalleryTrackGrid.js b/src/content/dependencies/generateAlbumGalleryTrackGrid.js
index 85e7576c..fb5ed7ea 100644
--- a/src/content/dependencies/generateAlbumGalleryTrackGrid.js
+++ b/src/content/dependencies/generateAlbumGalleryTrackGrid.js
@@ -69,7 +69,7 @@ export default {
       album.tracks
         .map(track => track.name),
 
-    trackArtworkArtists:
+    artworkArtists:
       query.artworks.map(artwork =>
         (query.artistsForAllTrackArtworks
           ? null
@@ -110,7 +110,7 @@ export default {
                 })),
 
           info:
-            data.trackArtworkArtists.map(artists =>
+            data.artworkArtists.map(artists =>
               language.$('misc.coverGrid.details.coverArtists', {
                 [language.onlyIfOptions]: ['artists'],
 
diff --git a/src/content/dependencies/generateArtTagGalleryPage.js b/src/content/dependencies/generateArtTagGalleryPage.js
index 344e7bda..cfd6d03e 100644
--- a/src/content/dependencies/generateArtTagGalleryPage.js
+++ b/src/content/dependencies/generateArtTagGalleryPage.js
@@ -1,5 +1,5 @@
 import {sortArtworksChronologically} from '#sort';
-import {empty, unique} from '#sugar';
+import {empty, stitchArrays, unique} from '#sugar';
 
 export default {
   contentDependencies: [
@@ -103,11 +103,15 @@ export default {
       query.allArtworks
         .map(artwork => artwork.thing.name);
 
-    data.coverArtists =
+    data.artworkArtists =
       query.allArtworks
         .map(artwork => artwork.artistContribs
           .map(contrib => contrib.artist.name));
 
+    data.artworkLabels =
+      query.allArtworks
+        .map(artwork => artwork.label)
+
     data.onlyFeaturedIndirectly =
       query.allArtworks.map(artwork =>
         !query.directArtworks.includes(artwork));
@@ -204,12 +208,24 @@ export default {
                   (onlyFeaturedIndirectly ? 'featured-indirectly' : '')),
 
               info:
-                data.coverArtists.map(names =>
-                  (names === null
-                    ? null
-                    : language.$('misc.coverGrid.details.coverArtists', {
-                        artists: language.formatUnitList(names),
-                      }))),
+                stitchArrays({
+                  artists: data.artworkArtists,
+                  label: data.artworkLabels,
+                }).map(({artists, label}) =>
+                    language.encapsulate('misc.coverGrid.details.coverArtists', workingCapsule => {
+                      const workingOptions = {};
+
+                      workingOptions[language.onlyIfOptions] = ['artists'];
+                      workingOptions.artists =
+                        language.formatUnitList(artists);
+
+                      if (label) {
+                        workingCapsule += '.customLabel';
+                        workingOptions.label = label;
+                      }
+
+                      return language.$(workingCapsule, workingOptions);
+                    })),
             }),
         ],
 
diff --git a/src/content/dependencies/generateCoverArtwork.js b/src/content/dependencies/generateCoverArtwork.js
index 3a10ab20..2bff4643 100644
--- a/src/content/dependencies/generateCoverArtwork.js
+++ b/src/content/dependencies/generateCoverArtwork.js
@@ -27,6 +27,9 @@ export default {
   }),
 
   data: (artwork) => ({
+    attachAbove:
+      artwork.attachAbove,
+
     color:
       artwork.thing.color ?? null,
 
@@ -76,7 +79,10 @@ export default {
       image.setSlot('dimensions', data.dimensions);
     }
 
-    return (
+    return html.tags([
+      data.attachAbove &&
+        html.tag('div', {class: 'cover-artwork-joiner'}),
+
       html.tag('div', {class: 'cover-artwork'},
         slots.mode === 'commentary' &&
           {class: 'commentary-art'},
@@ -116,6 +122,7 @@ export default {
               link: true,
               lazy: true,
             })
-          : html.blank())));
+          : html.blank())),
+    ]);
   },
 };
diff --git a/src/content/dependencies/generateCoverArtworkArtTagDetails.js b/src/content/dependencies/generateCoverArtworkArtTagDetails.js
index b20f599b..4d908665 100644
--- a/src/content/dependencies/generateCoverArtworkArtTagDetails.js
+++ b/src/content/dependencies/generateCoverArtworkArtTagDetails.js
@@ -1,13 +1,21 @@
-import {stitchArrays} from '#sugar';
+import {compareArrays, empty, stitchArrays} from '#sugar';
+
+function linkable(tag) {
+  return !tag.isContentWarning;
+}
 
 export default {
   contentDependencies: ['linkArtTagGallery'],
-  extraDependencies: ['html'],
+  extraDependencies: ['html', 'language'],
 
   query: (artwork) => ({
     linkableArtTags:
-      artwork.artTags
-        .filter(tag => !tag.isContentWarning),
+      artwork.artTags.filter(linkable),
+
+    mainArtworkLinkableArtTags:
+      (artwork.mainArtwork
+        ? artwork.mainArtwork.artTags.filter(linkable)
+        : null),
   }),
 
   relations: (relation, query, _artwork) => ({
@@ -16,7 +24,19 @@ export default {
         .map(tag => relation('linkArtTagGallery', tag)),
   }),
 
-  data: (query, _artwork) => {
+  data: (query, artwork) => {
+    const data = {};
+
+    data.attachAbove = artwork.attachAbove;
+
+    data.sameAsMainArtwork =
+      !artwork.isMainArtwork &&
+      query.mainArtworkLinkableArtTags &&
+      !empty(query.mainArtworkLinkableArtTags) &&
+      compareArrays(
+        query.mainArtworkLinkableArtTags,
+        query.linkableArtTags);
+
     const seenShortNames = new Set();
     const duplicateShortNames = new Set();
 
@@ -28,23 +48,28 @@ export default {
       }
     }
 
-    const preferShortName =
+    data.preferShortName =
       query.linkableArtTags
         .map(artTag => !duplicateShortNames.has(artTag.nameShort));
 
-    return {preferShortName};
+    return data;
   },
 
-  generate: (data, relations, {html}) =>
-    html.tag('ul', {class: 'image-details'},
-      {[html.onlyIfContent]: true},
+  generate: (data, relations, {html, language}) =>
+    language.encapsulate('misc.coverArtwork', capsule =>
+      html.tag('ul', {class: 'image-details'},
+        {[html.onlyIfContent]: true},
 
-      {class: 'art-tag-details'},
+        {class: 'art-tag-details'},
 
-      stitchArrays({
-        artTagLink: relations.artTagLinks,
-        preferShortName: data.preferShortName,
-      }).map(({artTagLink, preferShortName}) =>
-          html.tag('li',
-            artTagLink.slot('preferShortName', preferShortName)))),
+        (data.sameAsMainArtwork && data.attachAbove
+          ? html.blank()
+       : data.sameAsMainArtwork && relations.artTagLinks.length >= 3
+          ? language.$(capsule, 'sameTagsAsMainArtwork')
+          : stitchArrays({
+              artTagLink: relations.artTagLinks,
+              preferShortName: data.preferShortName,
+            }).map(({artTagLink, preferShortName}) =>
+                html.tag('li',
+                  artTagLink.slot('preferShortName', preferShortName)))))),
 };
diff --git a/src/content/dependencies/generateCoverArtworkOriginDetails.js b/src/content/dependencies/generateCoverArtworkOriginDetails.js
index 08a01cfe..3908414f 100644
--- a/src/content/dependencies/generateCoverArtworkOriginDetails.js
+++ b/src/content/dependencies/generateCoverArtworkOriginDetails.js
@@ -13,11 +13,18 @@ export default {
   query: (artwork) => ({
     artworkThingType:
       artwork.thing.constructor[Thing.referenceType],
+
+    attachedArtistContribs:
+      (artwork.attachedArtwork
+        ? artwork.attachedArtwork.artistContribs
+        : null)
   }),
 
   relations: (relation, query, artwork) => ({
     credit:
-      relation('generateArtistCredit', artwork.artistContribs, []),
+      relation('generateArtistCredit',
+        artwork.artistContribs,
+        query.attachedArtistContribs ?? []),
 
     source:
       relation('transformContent', artwork.source),
@@ -50,49 +57,101 @@ export default {
 
         {class: 'origin-details'},
 
-        [
-          language.encapsulate(capsule, 'artworkBy', workingCapsule => {
-            const workingOptions = {};
+        (() => {
+          relations.datetimestamp?.setSlots({
+            style: 'year',
+            tooltip: true,
+          });
+
+          const artworkBy =
+            language.encapsulate(capsule, 'artworkBy', workingCapsule => {
+              const workingOptions = {};
 
-            if (data.label) {
-              workingCapsule += '.customLabel';
-              workingOptions.label = data.label;
-            }
+              if (data.label) {
+                workingCapsule += '.customLabel';
+                workingOptions.label = data.label;
+              }
 
-            if (relations.datetimestamp) {
-              workingCapsule += '.withYear';
-              workingOptions.year =
-                relations.datetimestamp.slots({
-                  style: 'year',
-                  tooltip: true,
-                });
-            }
+              if (relations.datetimestamp) {
+                workingCapsule += '.withYear';
+                workingOptions.year = relations.datetimestamp;
+              }
 
-            return relations.credit.slots({
-              showAnnotation: true,
-              showExternalLinks: true,
-              showChronology: true,
-              showWikiEdits: true,
+              return relations.credit.slots({
+                showAnnotation: true,
+                showExternalLinks: true,
+                showChronology: true,
+                showWikiEdits: true,
 
-              trimAnnotation: false,
+                trimAnnotation: false,
 
-              chronologyKind: 'coverArt',
+                chronologyKind: 'coverArt',
 
-              normalStringKey: workingCapsule,
-              additionalStringOptions: workingOptions,
+                normalStringKey: workingCapsule,
+                additionalStringOptions: workingOptions,
+              });
             });
-          }),
-
-          pagePath[0] === 'track' &&
-          data.artworkThingType === 'album' &&
-            language.$(capsule, 'trackArtFromAlbum', {
-              album:
-                relations.albumLink.slot('color', false),
-            }),
-
-          language.$(capsule, 'source', {
-            [language.onlyIfOptions]: ['source'],
-            source: relations.source.slot('mode', 'inline'),
-          }),
-        ])),
+
+          const trackArtFromAlbum =
+            pagePath[0] === 'track' &&
+            data.artworkThingType === 'album' &&
+              language.$(capsule, 'trackArtFromAlbum', {
+                album:
+                  relations.albumLink.slot('color', false),
+              });
+
+          const source =
+            language.encapsulate(capsule, 'source', workingCapsule => {
+              const workingOptions = {
+                [language.onlyIfOptions]: ['source'],
+                source: relations.source.slot('mode', 'inline'),
+              };
+
+              if (html.isBlank(artworkBy) && data.label) {
+                workingCapsule += '.customLabel';
+                workingOptions.label = data.label;
+              }
+
+              if (html.isBlank(artworkBy) && relations.datetimestamp) {
+                workingCapsule += '.withYear';
+                workingOptions.year = relations.datetimestamp;
+              }
+
+              return language.$(workingCapsule, workingOptions);
+            });
+
+          const label =
+            html.isBlank(artworkBy) &&
+            html.isBlank(source) &&
+            language.encapsulate(capsule, 'customLabel', workingCapsule => {
+              const workingOptions = {
+                [language.onlyIfOptions]: ['label'],
+                label: data.label,
+              };
+
+              if (relations.datetimestamp) {
+                workingCapsule += '.withYear';
+                workingOptions.year = relations.datetimestamp;
+              }
+
+              return language.$(workingCapsule, workingOptions);
+            });
+
+          const year =
+            html.isBlank(artworkBy) &&
+            html.isBlank(source) &&
+            html.isBlank(label) &&
+            language.$(capsule, 'year', {
+              [language.onlyIfOptions]: ['year'],
+              year: relations.datetimestamp,
+            });
+
+          return [
+            artworkBy,
+            trackArtFromAlbum,
+            source,
+            label,
+            year,
+          ];
+        })())),
 };
diff --git a/src/content/dependencies/generateGroupInfoPageAlbumsListItem.js b/src/content/dependencies/generateGroupInfoPageAlbumsListItem.js
index 99e7e8ff..4680cb46 100644
--- a/src/content/dependencies/generateGroupInfoPageAlbumsListItem.js
+++ b/src/content/dependencies/generateGroupInfoPageAlbumsListItem.js
@@ -127,7 +127,8 @@ export default {
             workingCapsule += '.withArtists';
             workingOptions.by =
               html.tag('span', {class: 'by'},
-                html.metatag('chunkwrap', {split: ','},
+                // TODO: This is obviously evil.
+                html.metatag('chunkwrap', {split: /,| (?=and)/},
                   html.resolve(artistCredit)));
           }
 
diff --git a/src/content/dependencies/generatePageLayout.js b/src/content/dependencies/generatePageLayout.js
index 070c7c82..89fefb23 100644
--- a/src/content/dependencies/generatePageLayout.js
+++ b/src/content/dependencies/generatePageLayout.js
@@ -262,16 +262,28 @@ export default {
         ? data.canonicalBase + pagePathStringFromRoot
         : null);
 
-    const firstItemInArtworkColumn =
-      html.smooth(slots.artworkColumnContent)
-        .content[0];
-
-    const primaryCover =
-      (firstItemInArtworkColumn &&
-       html.resolve(firstItemInArtworkColumn, {normalize: 'tag'})
-         .attributes.has('class', 'cover-artwork')
-        ? firstItemInArtworkColumn
-        : null);
+    const primaryCover = (() => {
+      const apparentFirst = tag => html.smooth(tag).content[0];
+
+      const maybeTemplate =
+        apparentFirst(slots.artworkColumnContent);
+
+      if (!maybeTemplate) return null;
+
+      const maybeTemplateContent =
+        html.resolve(maybeTemplate, {normalize: 'tag'});
+
+      const maybeCoverArtwork =
+        apparentFirst(maybeTemplateContent);
+
+      if (!maybeCoverArtwork) return null;
+
+      if (maybeCoverArtwork.attributes.has('class', 'cover-artwork')) {
+        return maybeTemplate;
+      } else {
+        return null;
+      }
+    })();
 
     const titleContentsHTML =
       (html.isBlank(slots.title)
@@ -583,6 +595,11 @@ export default {
           `    background-image: url("${to('media.path', 'bg.jpg')}");\n` +
           `}`);
 
+    const goshFrigginDarnitStyleRule =
+      `.image-media-link::after {\n` +
+      `    mask-image: url("${to('staticMisc.path', 'image.svg')}");\n` +
+      `}`;
+
     const numWallpaperParts =
       html.resolve(slots.styleRules, {normalize: 'string'})
         .match(/\.wallpaper-part:nth-child/g)
@@ -733,6 +750,7 @@ export default {
                 .slot('color', slots.color ?? data.wikiColor),
 
               fallbackBackgroundStyleRule,
+              goshFrigginDarnitStyleRule,
               slots.styleRules,
             ]),
 
diff --git a/src/content/dependencies/generateSearchSidebarBox.js b/src/content/dependencies/generateSearchSidebarBox.js
index 188a678f..308a1105 100644
--- a/src/content/dependencies/generateSearchSidebarBox.js
+++ b/src/content/dependencies/generateSearchSidebarBox.js
@@ -57,6 +57,26 @@ export default {
             html.tag('template', {class: 'wiki-search-tag-result-kind-string'},
               language.$(capsule, 'artTag')),
           ]),
+
+          language.encapsulate(capsule, 'resultFilter', capsule => [
+            html.tag('template', {class: 'wiki-search-album-result-filter-string'},
+              language.$(capsule, 'album')),
+
+            html.tag('template', {class: 'wiki-search-artist-result-filter-string'},
+              language.$(capsule, 'artist')),
+
+            html.tag('template', {class: 'wiki-search-flash-result-filter-string'},
+              language.$(capsule, 'flash')),
+
+            html.tag('template', {class: 'wiki-search-group-result-filter-string'},
+              language.$(capsule, 'group')),
+
+            html.tag('template', {class: 'wiki-search-track-result-filter-string'},
+              language.$(capsule, 'track')),
+
+            html.tag('template', {class: 'wiki-search-tag-result-filter-string'},
+              language.$(capsule, 'artTag')),
+          ]),
         ],
       })),
 };
diff --git a/src/content/dependencies/generateTrackInfoPage.js b/src/content/dependencies/generateTrackInfoPage.js
index ca6f82b9..11d179ad 100644
--- a/src/content/dependencies/generateTrackInfoPage.js
+++ b/src/content/dependencies/generateTrackInfoPage.js
@@ -311,18 +311,6 @@ export default {
 
           relations.lyricsSection,
 
-          // html.tags([
-          //   relations.contentHeading.clone()
-          //     .slots({
-          //       attributes: {id: 'lyrics'},
-          //       title: language.$('releaseInfo.lyrics'),
-          //     }),
-
-          //   html.tag('blockquote',
-          //     {[html.onlyIfContent]: true},
-          //     relations.lyrics.slot('mode', 'lyrics')),
-          // ]),
-
           html.tags([
             relations.contentHeading.clone()
               .slots({
diff --git a/src/content/dependencies/generateTrackListItem.js b/src/content/dependencies/generateTrackListItem.js
index 887b6f03..3c850a18 100644
--- a/src/content/dependencies/generateTrackListItem.js
+++ b/src/content/dependencies/generateTrackListItem.js
@@ -97,7 +97,8 @@ export default {
             workingCapsule += '.withArtists';
             workingOptions.by =
               html.tag('span', {class: 'by'},
-                html.metatag('chunkwrap', {split: ','},
+                // TODO: This is obviously evil.
+                html.metatag('chunkwrap', {split: /,| (?=and)/},
                   html.resolve(relations.credit)));
           }
 
diff --git a/src/content/dependencies/linkExternal.js b/src/content/dependencies/linkExternal.js
index 073c821e..c132baaf 100644
--- a/src/content/dependencies/linkExternal.js
+++ b/src/content/dependencies/linkExternal.js
@@ -39,6 +39,11 @@ export default {
       default: false,
     },
 
+    disableBrowserTooltip: {
+      type: 'boolean',
+      default: false,
+    },
+
     tab: {
       validate: v => v.is('default', 'separate'),
       default: 'default',
@@ -111,7 +116,9 @@ export default {
       linkAttributes.add('class', 'indicate-external');
 
       let titleText;
-      if (slots.tab === 'separate') {
+      if (slots.disableBrowserTooltip) {
+        titleText = null;
+      } else if (slots.tab === 'separate') {
         if (html.isBlank(slots.content)) {
           titleText =
             language.$('misc.external.opensInNewTab.annotation');
diff --git a/src/content/dependencies/listTracksWithLyrics.js b/src/content/dependencies/listTracksWithLyrics.js
index a13a76f0..e6ab9d7d 100644
--- a/src/content/dependencies/listTracksWithLyrics.js
+++ b/src/content/dependencies/listTracksWithLyrics.js
@@ -2,7 +2,7 @@ export default {
   contentDependencies: ['listTracksWithExtra'],
 
   relations: (relation, spec) =>
-    ({page: relation('listTracksWithExtra', spec, 'lyrics', 'truthy')}),
+    ({page: relation('listTracksWithExtra', spec, 'lyrics', 'array')}),
 
   generate: (relations) =>
     relations.page,
diff --git a/src/content/dependencies/transformContent.js b/src/content/dependencies/transformContent.js
index 1bbd45e2..805c3625 100644
--- a/src/content/dependencies/transformContent.js
+++ b/src/content/dependencies/transformContent.js
@@ -46,6 +46,14 @@ function getPlaceholder(node, content) {
   return {type: 'text', data: content.slice(node.i, node.iEnd)};
 }
 
+function getArg(node, argKey) {
+  return (
+    node.data.args
+      ?.find(({key}) => key.data === argKey)
+      ?.value ??
+    null);
+}
+
 export default {
   contentDependencies: [
     ...(
@@ -53,6 +61,8 @@ export default {
         .map(description => description.link)
         .filter(Boolean)),
     'image',
+    'generateTextWithTooltip',
+    'generateTooltip',
     'linkExternal',
   ],
 
@@ -134,6 +144,30 @@ export default {
             return {i: node.i, iEnd: node.iEnd, type: 'internal-link', data};
           }
 
+          if (replacerKey === 'tooltip') {
+            // TODO: Again, no recursive nodes. Sorry!
+            // const enteredLabel = node.data.label && transformNode(node.data.label, opts);
+            const enteredLabel = node.data.label?.data;
+
+            return {
+              i: node.i,
+              iEnd: node.iEnd,
+              type: 'tooltip',
+              data: {
+                tooltip:
+                  replacerValue ?? '(empty tooltip...)',
+
+                label:
+                  enteredLabel ?? '(tooltip without label)',
+
+                link:
+                  (getArg(node, 'link')
+                    ? getArg(node, 'link')[0].data
+                    : null),
+              },
+            };
+          }
+
           // This will be another {type: 'tag'} node which gets processed in
           // generate. Extract replacerKey and replacerValue now, since it'd
           // be a pain to deal with later.
@@ -191,6 +225,12 @@ export default {
           : getPlaceholder(node, content));
 
     return {
+      textWithTooltip:
+        relation('generateTextWithTooltip'),
+
+      tooltip:
+        relation('generateTooltip'),
+
       internalLinks:
         nodes
           .filter(({type}) => type === 'internal-link')
@@ -209,11 +249,15 @@ export default {
       externalLinks:
         nodes
           .filter(({type}) => type === 'external-link')
-          .map(node => {
-            const {href} = node.data;
+          .map(({data: {href}}) =>
+            relation('linkExternal', href)),
 
-            return relation('linkExternal', href);
-          }),
+      externalLinksForTooltipNodes:
+        nodes
+          .filter(({type}) => type === 'tooltip')
+          .filter(({data}) => data.link)
+          .map(({data: {link: href}}) =>
+            relation('linkExternal', href)),
 
       images:
         nodes
@@ -259,6 +303,7 @@ export default {
     let imageIndex = 0;
     let internalLinkIndex = 0;
     let externalLinkIndex = 0;
+    let externalLinkForTooltipNodeIndex = 0;
 
     let offsetTextNode = 0;
 
@@ -548,6 +593,52 @@ export default {
             return {type: 'processed-external-link', data: externalLink};
           }
 
+          case 'tooltip': {
+            const {label, link, tooltip: tooltipContent} = node.data;
+
+            const externalLink =
+              (link
+                ? relations.externalLinksForTooltipNodes
+                    .at(externalLinkForTooltipNodeIndex++)
+                : null);
+
+            if (externalLink) {
+              externalLink.setSlots({
+                content: label,
+                fromContent: true,
+              });
+
+              if (slots.indicateExternalLinks) {
+                externalLink.setSlots({
+                  indicateExternal: true,
+                  disableBrowserTooltip: true,
+                  tab: 'separate',
+                  style: 'platform',
+                });
+              }
+            }
+
+            const textWithTooltip = relations.textWithTooltip.clone();
+            const tooltip = relations.tooltip.clone();
+
+            tooltip.setSlots({
+              attributes: {class: 'content-tooltip'},
+              content: tooltipContent, // Not sanitized!
+            });
+
+            textWithTooltip.setSlots({
+              attributes: [
+                {class: 'content-tooltip-guy'},
+                externalLink && {class: 'has-link'},
+              ],
+
+              text: externalLink ?? label,
+              tooltip,
+            });
+
+            return {type: 'processed-tooltip', data: textWithTooltip};
+          }
+
           case 'tag': {
             const {replacerKey, replacerValue} = node.data;