« 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/generateAlbumTrackListItem.js2
-rw-r--r--src/content/dependencies/generateCoverArtwork.js24
-rw-r--r--src/content/dependencies/generateFlashInfoPage.js2
-rw-r--r--src/content/dependencies/generateNearbyTrackList.js44
-rw-r--r--src/content/dependencies/generateReferencedTracksList.js29
-rw-r--r--src/content/dependencies/generateTrackInfoPage.js17
-rw-r--r--src/content/dependencies/generateTrackInfoPageOtherReleasesLine.js80
-rw-r--r--src/content/dependencies/generateTrackInfoPageOtherReleasesList.js83
-rw-r--r--src/content/dependencies/generateTrackInfoPagePreviousProductionLine.js37
-rw-r--r--src/content/dependencies/generateTrackList.js29
-rw-r--r--src/content/dependencies/generateTrackListDividedByGroups.js6
-rw-r--r--src/content/dependencies/generateTrackListItem.js62
-rw-r--r--src/content/dependencies/image.js78
-rw-r--r--src/content/dependencies/linkThing.js5
14 files changed, 346 insertions, 152 deletions
diff --git a/src/content/dependencies/generateAlbumTrackListItem.js b/src/content/dependencies/generateAlbumTrackListItem.js
index ab8d477d..68722a83 100644
--- a/src/content/dependencies/generateAlbumTrackListItem.js
+++ b/src/content/dependencies/generateAlbumTrackListItem.js
@@ -40,7 +40,7 @@ export default {
 
   generate: (data, relations, slots) =>
     relations.item.slots({
-      showArtists: true,
+      showArtists: 'auto',
 
       showDuration:
         (slots.collapseDurationScope === 'track'
diff --git a/src/content/dependencies/generateCoverArtwork.js b/src/content/dependencies/generateCoverArtwork.js
index 89b66ce0..616b3c95 100644
--- a/src/content/dependencies/generateCoverArtwork.js
+++ b/src/content/dependencies/generateCoverArtwork.js
@@ -122,6 +122,30 @@ export default {
                 thumb: 'medium',
                 reveal: true,
                 link: true,
+
+                responsiveThumb: true,
+                responsiveSizes:
+                  // No clamp(), min(), or max() here because Safari.
+                  // The boundaries here are mostly experimental, apart from
+                  // the ones which flat-out switch layouts.
+
+                  // Layout - Thin (phones)
+                  // Most of viewport width
+                  '(max-width: 600px) 90vw,\n' +
+
+                  // Layout - Medium
+                  // Sidebar is hidden; content area is by definition
+                  // most of the viewport
+                  '(max-width: 640px) 220px,\n' +
+                  '(max-width: 800px) 36vw,\n' +
+                  '(max-width: 850px) 280px,\n' +
+
+                  // Layout - Wide
+                  // Sidebar is visible; content area has its own maximum
+                  // Assume the sidebar is at minimum width
+                  '(max-width: 880px) 220px,\n' +
+                  '(max-width: 1050pz) calc(0.40 * (90vw - 150px - 10px)),\n' +
+                  '280px',
               }),
 
               slots.showOriginDetails &&
diff --git a/src/content/dependencies/generateFlashInfoPage.js b/src/content/dependencies/generateFlashInfoPage.js
index 86ec6648..935ffdc6 100644
--- a/src/content/dependencies/generateFlashInfoPage.js
+++ b/src/content/dependencies/generateFlashInfoPage.js
@@ -63,7 +63,7 @@ export default {
       relation('generateFlashNavAccent', flash),
 
     featuredTracksList:
-      relation('generateTrackList', flash.featuredTracks),
+      relation('generateTrackList', flash.featuredTracks, []),
 
     contributorContributionList:
       relation('generateContributionList', flash.contributorContribs),
diff --git a/src/content/dependencies/generateNearbyTrackList.js b/src/content/dependencies/generateNearbyTrackList.js
new file mode 100644
index 00000000..56ab2df5
--- /dev/null
+++ b/src/content/dependencies/generateNearbyTrackList.js
@@ -0,0 +1,44 @@
+export default {
+  query: (tracks, contextTrack, _contextContributions) => ({
+    presentedTracks:
+      (contextTrack
+        ? tracks.map(track =>
+            track.otherReleases.find(({album}) => album === contextTrack.album) ??
+            track)
+        : tracks),
+  }),
+
+  relations: (relation, query, _tracks, _contextTrack, contextContributions) => ({
+    items:
+      query.presentedTracks
+        .map(track => relation('generateTrackListItem', track, contextContributions)),
+  }),
+
+  slots: {
+    showArtists: {
+      validate: v => v.is(true, false, 'auto'),
+      default: 'auto',
+    },
+
+    showDuration: {
+      type: 'boolean',
+      default: false,
+    },
+
+    colorMode: {
+      validate: v => v.is('none', 'track', 'line'),
+      default: 'track',
+    },
+  },
+
+  generate: (relations, slots, {html}) =>
+    html.tag('ul',
+      {[html.onlyIfContent]: true},
+
+      relations.items.map(item =>
+        item.slots({
+          showArtists: slots.showArtists,
+          showDuration: slots.showDuration,
+          colorMode: slots.colorMode,
+        }))),
+};
diff --git a/src/content/dependencies/generateReferencedTracksList.js b/src/content/dependencies/generateReferencedTracksList.js
new file mode 100644
index 00000000..1d566ce9
--- /dev/null
+++ b/src/content/dependencies/generateReferencedTracksList.js
@@ -0,0 +1,29 @@
+export default {
+  relations: (relation, track) => ({
+    previousProductionTrackList:
+      relation('generateNearbyTrackList',
+        track.previousProductionTracks,
+        track,
+        track.artistContribs),
+
+    referencedTrackList:
+      relation('generateNearbyTrackList',
+        track.referencedTracks,
+        track,
+        []),
+  }),
+
+  generate: (relations, {html, language}) =>
+    html.tag('ul', {[html.onlyIfContent]: true}, [
+      html.inside(relations.previousProductionTrackList)
+        .map(li => html.inside(li))
+        .map(label =>
+          html.tag('li',
+            language.$('trackList.item.previousProduction',
+              {track: label}))),
+
+      html.inside(relations.referencedTrackList),
+    ]),
+};
+
+
diff --git a/src/content/dependencies/generateTrackInfoPage.js b/src/content/dependencies/generateTrackInfoPage.js
index 92e00a41..d3c2d766 100644
--- a/src/content/dependencies/generateTrackInfoPage.js
+++ b/src/content/dependencies/generateTrackInfoPage.js
@@ -80,17 +80,20 @@ export default {
     readCommentaryLine:
       relation('generateReadCommentaryLine', track),
 
-    otherReleasesList:
-      relation('generateTrackInfoPageOtherReleasesList', track),
+    otherReleasesLine:
+      relation('generateTrackInfoPageOtherReleasesLine', track),
+
+    previousProductionLine:
+      relation('generateTrackInfoPagePreviousProductionLine', track),
 
     contributorContributionList:
       relation('generateContributionList', track.contributorContribs),
 
     referencedTracksList:
-      relation('generateTrackList', track.referencedTracks, track),
+      relation('generateReferencedTracksList', track),
 
     sampledTracksList:
-      relation('generateTrackList', track.sampledTracks, track),
+      relation('generateNearbyTrackList', track.sampledTracks, track, []),
 
     referencedByTracksList:
       relation('generateTrackListDividedByGroups',
@@ -228,7 +231,11 @@ export default {
                   })),
             ])),
 
-          relations.otherReleasesList,
+          html.tag('p', {[html.onlyIfContent]: true},
+            relations.otherReleasesLine),
+
+          html.tag('p', {[html.onlyIfContent]: true},
+            relations.previousProductionLine),
 
           html.tags([
             relations.contentHeading.clone()
diff --git a/src/content/dependencies/generateTrackInfoPageOtherReleasesLine.js b/src/content/dependencies/generateTrackInfoPageOtherReleasesLine.js
new file mode 100644
index 00000000..1793b73f
--- /dev/null
+++ b/src/content/dependencies/generateTrackInfoPageOtherReleasesLine.js
@@ -0,0 +1,80 @@
+import {onlyItem, stitchArrays} from '#sugar';
+
+export default {
+  query(track) {
+    const query = {};
+
+    query.singleSingle =
+      onlyItem(
+        track.otherReleases.filter(track => track.album.style === 'single'));
+
+    query.regularReleases =
+      (query.singleSingle
+        ? track.otherReleases.filter(track => track !== query.singleSingle)
+        : track.otherReleases);
+
+    return query;
+  },
+
+  relations: (relation, query, _track) => ({
+    singleLink:
+      (query.singleSingle
+        ? relation('linkTrack', query.singleSingle)
+        : null),
+
+    trackLinks:
+      query.regularReleases
+        .map(track => relation('linkTrack', track)),
+  }),
+
+  data: (query, _track) => ({
+    albumNames:
+      query.regularReleases
+        .map(track => track.album.name),
+
+    albumColors:
+      query.regularReleases
+        .map(track => track.album.color),
+  }),
+
+  generate: (data, relations, {html, language}) =>
+    language.encapsulate('releaseInfo.alsoReleased', capsule =>
+      language.encapsulate(capsule, workingCapsule => {
+        const workingOptions = {};
+
+        let any = false;
+
+        const albumList =
+          language.formatConjunctionList(
+            stitchArrays({
+              trackLink: relations.trackLinks,
+              albumName: data.albumNames,
+              albumColor: data.albumColors,
+            }).map(({trackLink, albumName, albumColor}) =>
+                trackLink.slots({
+                  content: language.sanitize(albumName),
+                  color: albumColor,
+                })));
+
+        if (!html.isBlank(albumList)) {
+          any = true;
+          workingCapsule += '.onAlbums';
+          workingOptions.albums = albumList;
+        }
+
+        if (relations.singleLink) {
+          any = true;
+          workingCapsule += '.asSingle';
+          workingOptions.single =
+            relations.singleLink.slots({
+              content: language.$(capsule, 'single'),
+            });
+        }
+
+        if (any) {
+          return language.$(workingCapsule, workingOptions);
+        } else {
+          return html.blank();
+        }
+      })),
+};
diff --git a/src/content/dependencies/generateTrackInfoPageOtherReleasesList.js b/src/content/dependencies/generateTrackInfoPageOtherReleasesList.js
deleted file mode 100644
index ca6c3fb7..00000000
--- a/src/content/dependencies/generateTrackInfoPageOtherReleasesList.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import {onlyItem, stitchArrays} from '#sugar';
-
-export default {
-  query(track) {
-    const query = {};
-
-    query.singleSingle =
-      onlyItem(
-        track.otherReleases.filter(track => track.album.style === 'single'));
-
-    query.regularReleases =
-      (query.singleSingle
-        ? track.otherReleases.filter(track => track !== query.singleSingle)
-        : track.otherReleases);
-
-    return query;
-  },
-
-  relations: (relation, query, _track) => ({
-    singleLink:
-      (query.singleSingle
-        ? relation('linkTrack', query.singleSingle)
-        : null),
-
-    trackLinks:
-      query.regularReleases
-        .map(track => relation('linkTrack', track)),
-  }),
-
-  data: (query, _track) => ({
-    albumNames:
-      query.regularReleases
-        .map(track => track.album.name),
-
-    albumColors:
-      query.regularReleases
-        .map(track => track.album.color),
-  }),
-
-  generate: (data, relations, {html, language}) =>
-    html.tag('p',
-      {[html.onlyIfContent]: true},
-
-      language.encapsulate('releaseInfo.alsoReleased', capsule =>
-        language.encapsulate(capsule, workingCapsule => {
-          const workingOptions = {};
-
-          let any = false;
-
-          const albumList =
-            language.formatConjunctionList(
-              stitchArrays({
-                trackLink: relations.trackLinks,
-                albumName: data.albumNames,
-                albumColor: data.albumColors,
-              }).map(({trackLink, albumName, albumColor}) =>
-                  trackLink.slots({
-                    content: language.sanitize(albumName),
-                    color: albumColor,
-                  })));
-
-          if (!html.isBlank(albumList)) {
-            any = true;
-            workingCapsule += '.onAlbums';
-            workingOptions.albums = albumList;
-          }
-
-          if (relations.singleLink) {
-            any = true;
-            workingCapsule += '.asSingle';
-            workingOptions.single =
-              relations.singleLink.slots({
-                content: language.$(capsule, 'single'),
-              });
-          }
-
-          if (any) {
-            return language.$(workingCapsule, workingOptions);
-          } else {
-            return html.blank();
-          }
-        }))),
-};
diff --git a/src/content/dependencies/generateTrackInfoPagePreviousProductionLine.js b/src/content/dependencies/generateTrackInfoPagePreviousProductionLine.js
new file mode 100644
index 00000000..b2f50cf3
--- /dev/null
+++ b/src/content/dependencies/generateTrackInfoPagePreviousProductionLine.js
@@ -0,0 +1,37 @@
+import {stitchArrays} from '#sugar';
+import {getKebabCase} from '#wiki-data';
+
+export default {
+  relations: (relation, track) => ({
+    trackLinks:
+      track.followingProductionTracks
+        .map(track => relation('linkTrack', track)),
+
+    albumLinks:
+      track.followingProductionTracks
+        .map(following =>
+          (following.album !== track.album &&
+           getKebabCase(following.name) === getKebabCase(track.name)
+
+            ? relation('linkAlbum', following.album)
+            : null)),
+  }),
+
+  generate: (relations, {language}) =>
+    language.encapsulate('releaseInfo.previousProduction', capsule =>
+      language.$(capsule, {
+        [language.onlyIfOptions]: ['tracks'],
+
+        tracks:
+          stitchArrays({
+            trackLink: relations.trackLinks,
+            albumLink: relations.albumLinks,
+          }).map(({trackLink, albumLink}) =>
+              (albumLink
+                ? language.$(capsule, 'trackOnAlbum', {
+                    track: trackLink,
+                    album: albumLink,
+                  })
+                : trackLink)),
+      })),
+};
diff --git a/src/content/dependencies/generateTrackList.js b/src/content/dependencies/generateTrackList.js
index e30feb23..c259c914 100644
--- a/src/content/dependencies/generateTrackList.js
+++ b/src/content/dependencies/generateTrackList.js
@@ -1,20 +1,21 @@
 export default {
-  query: (tracks, contextTrack) => ({
-    presentedTracks:
-      (contextTrack
-        ? tracks.map(track =>
-            track.otherReleases.find(({album}) => album === contextTrack.album) ??
-            track)
-        : tracks),
-  }),
-
-  relations: (relation, query, _tracks, _contextTrack) => ({
+  relations: (relation, tracks, contextContributions) => ({
     items:
-      query.presentedTracks
-        .map(track => relation('generateTrackListItem', track, [])),
+      tracks.map(track =>
+        relation('generateTrackListItem', track, contextContributions)),
   }),
 
   slots: {
+    showArtists: {
+      validate: v => v.is(true, false, 'auto'),
+      default: 'auto',
+    },
+
+    showDuration: {
+      type: 'boolean',
+      default: false,
+    },
+
     colorMode: {
       validate: v => v.is('none', 'track', 'line'),
       default: 'track',
@@ -27,8 +28,8 @@ export default {
 
       relations.items.map(item =>
         item.slots({
-          showArtists: true,
-          showDuration: false,
+          showArtists: slots.showArtists,
+          showDuration: slots.showDuration,
           colorMode: slots.colorMode,
         }))),
 };
diff --git a/src/content/dependencies/generateTrackListDividedByGroups.js b/src/content/dependencies/generateTrackListDividedByGroups.js
index d7342891..419d7c0f 100644
--- a/src/content/dependencies/generateTrackListDividedByGroups.js
+++ b/src/content/dependencies/generateTrackListDividedByGroups.js
@@ -45,7 +45,7 @@ export default {
   relations: (relation, query, sprawl, tracks, contextTrack) => ({
     flatList:
       (empty(sprawl.divideTrackListsByGroups)
-        ? relation('generateTrackList', tracks, contextTrack)
+        ? relation('generateNearbyTrackList', tracks, contextTrack, [])
         : null),
 
     contentHeading:
@@ -57,12 +57,12 @@ export default {
 
     groupedTrackLists:
       query.groupedTracks
-        .map(tracks => relation('generateTrackList', tracks, contextTrack)),
+        .map(tracks => relation('generateNearbyTrackList', tracks, contextTrack, [])),
 
     ungroupedTrackList:
       (empty(query.ungroupedTracks)
         ? null
-        : relation('generateTrackList', query.ungroupedTracks, contextTrack)),
+        : relation('generateNearbyTrackList', query.ungroupedTracks, contextTrack, [])),
   }),
 
   data: (query, _sprawl, _tracks) => ({
diff --git a/src/content/dependencies/generateTrackListItem.js b/src/content/dependencies/generateTrackListItem.js
index 9de9c3a6..c4b7c21e 100644
--- a/src/content/dependencies/generateTrackListItem.js
+++ b/src/content/dependencies/generateTrackListItem.js
@@ -3,12 +3,18 @@ export default {
     trackLink:
       relation('linkTrack', track),
 
-    credit:
+    contextualCredit:
       relation('generateArtistCredit',
         track.artistContribs,
         contextContributions,
         track.artistText),
 
+    acontextualCredit:
+      relation('generateArtistCredit',
+        track.artistContribs,
+        [],
+        track.artistText),
+
     colorStyle:
       relation('generateColorStyleAttribute', track.color),
 
@@ -27,12 +33,11 @@ export default {
   }),
 
   slots: {
-    // showArtists enables showing artists *at all.* It doesn't take precedence
-    // over behavior which automatically collapses (certain) artists because of
-    // provided context contributions.
+    // true always shows artists, false never does; 'auto' shows only if
+    // the track's artists differ from the given context contributions.
     showArtists: {
-      type: 'boolean',
-      default: true,
+      validate: v => v.is(true, false, 'auto'),
+      default: 'auto',
     },
 
     // If true and the track doesn't have a duration, a missing-duration cue
@@ -72,24 +77,33 @@ export default {
                 : relations.missingDuration);
           }
 
-          const artistCapsule = language.encapsulate(itemCapsule, 'withArtists');
-
-          relations.credit.setSlots({
-            normalStringKey:
-              artistCapsule + '.by',
-
-            featuringStringKey:
-              artistCapsule + '.featuring',
-
-            normalFeaturingStringKey:
-              artistCapsule + '.by.featuring',
-          });
-
-          if (!html.isBlank(relations.credit)) {
-            workingCapsule += '.withArtists';
-            workingOptions.by =
-              html.tag('span', {class: 'by'},
-                relations.credit);
+          const chosenCredit =
+            (slots.showArtists === true
+              ? relations.acontextualCredit
+           : slots.showArtists === 'auto'
+              ? relations.contextualCredit
+              : null);
+
+          if (chosenCredit) {
+            const artistCapsule = language.encapsulate(itemCapsule, 'withArtists');
+
+            chosenCredit.setSlots({
+              normalStringKey:
+                artistCapsule + '.by',
+
+              featuringStringKey:
+                artistCapsule + '.featuring',
+
+              normalFeaturingStringKey:
+                artistCapsule + '.by.featuring',
+            });
+
+            if (!html.isBlank(chosenCredit)) {
+              workingCapsule += '.withArtists';
+              workingOptions.by =
+                html.tag('span', {class: 'by'},
+                  chosenCredit);
+            }
           }
 
           return language.$(workingCapsule, workingOptions);
diff --git a/src/content/dependencies/image.js b/src/content/dependencies/image.js
index 1b6b08dd..d979b0bc 100644
--- a/src/content/dependencies/image.js
+++ b/src/content/dependencies/image.js
@@ -28,6 +28,8 @@ export default {
 
   slots: {
     thumb: {type: 'string'},
+    responsiveThumb: {type: 'boolean', default: false},
+    responsiveSizes: {type: 'string'},
 
     reveal: {type: 'boolean', default: true},
     lazy: {type: 'boolean', default: false},
@@ -199,31 +201,29 @@ export default {
     // so it won't be set if thumbnails aren't available.
     let revealSrc = null;
 
+    let originalDimensions;
+    let availableThumbs;
+    let selectedThumbtack;
+
+    const getThumbSrc = (thumbtack) =>
+      to('thumb.path', mediaSrc.replace(/\.(png|jpg)$/, `.${thumbtack}.jpg`));
+
     // If thumbnails are available *and* being used, calculate thumbSrc,
     // and provide some attributes relevant to the large image overlay.
     if (hasThumbnails && slots.thumb) {
-      const selectedSize =
+      selectedThumbtack =
         getThumbnailEqualOrSmaller(slots.thumb, mediaSrc);
 
-      const mediaSrcJpeg =
-        mediaSrc.replace(/\.(png|jpg)$/, `.${selectedSize}.jpg`);
-
       displaySrc =
-        to('thumb.path', mediaSrcJpeg);
+        getThumbSrc(selectedThumbtack);
 
       if (willReveal) {
-        const miniSize =
-          getThumbnailEqualOrSmaller('mini', mediaSrc);
-
-        const mediaSrcJpeg =
-          mediaSrc.replace(/\.(png|jpg)$/, `.${miniSize}.jpg`);
-
         revealSrc =
-          to('thumb.path', mediaSrcJpeg);
+          getThumbSrc(getThumbnailEqualOrSmaller('mini', mediaSrc));
       }
 
-      const originalDimensions = getDimensionsOfImagePath(mediaSrc);
-      const availableThumbs = getThumbnailsAvailableForDimensions(originalDimensions);
+      originalDimensions = getDimensionsOfImagePath(mediaSrc);
+      availableThumbs = getThumbnailsAvailableForDimensions(originalDimensions);
 
       const fileSize =
         (willLink && mediaSrc
@@ -239,11 +239,54 @@ export default {
         !empty(availableThumbs) &&
           {'data-thumbs':
               availableThumbs
-                .map(([name, size]) => `${name}:${size}`)
+                .map(([tack, size]) => `${tack}:${size}`)
                 .join(' ')},
       ]);
     }
 
+    let displayStaticImg =
+      html.tag('img',
+        imgAttributes,
+        {src: displaySrc});
+
+    if (hasThumbnails && slots.responsiveThumb) responsive: {
+      if (slots.lazy) {
+        logWarn`${'responsiveThumb'} and ${'lazy'} are used together, but not compatible`;
+        break responsive;
+      }
+
+      if (!slots.thumb) {
+        logWarn`${'responsiveThumb'} must be used alongside a default ${'thumb'}`;
+        break responsive;
+      }
+
+      const srcset = [
+        // Never load the original source, which might be a very large
+        // uncompressed file. Bah!
+        /* [originalSrc, `${Math.min(...originalDimensions)}w`], */
+
+        ...availableThumbs.map(([tack, size]) =>
+          [getThumbSrc(tack), `${Math.floor(0.95 * size)}w`]),
+
+        // fallback
+        [displaySrc],
+      ].map(line => line.join(' ')).join(',\n');
+
+      displayStaticImg =
+        html.tag('img',
+          imgAttributes,
+
+          {sizes:
+            (slots.responsiveSizes.match(/(?=(?:,|^))\s*\S/)
+                // slot provided fallback size
+              ? slots.responsiveSizes
+                // default fallback size
+              : slots.responsiveSizes + ',\n' +
+                new Map(availableThumbs).get(selectedThumbtack) + 'px')},
+
+          {srcset});
+    }
+
     if (!displaySrc) {
       return (
         prepare(
@@ -252,10 +295,7 @@ export default {
     }
 
     const images = {
-      displayStatic:
-        html.tag('img',
-          imgAttributes,
-          {src: displaySrc}),
+      displayStatic: displayStaticImg,
 
       displayLazy:
         slots.lazy &&
diff --git a/src/content/dependencies/linkThing.js b/src/content/dependencies/linkThing.js
index 7784afe7..166a857d 100644
--- a/src/content/dependencies/linkThing.js
+++ b/src/content/dependencies/linkThing.js
@@ -77,14 +77,15 @@ export default {
     const linkAttributes = slots.attributes;
     const wrapperAttributes = html.attributes();
 
+    const name =
+      relations.name.slot('preferShortName', slots.preferShortName);
+
     const showShortName =
       slots.preferShortName &&
      !data.nameText &&
       data.nameShort &&
       data.nameShort !== data.name;
 
-    const name = relations.name;
-
     const showWikiTooltip =
       (slots.tooltipStyle === 'auto'
         ? showShortName