« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/content/dependencies/generateCoverGrid.js37
-rw-r--r--src/content/dependencies/generateExpandableGallerySection.js92
-rw-r--r--src/content/dependencies/generateGridExpando.js39
-rw-r--r--src/content/dependencies/generateGroupGalleryPageSeriesSection.js147
-rw-r--r--src/static/css/site.css74
-rw-r--r--src/static/js/client/expandable-gallery-section.js77
-rw-r--r--src/static/js/client/expandable-grid-section.js85
-rw-r--r--src/static/js/client/index.js4
-rw-r--r--src/static/js/client/quick-description.js2
-rw-r--r--src/strings-default.yaml12
10 files changed, 270 insertions, 299 deletions
diff --git a/src/content/dependencies/generateCoverGrid.js b/src/content/dependencies/generateCoverGrid.js
index 89371015..53b2b8b8 100644
--- a/src/content/dependencies/generateCoverGrid.js
+++ b/src/content/dependencies/generateCoverGrid.js
@@ -1,14 +1,16 @@
 import {empty, stitchArrays, unique} from '#sugar';
 
 export default {
-  contentDependencies: ['generateGridActionLinks'],
+  contentDependencies: ['generateGridActionLinks', 'generateGridExpando'],
   extraDependencies: ['html', 'language'],
 
-  relations(relation) {
-    return {
-      actionLinks: relation('generateGridActionLinks'),
-    };
-  },
+  relations: (relation) => ({
+    actionLinks:
+      relation('generateGridActionLinks'),
+
+    expando:
+      relation('generateGridExpando'),
+  }),
 
   slots: {
     attributes: {type: 'attributes', mutable: false},
@@ -45,6 +47,13 @@ export default {
     revealAllWarnings: {
       validate: v => v.looseArrayOf(v.isString),
     },
+
+    bottomCaption: {
+      type: 'html',
+      mutable: false,
+    },
+
+    cutIndex: {validate: v => v.isWholeNumber},
   },
 
   generate: (relations, slots, {html, language}) =>
@@ -121,6 +130,10 @@ export default {
                 (classes
                   ? {class: classes}
                   : null),
+
+                slots.cutIndex >= 1 &&
+                index >= slots.cutIndex &&
+                  {class: 'hidden-by-expandable-cut'},
               ],
 
               colorContext: 'image-box',
@@ -168,5 +181,17 @@ export default {
 
         relations.actionLinks
           .slot('actionLinks', slots.actionLinks),
+
+        (slots.cutIndex >= 1 &&
+         slots.cutIndex < slots.links.length
+          ? relations.expando.slots({
+              caption: slots.bottomCaption,
+            })
+
+       : !html.isBlank(relations.bottomCaption)
+          ? html.tag('p', {class: 'grid-caption'},
+              slots.caption)
+
+          : html.blank()),
       ]),
 };
