« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/listing-spec.js305
-rw-r--r--src/page/listing.js224
-rw-r--r--src/util/html.js18
3 files changed, 267 insertions, 280 deletions
diff --git a/src/listing-spec.js b/src/listing-spec.js
index 28f4b1ae..c1f44346 100644
--- a/src/listing-spec.js
+++ b/src/listing-spec.js
@@ -15,237 +15,198 @@ const listingSpec = [
     directory: 'albums/by-name',
     stringsKey: 'listAlbums.byName',
 
-    data({wikiData}) {
-      return sortAlphabetically(wikiData.albumData.slice());
-    },
+    data: ({wikiData: {albumData}}) =>
+      sortAlphabetically(albumData.slice()),
 
-    row(album, {link, language}) {
-      return language.$('listingPage.listAlbums.byName.item', {
+    row: (album, {language, link}) =>
+      language.$('listingPage.listAlbums.byName.item', {
         album: link.album(album),
         tracks: language.countTracks(album.tracks.length, {unit: true}),
-      });
-    },
+      }),
   },
 
   {
     directory: 'albums/by-tracks',
     stringsKey: 'listAlbums.byTracks',
 
-    data({wikiData}) {
-      return wikiData.albumData
-        .slice()
-        .sort((a, b) => b.tracks.length - a.tracks.length);
-    },
+    data: ({wikiData: {albumData}}) =>
+      albumData.slice()
+        .sort((a, b) => b.tracks.length - a.tracks.length),
 
-    row(album, {link, language}) {
-      return language.$('listingPage.listAlbums.byTracks.item', {
+    row: (album, {language, link}) =>
+      language.$('listingPage.listAlbums.byTracks.item', {
         album: link.album(album),
         tracks: language.countTracks(album.tracks.length, {unit: true}),
-      });
-    },
+      }),
   },
 
   {
     directory: 'albums/by-duration',
     stringsKey: 'listAlbums.byDuration',
 
-    data({wikiData}) {
-      return wikiData.albumData
-        .map((album) => ({album, duration: getTotalDuration(album.tracks)}))
-        .sort((a, b) => b.duration - a.duration);
-    },
+    data: ({wikiData: {albumData}}) =>
+      albumData
+        .map(album => ({
+          album,
+          duration: getTotalDuration(album.tracks),
+        }))
+        .filter(album => album.duration)
+        .sort((a, b) => b.duration - a.duration),
 
-    row({album, duration}, {link, language}) {
-      return language.$('listingPage.listAlbums.byDuration.item', {
+    row: ({album, duration}, {language, link}) =>
+      language.$('listingPage.listAlbums.byDuration.item', {
         album: link.album(album),
         duration: language.formatDuration(duration),
-      });
-    },
+      }),
   },
 
   {
     directory: 'albums/by-date',
     stringsKey: 'listAlbums.byDate',
 
-    data({wikiData}) {
-      return sortChronologically(
-        wikiData.albumData.filter((album) => album.date)
-      );
-    },
+    data: ({wikiData: {albumData}}) =>
+      sortChronologically(
+        albumData
+          .filter(album => album.date)),
 
-    row(album, {link, language}) {
-      return language.$('listingPage.listAlbums.byDate.item', {
+    row: (album, {language, link}) =>
+      language.$('listingPage.listAlbums.byDate.item', {
         album: link.album(album),
         date: language.formatDate(album.date),
-      });
-    },
+      }),
   },
 
   {
     directory: 'albums/by-date-added',
     stringsKey: 'listAlbums.byDateAdded',
 
-    data({wikiData}) {
-      return chunkByProperties(
-        wikiData.albumData
-          .filter((a) => a.dateAddedToWiki)
+    data: ({wikiData: {albumData}}) =>
+      chunkByProperties(
+        albumData
+          .filter(a => a.dateAddedToWiki)
           .sort((a, b) => {
             if (a.dateAddedToWiki < b.dateAddedToWiki) return -1;
             if (a.dateAddedToWiki > b.dateAddedToWiki) return 1;
           }),
-        ['dateAddedToWiki']
-      );
-    },
-
-    html(chunks, {link, language}) {
-      return fixWS`
-                <dl>
-                    ${chunks
-                      .map(
-                        ({dateAddedToWiki, chunk: albums}) => fixWS`
-                        <dt>${language.$(
-                          'listingPage.listAlbums.byDateAdded.date',
-                          {
-                            date: language.formatDate(dateAddedToWiki),
-                          }
-                        )}</dt>
-                        <dd><ul>
-                            ${albums
-                              .map((album) =>
-                                language.$(
-                                  'listingPage.listAlbums.byDateAdded.album',
-                                  {
-                                    album: link.album(album),
-                                  }
-                                )
-                              )
-                              .map((row) => `<li>${row}</li>`)
-                              .join('\n')}
-                        </ul></dd>
-                    `
-                      )
-                      .join('\n')}
-                </dl>
-            `;
-    },
+        ['dateAddedToWiki']),
+
+    html: (chunks, {html, language, link}) =>
+      html.tag('dl',
+        chunks.flatMap(({dateAddedToWiki, chunk: albums}) => [
+          html.tag('dt',
+            language.$('listingPage.listAlbums.byDateAdded.date', {
+              date: language.formatDate(dateAddedToWiki),
+            })),
+          html.tag('dd',
+            html.tag('ul',
+              albums.map((album) =>
+                html.tag('li',
+                  language.$('listingPage.listAlbums.byDateAdded.album', {
+                    album: link.album(album),
+                  }))))),
+        ])),
   },
 
   {
     directory: 'artists/by-name',
     stringsKey: 'listArtists.byName',
 
-    data({wikiData}) {
-      return sortAlphabetically(wikiData.artistData.slice()).map((artist) => ({
-        artist,
-        contributions: getArtistNumContributions(artist),
-      }));
-    },
+    data: ({wikiData: {artistData}}) =>
+      sortAlphabetically(artistData.slice())
+        .map(artist => ({
+          artist,
+          contributions: getArtistNumContributions(artist),
+        })),
 
-    row({artist, contributions}, {link, language}) {
-      return language.$('listingPage.listArtists.byName.item', {
+    row: ({artist, contributions}, {link, language}) =>
+      language.$('listingPage.listArtists.byName.item', {
         artist: link.artist(artist),
         contributions: language.countContributions(contributions, {
           unit: true,
         }),
-      });
-    },
+      }),
   },
 
   {
     directory: 'artists/by-contribs',
     stringsKey: 'listArtists.byContribs',
 
-    data({wikiData}) {
-      return {
-        toTracks: wikiData.artistData
-          .map((artist) => ({
-            artist,
-            contributions:
-              (artist.tracksAsContributor?.length ?? 0) +
-              (artist.tracksAsArtist?.length ?? 0),
-          }))
-          .sort((a, b) => b.contributions - a.contributions)
-          .filter(({contributions}) => contributions),
-
-        toArtAndFlashes: wikiData.artistData
-          .map((artist) => ({
-            artist,
-            contributions:
-              (artist.tracksAsCoverArtist?.length ?? 0) +
-              (artist.albumsAsCoverArtist?.length ?? 0) +
-              (artist.albumsAsWallpaperArtist?.length ?? 0) +
-              (artist.albumsAsBannerArtist?.length ?? 0) +
-              (wikiData.wikiInfo.enableFlashesAndGames
-                ? artist.flashesAsContributor?.length ?? 0
-                : 0),
-          }))
-          .sort((a, b) => b.contributions - a.contributions)
-          .filter(({contributions}) => contributions),
+    data: ({wikiData: {artistData, wikiInfo}}) => ({
+      toTracks: artistData
+        .map(artist => ({
+          artist,
+          contributions:
+            (artist.tracksAsContributor?.length ?? 0) +
+            (artist.tracksAsArtist?.length ?? 0),
+        }))
+        .sort((a, b) => b.contributions - a.contributions)
+        .filter(({contributions}) => contributions),
 
-        // This is a kinda naughty hack, 8ut like, it's the only place
-        // we'd 8e passing wikiData to html() otherwise, so like....
-        // (Ok we do do this again once later.)
-        showAsFlashes: wikiData.wikiInfo.enableFlashesAndGames,
-      };
-    },
+      toArtAndFlashes: artistData
+        .map(artist => ({
+          artist,
+          contributions:
+            (artist.tracksAsCoverArtist?.length ?? 0) +
+            (artist.albumsAsCoverArtist?.length ?? 0) +
+            (artist.albumsAsWallpaperArtist?.length ?? 0) +
+            (artist.albumsAsBannerArtist?.length ?? 0) +
+            (wikiInfo.enableFlashesAndGames
+              ? artist.flashesAsContributor?.length ?? 0
+              : 0),
+        }))
+        .sort((a, b) => b.contributions - a.contributions)
+        .filter(({contributions}) => contributions),
 
-    html({toTracks, toArtAndFlashes, showAsFlashes}, {link, language}) {
-      return fixWS`
-                <div class="content-columns">
-                    <div class="column">
-                        <h2>${language.$(
-                          'listingPage.misc.trackContributors'
-                        )}</h2>
-                        <ul>
-                            ${toTracks
-                              .map(({artist, contributions}) =>
-                                language.$(
-                                  'listingPage.listArtists.byContribs.item',
-                                  {
-                                    artist: link.artist(artist),
-                                    contributions: language.countContributions(
-                                      contributions,
-                                      {
-                                        unit: true,
-                                      }
-                                    ),
-                                  }
-                                )
-                              )
-                              .map((row) => `<li>${row}</li>`)
-                              .join('\n')}
-                         </ul>
-                    </div>
-                    <div class="column">
-                        <h2>${language.$(
-                          'listingPage.misc' +
-                            (showAsFlashes
-                              ? '.artAndFlashContributors'
-                              : '.artContributors')
-                        )}</h2>
-                        <ul>
-                            ${toArtAndFlashes
-                              .map(({artist, contributions}) =>
-                                language.$(
-                                  'listingPage.listArtists.byContribs.item',
-                                  {
-                                    artist: link.artist(artist),
-                                    contributions: language.countContributions(
-                                      contributions,
-                                      {
-                                        unit: true,
-                                      }
-                                    ),
-                                  }
-                                )
-                              )
-                              .map((row) => `<li>${row}</li>`)
-                              .join('\n')}
-                        </ul>
-                    </div>
-                </div>
-            `;
-    },
+      // This is a kinda naughty hack, 8ut like, it's the only place
+      // we'd 8e passing wikiData to html() otherwise, so like....
+      // (Ok we do do this again once later.)
+      showAsFlashes: wikiInfo.enableFlashesAndGames,
+    }),
+
+    html: (
+      {toTracks, toArtAndFlashes, showAsFlashes},
+      {html, language, link}
+    ) =>
+      html.tag('div',
+        {class: 'content-columns'},
+        [
+          html.tag('div',
+            {class: 'column'},
+            [
+              html.tag('h2',
+                language.$('listingPage.misc.trackContributors')),
+
+              html.tag('ul',
+                toTracks.map(({artist, contributions}) =>
+                  html.tag('li',
+                    language.$('listingPage.listArtists.byContribs.item', {
+                      artist: link.artist(artist),
+                      contributions: language.countContributions(contributions, {
+                        unit: true,
+                      }),
+                    })))),
+            ]),
+
+          html.tag('div',
+            {class: 'column'},
+            [
+              html.tag('h2',
+                language.$(
+                  'listingPage.misc' +
+                    (showAsFlashes
+                      ? '.artAndFlashContributors'
+                      : '.artContributors'))),
+
+              html.tag('ul',
+                toArtAndFlashes.map(({artist, contributions}) =>
+                  html.tag('li',
+                    language.$('listingPage.listArtists.byContribs.item', {
+                      artist: link.artist(artist),
+                      contributions:
+                        language.countContributions(contributions, {unit: true}),
+                    })))),
+            ]),
+        ]),
   },
 
   {
diff --git a/src/page/listing.js b/src/page/listing.js
index 5db6c916..5a2b6d2a 100644
--- a/src/page/listing.js
+++ b/src/page/listing.js
@@ -10,16 +10,8 @@
 // Individual listing specs are described in src/listing-spec.js, but are
 // provided via wikiData like other (normal) data objects.
 
-// Imports
-
-import fixWS from 'fix-whitespace';
-
-import * as html from '../util/html.js';
-
 import {getTotalDuration} from '../util/wiki-data.js';
 
-// Page exports
-
 export function condition({wikiData}) {
   return wikiData.wikiInfo.enableListings;
 }
@@ -39,40 +31,43 @@ export function write(listing, {wikiData}) {
     type: 'page',
     path: ['listing', listing.directory],
     page: (opts) => {
-      const {getLinkThemeString, link, language} = opts;
+      const {
+        getLinkThemeString,
+        html,
+        language,
+        link,
+      } = opts;
+
       const titleKey = `listingPage.${listing.stringsKey}.title`;
 
       return {
         title: language.$(titleKey),
 
         main: {
-          content: fixWS`
-                        <h1>${language.$(titleKey)}</h1>
-                        ${
-                          listing.html &&
-                          (listing.data
-                            ? listing.html(data, opts)
-                            : listing.html(opts))
-                        }
-                        ${
-                          listing.row &&
-                          fixWS`
-                            <ul>
-                                ${data
-                                  .map((item) => listing.row(item, opts))
-                                  .map((row) => `<li>${row}</li>`)
-                                  .join('\n')}
-                            </ul>
-                        `
-                        }
-                    `,
+          content: [
+            html.tag('h1',
+              language.$(titleKey)),
+
+            ...html.fragment(
+              listing.html &&
+                (listing.data
+                  ? listing.html(data, opts)
+                  : listing.html(opts))),
+
+            listing.row &&
+              html.tag('ul',
+                data.map((item) =>
+                  html.tag('li',
+                    listing.row(item, opts)))),
+          ],
         },
 
         sidebarLeft: {
           content: generateSidebarForListings(listing, {
             getLinkThemeString,
-            link,
+            html,
             language,
+            link,
             wikiData,
           }),
         },
@@ -103,40 +98,58 @@ export function writeTargetless({wikiData}) {
   const page = {
     type: 'page',
     path: ['listingIndex'],
-    page: ({getLinkThemeString, language, link}) => ({
+    page: ({
+      getLinkThemeString,
+      html,
+      language,
+      link,
+    }) => ({
       title: language.$('listingIndex.title'),
 
       main: {
-        content: fixWS`
-                    <h1>${language.$('listingIndex.title')}</h1>
-                    <p>${language.$('listingIndex.infoLine', {
-                      wiki: wikiInfo.name,
-                      tracks: `<b>${language.countTracks(trackData.length, {
-                        unit: true,
-                      })}</b>`,
-                      albums: `<b>${language.countAlbums(albumData.length, {
-                        unit: true,
-                      })}</b>`,
-                      duration: `<b>${language.formatDuration(totalDuration, {
-                        approximate: true,
-                        unit: true,
-                      })}</b>`,
-                    })}</p>
-                    <hr>
-                    <p>${language.$('listingIndex.exploreList')}</p>
-                    ${generateLinkIndexForListings(null, false, {
-                      link,
-                      language,
-                      wikiData,
-                    })}
-                `,
+        content: [
+          html.tag('h1',
+            language.$('listingIndex.title')),
+
+          html.tag('p',
+            language.$('listingIndex.infoLine', {
+              wiki: wikiInfo.name,
+              tracks: html.tag('b',
+                language.countTracks(trackData.length, {
+                  unit: true,
+                })),
+              albums: html.tag('b',
+                language.countAlbums(albumData.length, {
+                  unit: true,
+                })),
+              duration: html.tag('b',
+                language.formatDuration(totalDuration, {
+                  approximate: true,
+                  unit: true,
+                })),
+            })),
+
+          html.tag('hr'),
+
+          html.tag('p',
+            language.$('listingIndex.exploreList')),
+
+          ...html.fragment(
+            generateLinkIndexForListings(null, false, {
+              html,
+              link,
+              language,
+              wikiData,
+            })),
+        ],
       },
 
       sidebarLeft: {
         content: generateSidebarForListings(null, {
           getLinkThemeString,
-          link,
+          html,
           language,
+          link,
           wikiData,
         }),
       },
@@ -150,28 +163,37 @@ export function writeTargetless({wikiData}) {
 
 // Utility functions
 
-function generateSidebarForListings(
-  currentListing,
-  {getLinkThemeString, link, language, wikiData}
-) {
-  return fixWS`
-        <h1>${link.listingIndex('', {
-          text: language.$('listingIndex.title'),
-        })}</h1>
-        ${generateLinkIndexForListings(currentListing, true, {
-          getLinkThemeString,
-          link,
-          language,
-          wikiData,
-        })}
-    `;
+function generateSidebarForListings(currentListing, {
+  getLinkThemeString,
+  html,
+  language,
+  link,
+  wikiData,
+}) {
+  return [
+    html.tag('h1',
+      link.listingIndex('', {
+        text: language.$('listingIndex.title'),
+      })),
+
+    ...html.fragment(
+      generateLinkIndexForListings(currentListing, true, {
+        getLinkThemeString,
+        html,
+        language,
+        link,
+        wikiData,
+      })),
+  ];
 }
 
-function generateLinkIndexForListings(
-  currentListing,
-  forSidebar,
-  {getLinkThemeString, link, language, wikiData}
-) {
+function generateLinkIndexForListings(currentListing, forSidebar, {
+  getLinkThemeString,
+  html,
+  language,
+  link,
+  wikiData,
+}) {
   const {listingTargetSpec, wikiInfo} = wikiData;
 
   const filteredByCondition = listingTargetSpec
@@ -182,46 +204,34 @@ function generateLinkIndexForListings(
     .filter(({listings}) => listings.length > 0);
 
   const genUL = (listings) =>
-    html.tag(
-      'ul',
+    html.tag('ul',
       listings.map((listing) =>
-        html.tag(
-          'li',
+        html.tag('li',
           {class: [listing === currentListing && 'current']},
           link.listing(listing, {
             text: language.$(`listingPage.${listing.stringsKey}.title.short`),
-          })
-        )
-      )
-    );
-
-  if (forSidebar) {
-    return filteredByCondition
-      .map(({title, listings}) =>
-        html.tag(
-          'details',
+          }))));
+
+  return forSidebar
+    ? filteredByCondition.map(({title, listings}) =>
+        html.tag('details',
           {
-            open: !forSidebar || listings.includes(currentListing),
+            open: listings.includes(currentListing),
             class: listings.includes(currentListing) && 'current',
           },
           [
-            html.tag(
-              'summary',
+            html.tag('summary',
               {style: getLinkThemeString(wikiInfo.color)},
-              html.tag('span', {class: 'group-name'}, title({language}))
-            ),
+              html.tag('span',
+                {class: 'group-name'},
+                title({language}))),
             genUL(listings),
-          ]
-        )
-      )
-      .join('\n');
-  } else {
-    return html.tag(
-      'dl',
-      filteredByCondition.flatMap(({title, listings}) => [
-        html.tag('dt', title({language})),
-        html.tag('dd', genUL(listings)),
-      ])
-    );
-  }
+          ]))
+    : html.tag('dl',
+        filteredByCondition.flatMap(({title, listings}) => [
+          html.tag('dt',
+            title({language})),
+          html.tag('dd',
+            genUL(listings)),
+        ]));
 }
diff --git a/src/util/html.js b/src/util/html.js
index 338df71b..bdb385b5 100644
--- a/src/util/html.js
+++ b/src/util/html.js
@@ -55,7 +55,7 @@ export function tag(tagName, ...args) {
   }
 
   if (attrs) {
-    const attrString = attributes(args[0]);
+    const attrString = attributes(attrs);
     if (attrString) {
       openTag = `${tagName} ${attrString}`;
     }
@@ -121,3 +121,19 @@ export function attributes(attribs) {
     )
     .join(' ');
 }
+
+// Ensures the passed value is an array of elements, for usage in [...spread]
+// syntax. This may be used when it's not guaranteed whether the return value of
+// an external function is one child or an array, or in combination with
+// conditionals, e.g. fragment(cond && [x, y, z]).
+export function fragment(childOrChildren) {
+  if (!childOrChildren) {
+    return [];
+  }
+
+  if (Array.isArray(childOrChildren)) {
+    return childOrChildren;
+  }
+
+  return [childOrChildren];
+}