diff --git a/src/content/dependencies/generateExpandableGallerySection.js b/src/content/dependencies/generateExpandableGallerySection.js
deleted file mode 100644
index 122ca4b1..00000000
--- a/src/content/dependencies/generateExpandableGallerySection.js
+++ /dev/null
@@ -1,92 +0,0 @@
-export default {
-  contentDependencies: ['generateContentHeading'],
-  extraDependencies: ['html', 'language'],
-
-  relations: (relation) => ({
-    contentHeading:
-      relation('generateContentHeading'),
-  }),
-
-  slots: {
-    title: {
-      type: 'html',
-      mutable: false,
-    },
-
-    contentAboveCut: {
-      type: 'html',
-      mutable: false,
-    },
-
-    contentBelowCut: {
-      type: 'html',
-      mutable: false,
-    },
-
-    caption: {
-      type: 'html',
-      mutable: false,
-    },
-
-    expandCue: {
-      type: 'html',
-      mutable: false,
-    },
-
-    collapseCue: {
-      type: 'html',
-      mutable: false,
-    },
-  },
-
-  generate: (relations, slots, {html, language}) =>
-    html.tag('section', {class: 'expandable-gallery-section'}, [
-      relations.contentHeading.slots({
-        tag: 'h2',
-        title: slots.title,
-      }),
-
-      html.tag('div', {class: 'section-content-above-cut'},
-        {[html.onlyIfContent]: true},
-
-        slots.contentAboveCut),
-
-      html.tag('div', {class: 'section-content-below-cut'},
-        {[html.onlyIfContent]: true},
-
-        !html.isBlank(slots.contentBelowCut) &&
-          {style: 'display: none'},
-
-        slots.contentBelowCut),
-
-      html.tag('div', {class: 'section-expando'},
-        {[html.onlyIfSiblings]: true},
-
-        html.tag('div', {class: 'section-expando-content'},
-          {[html.joinChildren]: html.tag('br')},
-
-          [
-            html.tag('span', {class: 'section-caption'},
-              slots.caption),
-
-            !html.isBlank(slots.contentBelowCut) &&
-              language.$('misc.coverGrid.expandCollapseCue', {
-                cue:
-                  html.tag('a', {class: 'section-expando-toggle'},
-                    {href: '#'},
-
-                    {[html.joinChildren]: ''},
-                    {[html.noEdgeWhitespace]: true},
-
-                    [
-                      html.tag('span', {class: 'section-expand-cue'},
-                        slots.expandCue),
-
-                      html.tag('span', {class: 'section-collapse-cue'},
-                        {style: 'display: none'},
-                        slots.collapseCue),
-                    ]),
-              }),
-          ])),
-    ]),
-};
diff --git a/src/content/dependencies/generateGridExpando.js b/src/content/dependencies/generateGridExpando.js
new file mode 100644
index 00000000..71c2f970
--- /dev/null
+++ b/src/content/dependencies/generateGridExpando.js
@@ -0,0 +1,39 @@
+export default {
+  extraDependencies: ['html', 'language'],
+
+  slots: {
+    caption: {type: 'html', mutable: false},
+  },
+
+  generate: (slots, {html, language}) =>
+    language.encapsulate('misc.coverGrid', capsule =>
+      html.tag('div', {class: 'grid-expando'},
+        {[html.onlyIfSiblings]: true},
+
+        html.tag('p', {class: 'grid-expando-content'},
+          {[html.joinChildren]: html.tag('br')},
+
+          [
+            html.tag('span', {class: 'grid-caption'},
+              slots.caption),
+
+            !html.isBlank(slots.contentBelowCut) &&
+              language.$(capsule, 'expandCollapseCue', {
+                cue:
+                  html.tag('a', {class: 'grid-expando-toggle'},
+                    {href: '#'},
+
+                    {[html.joinChildren]: ''},
+                    {[html.noEdgeWhitespace]: true},
+
+                    [
+                      html.tag('span', {class: 'grid-expand-cue'},
+                        language.$(capsule, 'expand')),
+
+                      html.tag('span', {class: 'grid-collapse-cue'},
+                        {style: 'display: none'},
+                        language.$(capsule, 'collapse')),
+                    ]),
+              }),
+          ]))),
+};
diff --git a/src/content/dependencies/generateGroupGalleryPageSeriesSection.js b/src/content/dependencies/generateGroupGalleryPageSeriesSection.js
index 2ccead5d..b88adfa3 100644
--- a/src/content/dependencies/generateGroupGalleryPageSeriesSection.js
+++ b/src/content/dependencies/generateGroupGalleryPageSeriesSection.js
@@ -2,7 +2,7 @@ import {sortChronologically} from '#sort';
 
 export default {
   contentDependencies: [
-    'generateExpandableGallerySection',
+    'generateContentHeading',
     'generateGroupGalleryPageAlbumGrid',
   ],
 
@@ -11,12 +11,8 @@ export default {
   query(series) {
     const query = {};
 
-    // Includes undated albums.
-    const albumsLatestFirst =
-      sortChronologically(series.albums, {latestFirst: true});
-
-    query.albumsAboveCut = albumsLatestFirst.slice(0, 4);
-    query.albumsBelowCut = albumsLatestFirst.slice(4);
+    query.albums =
+      sortChronologically(series.albums.slice(), {latestFirst: true});
 
     query.allAlbumsDated =
       series.albums.every(album => album.date);
@@ -25,13 +21,13 @@ export default {
       series.albums.some(album => !album.groups.includes(series.group));
 
     query.latestAlbum =
-      albumsLatestFirst
+      query.albums
         .filter(album => album.date)
         .at(0) ??
       null;
 
     query.earliestAlbum =
-      albumsLatestFirst
+      query.albums
         .filter(album => album.date)
         .at(-1) ??
       null;
@@ -40,17 +36,12 @@ export default {
   },
 
   relations: (relation, query, series) => ({
-    gallerySection:
-      relation('generateExpandableGallerySection'),
+    contentHeading:
+      relation('generateContentHeading'),
 
-    gridAboveCut:
+    grid:
       relation('generateGroupGalleryPageAlbumGrid',
-        query.albumsAboveCut,
-        series.group),
-
-    gridBelowCut:
-      relation('generateGroupGalleryPageAlbumGrid',
-        query.albumsBelowCut,
+        query.albums,
         series.group),
   }),
 
@@ -88,69 +79,67 @@ export default {
 
   generate: (data, relations, {html, language}) =>
     language.encapsulate('groupGalleryPage.albumSection', capsule =>
-      relations.gallerySection.slots({
-        title: data.name,
-
-        contentAboveCut: relations.gridAboveCut,
-        contentBelowCut: relations.gridBelowCut,
-
-        caption:
-          language.encapsulate(capsule, 'caption', captionCapsule =>
-            html.tags([
-              data.anyAlbumNotFromThisGroup &&
-                language.$(captionCapsule, 'seriesAlbumsNotFromGroup', {
-                  marker:
-                    language.$('misc.coverGrid.details.notFromThisGroup.marker'),
-
-                  series:
-                    html.tag('i', data.name),
-
-                  group: data.groupName,
-                }),
-
-              language.encapsulate(captionCapsule, workingCapsule => {
-                const workingOptions = {};
-
-                workingOptions.tracks =
-                  html.tag('b',
-                    language.countTracks(data.tracks, {unit: true}));
-
-                workingOptions.albums =
-                  html.tag('b',
-                    language.countAlbums(data.albums, {unit: true}));
-
-                if (data.allAlbumsDated) {
-                  const earliestDate = data.earliestAlbumDate;
-                  const latestDate = data.latestAlbumDate;
-
-                  const earliestYear = earliestDate.getFullYear();
-                  const latestYear = latestDate.getFullYear();
-
-                  if (earliestYear === latestYear) {
-                    if (data.albums === 1) {
-                      workingCapsule += '.withDate';
-                      workingOptions.date =
-                        language.formatDate(earliestDate);
+      html.tags([
+        relations.contentHeading.slots({
+          tag: 'h2',
+          title: language.sanitize(data.name),
+        }),
+
+        relations.grid.slots({
+          cutIndex: 4,
+
+          bottomCaption:
+            language.encapsulate(capsule, 'caption', captionCapsule =>
+              html.tags([
+                data.anyAlbumNotFromThisGroup &&
+                  language.$(captionCapsule, 'seriesAlbumsNotFromGroup', {
+                    marker:
+                      language.$('misc.coverGrid.details.notFromThisGroup.marker'),
+
+                    series:
+                      html.tag('i', data.name),
+
+                    group: data.groupName,
+                  }),
+
+                language.encapsulate(captionCapsule, workingCapsule => {
+                  const workingOptions = {};
+
+                  workingOptions.tracks =
+                    html.tag('b',
+                      language.countTracks(data.tracks, {unit: true}));
+
+                  workingOptions.albums =
+                    html.tag('b',
+                      language.countAlbums(data.albums, {unit: true}));
+
+                  if (data.allAlbumsDated) {
+                    const earliestDate = data.earliestAlbumDate;
+                    const latestDate = data.latestAlbumDate;
+
+                    const earliestYear = earliestDate.getFullYear();
+                    const latestYear = latestDate.getFullYear();
+
+                    if (earliestYear === latestYear) {
+                      if (data.albums === 1) {
+                        workingCapsule += '.withDate';
+                        workingOptions.date =
+                          language.formatDate(earliestDate);
+                      } else {
+                        workingCapsule += '.withYear';
+                        workingOptions.year =
+                          language.formatYear(earliestDate);
+                      }
                     } else {
-                      workingCapsule += '.withYear';
-                      workingOptions.year =
-                        language.formatYear(earliestDate);
+                      workingCapsule += '.withYearRange';
+                      workingOptions.yearRange =
+                        language.formatYearRange(earliestDate, latestDate);
                     }
-                  } else {
-                    workingCapsule += '.withYearRange';
-                    workingOptions.yearRange =
-                      language.formatYearRange(earliestDate, latestDate);
                   }
-                }
-
-                return language.$(workingCapsule, workingOptions);
-              }),
-            ], {[html.joinChildren]: html.tag('br')})),
 
-        expandCue:
-          language.$(capsule, 'expand'),
-
-        collapseCue:
-          language.$(capsule, 'collapse'),
-      })),
+                  return language.$(workingCapsule, workingOptions);
+                }),
+              ], {[html.joinChildren]: html.tag('br')})),
+        }),
+      ])),
 };
diff --git a/src/static/css/site.css b/src/static/css/site.css
index afc5cfbf..ba8ef6b1 100644
--- a/src/static/css/site.css
+++ b/src/static/css/site.css
@@ -2076,39 +2076,6 @@ ul.quick-info li:not(:last-child)::after {
   margin-bottom: 1.5em;
 }
 
-.expandable-gallery-section .section-expando {
-  margin-top: 1em;
-  margin-bottom: 2em;
-
-  display: flex;
-  flex-direction: row;
-  justify-content: space-around;
-}
-
-.expandable-gallery-section .section-expando-content {
-  text-align: center;
-  line-height: 1.5;
-}
-
-.expandable-gallery-section .section-expando-toggle {
-  text-decoration: underline;
-  text-decoration-style: dotted;
-}
-
-.expandable-gallery-section.expanded .section-content-below-cut {
-  animation: expand-gallery-section 0.8s forwards;
-}
-
-@keyframes expand-gallery-section {
-  from {
-    opacity: 0;
-  }
-
-  to {
-    opacity: 1;
-  }
-}
-
 .quick-description:not(.has-external-links-only) {
   --clamped-padding-ratio: max(var(--responsive-padding-ratio), 0.06);
   margin-left: auto;
@@ -3251,6 +3218,47 @@ video.pixelate, .pixelate video {
   --dim-color: inherit !important;
 }
 
+.grid-caption {
+  flex-basis: 100%;
+  text-align: center;
+  line-height: 1.5;
+}
+
+.grid-expando {
+  margin-top: 1em;
+  margin-bottom: 2em;
+  flex-basis: 100%;
+
+  display: flex;
+  flex-direction: row;
+  justify-content: space-around;
+}
+
+.grid-expando-content {
+  margin: 0;
+  text-align: center;
+  line-height: 1.5;
+}
+
+.grid-expando-toggle {
+  text-decoration: underline;
+  text-decoration-style: dotted;
+}
+
+.grid-item.shown-by-expandable-cut {
+  animation: expand-cover-grid 0.8s forwards;
+}
+
+@keyframes expand-cover-grid {
+  from {
+    opacity: 0;
+  }
+
+  to {
+    opacity: 1;
+  }
+}
+
 /* Carousel */
 
 .carousel-container {
diff --git a/src/static/js/client/expandable-gallery-section.js b/src/static/js/client/expandable-gallery-section.js
deleted file mode 100644
index dc83e8b7..00000000
--- a/src/static/js/client/expandable-gallery-section.js
+++ /dev/null
@@ -1,77 +0,0 @@
-/* eslint-env browser */
-
-// TODO: Combine this and quick-description.js
-
-import {cssProp} from '../client-util.js';
-
-import {stitchArrays} from '../../shared-util/sugar.js';
-
-export const info = {
-  id: 'expandableGallerySectionInfo',
-
-  sections: null,
-
-  sectionContentBelowCut: null,
-
-  sectionExpandoToggles: null,
-
-  sectionExpandCues: null,
-  sectionCollapseCues: null,
-};
-
-export function getPageReferences() {
-  info.sections =
-    Array.from(document.querySelectorAll('.expandable-gallery-section'))
-      .filter(section => section.querySelector('.section-expando-toggle'));
-
-  info.sectionContentBelowCut =
-    info.sections
-      .map(section => section.querySelector('.section-content-below-cut'));
-
-  info.sectionExpandoToggles =
-    info.sections
-      .map(section => section.querySelector('.section-expando-toggle'));
-
-  info.sectionExpandCues =
-    info.sections
-      .map(section => section.querySelector('.section-expand-cue'));
-
-  info.sectionCollapseCues =
-    info.sections
-      .map(section => section.querySelector('.section-collapse-cue'));
-}
-
-export function addPageListeners() {
-  for (const {
-    section,
-    contentBelowCut,
-    expandoToggle,
-    expandCue,
-    collapseCue,
-  } of stitchArrays({
-    section: info.sections,
-    contentBelowCut: info.sectionContentBelowCut,
-    expandoToggle: info.sectionExpandoToggles,
-    expandCue: info.sectionExpandCues,
-    collapseCue: info.sectionCollapseCues,
-  })) {
-    expandoToggle.addEventListener('click', domEvent => {
-      domEvent.preventDefault();
-
-      const collapsed =
-        cssProp(contentBelowCut, 'display') === 'none';
-
-      if (collapsed) {
-        section.classList.add('expanded');
-        cssProp(contentBelowCut, 'display', null);
-        cssProp(expandCue, 'display', 'none');
-        cssProp(collapseCue, 'display', null);
-      } else {
-        section.classList.remove('expanded');
-        cssProp(contentBelowCut, 'display', 'none');
-        cssProp(expandCue, 'display', null);
-        cssProp(collapseCue, 'display', 'none');
-      }
-    });
-  }
-}
diff --git a/src/static/js/client/expandable-grid-section.js b/src/static/js/client/expandable-grid-section.js
new file mode 100644
index 00000000..ce9a4c06
--- /dev/null
+++ b/src/static/js/client/expandable-grid-section.js
@@ -0,0 +1,85 @@
+/* eslint-env browser */
+
+import {cssProp} from '../client-util.js';
+
+import {stitchArrays} from '../../shared-util/sugar.js';
+
+export const info = {
+  id: 'expandableGallerySectionInfo',
+
+  items: null,
+  toggles: null,
+  expandCues: null,
+  collapseCues: null,
+};
+
+export function getPageReferences() {
+  const expandos =
+    Array.from(document.querySelectorAll('.grid-expando'));
+
+  const grids =
+    expandos
+      .map(expando => expando.closest('.grid-listing'));
+
+  info.items =
+    grids
+      .map(grid => grid.querySelectorAll('.grid-item'))
+      .map(items => Array.from(items));
+
+  info.toggles =
+    expandos
+      .map(expando => expando.querySelector('.grid-expando-toggle'));
+
+  info.expandCues =
+    info.toggles
+      .map(toggle => toggle.querySelector('.grid-expand-cue'));
+
+  info.collapseCues =
+    info.toggles
+      .map(toggle => toggle.querySelector('.grid-collapse-cue'));
+}
+
+export function addPageListeners() {
+  stitchArrays({
+    items: info.items,
+    toggle: info.toggles,
+    expandCue: info.expandCues,
+    collapseCue: info.collapseCues,
+  }).forEach(({
+      items,
+      toggle,
+      expandCue,
+      collapseCue,
+    }) => {
+      toggle.addEventListener('click', domEvent => {
+        domEvent.preventDefault();
+
+        const collapsed =
+          items.some(item =>
+            item.classList.contains('hidden-by-expandable-cut'));
+
+        for (const item of items) {
+          if (
+            !item.classList.contains('hidden-by-expandable-cut') &&
+            !item.classList.contains('shown-by-expandable-cut')
+          ) continue;
+
+          if (collapsed) {
+            item.classList.remove('hidden-by-expandable-cut');
+            item.classList.add('shown-by-expandable-cut');
+          } else {
+            item.classList.add('hidden-by-expandable-cut');
+            item.classList.remove('shown-by-expandable-cut');
+          }
+        }
+
+        if (collapsed) {
+          cssProp(expandCue, 'display', 'none');
+          cssProp(collapseCue, 'display', null);
+        } else {
+          cssProp(expandCue, 'display', null);
+          cssProp(collapseCue, 'display', 'none');
+        }
+      });
+    });
+}
diff --git a/src/static/js/client/index.js b/src/static/js/client/index.js
index 016ce9ad..86081b5d 100644
--- a/src/static/js/client/index.js
+++ b/src/static/js/client/index.js
@@ -11,7 +11,7 @@ import * as artistRollingWindowModule from './artist-rolling-window.js';
 import * as cssCompatibilityAssistantModule from './css-compatibility-assistant.js';
 import * as datetimestampTooltipModule from './datetimestamp-tooltip.js';
 import * as draggedLinkModule from './dragged-link.js';
-import * as expandableGallerySectionModule from './expandable-gallery-section.js';
+import * as expandableGridSectionModule from './expandable-grid-section.js';
 import * as galleryStyleSelectorModule from './gallery-style-selector.js';
 import * as hashLinkModule from './hash-link.js';
 import * as hoverableTooltipModule from './hoverable-tooltip.js';
@@ -37,7 +37,7 @@ export const modules = [
   cssCompatibilityAssistantModule,
   datetimestampTooltipModule,
   draggedLinkModule,
-  expandableGallerySectionModule,
+  expandableGridSectionModule,
   galleryStyleSelectorModule,
   hashLinkModule,
   hoverableTooltipModule,
diff --git a/src/static/js/client/quick-description.js b/src/static/js/client/quick-description.js
index 6a7a6023..cff82252 100644
--- a/src/static/js/client/quick-description.js
+++ b/src/static/js/client/quick-description.js
@@ -1,7 +1,5 @@
 /* eslint-env browser */
 
-// TODO: Combine this and expandable-gallery-section.js
-
 import {stitchArrays} from '../../shared-util/sugar.js';
 
 export const info = {
diff --git a/src/strings-default.yaml b/src/strings-default.yaml
index 4969bcff..be16ac19 100644
--- a/src/strings-default.yaml
+++ b/src/strings-default.yaml
@@ -1007,6 +1007,10 @@ misc:
       conceal: "Conceal all artworks"
       warnings: "In this gallery: {WARNINGS}"
 
+    expandCollapseCue: "({CUE})"
+    expand: "Show the rest!"
+    collapse: "Collapse these"
+
     noCoverArt: "{ALBUM}"
 
     tab:
@@ -1028,8 +1032,6 @@ misc:
 
       otherCoverArtists: "With {ARTISTS}"
 
-    expandCollapseCue: "({CUE})"
-
   albumGalleryGrid:
     noCoverArt: "{NAME}"
 
@@ -1797,12 +1799,6 @@ groupGalleryPage:
     caption.seriesAlbumsNotFromGroup: >-
       Albums marked {MARKER} are part of {SERIES}, but not from {GROUP}.
 
-    expand: >-
-      Show the rest!
-
-    collapse: >-
-      Collapse these
-
 #
 # listingIndex:
 #   The listing index page shows all available listings on the wiki,