« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/util/wiki-data.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/wiki-data.js')
-rw-r--r--src/util/wiki-data.js542
1 files changed, 0 insertions, 542 deletions
diff --git a/src/util/wiki-data.js b/src/util/wiki-data.js
deleted file mode 100644
index c93cb661..00000000
--- a/src/util/wiki-data.js
+++ /dev/null
@@ -1,542 +0,0 @@
-// Utility functions for interacting with wiki data.
-
-import {
-  accumulateSum,
-  empty,
-} from './sugar.js';
-
-// Generic value operations
-
-export function getKebabCase(name) {
-  return name
-    .split(' ')
-    .join('-')
-    .replace(/&/g, 'and')
-    .replace(/[^a-zA-Z0-9-]/g, '')
-    .replace(/-{2,}/g, '-')
-    .replace(/^-+|-+$/g, '')
-    .toLowerCase();
-}
-
-export function chunkByConditions(array, conditions) {
-  if (empty(array)) {
-    return [];
-  }
-
-  if (empty(conditions)) {
-    return [array];
-  }
-
-  const out = [];
-  let cur = [array[0]];
-  for (let i = 1; i < array.length; i++) {
-    const item = array[i];
-    const prev = array[i - 1];
-    let chunk = false;
-    for (const condition of conditions) {
-      if (condition(item, prev)) {
-        chunk = true;
-        break;
-      }
-    }
-    if (chunk) {
-      out.push(cur);
-      cur = [item];
-    } else {
-      cur.push(item);
-    }
-  }
-  out.push(cur);
-  return out;
-}
-
-export function chunkByProperties(array, properties) {
-  return chunkByConditions(
-    array,
-    properties.map((p) => (a, b) => {
-      if (a[p] instanceof Date && b[p] instanceof Date) return +a[p] !== +b[p];
-
-      if (a[p] !== b[p]) return true;
-
-      // Not sure if this line is still necessary with the specific check for
-      // d8tes a8ove, 8ut, uh, keeping it anyway, just in case....?
-      if (a[p] != b[p]) return true;
-
-      return false;
-    })
-  ).map((chunk) => ({
-    ...Object.fromEntries(properties.map((p) => [p, chunk[0][p]])),
-    chunk,
-  }));
-}
-
-// Sorting functions - all utils here are mutating, so make sure to initially
-// slice/filter/somehow generate a new array from input data if retaining the
-// initial sort matters! (Spoilers: If what you're doing involves any kind of
-// parallelization, it definitely matters.)
-
-// General sorting utilities! These don't do any sorting on their own but are
-// handy in the sorting functions below (or if you're making your own sort).
-
-export function compareCaseLessSensitive(a, b) {
-  // Compare two strings without considering capitalization... unless they
-  // happen to be the same that way.
-
-  const al = a.toLowerCase();
-  const bl = b.toLowerCase();
-
-  return al === bl
-    ? a.localeCompare(b, undefined, {numeric: true})
-    : al.localeCompare(bl, undefined, {numeric: true});
-}
-
-// Subtract common prefixes and other characters which some people don't like
-// to have considered while sorting. The words part of this is English-only for
-// now, which is totally evil.
-export function normalizeName(s) {
-  // Turn (some) ligatures into expanded variant for cleaner sorting, e.g.
-  // "ff" into "ff", in decompose mode, so that "ü" is represented as two
-  // bytes ("u" + \u0308 combining diaeresis).
-  s = s.normalize('NFKD');
-
-  // Replace one or more whitespace of any kind in a row, as well as certain
-  // punctuation, with a single typical space, then trim the ends.
-  s = s
-    .replace(
-      /[\p{Separator}\p{Dash_Punctuation}\p{Connector_Punctuation}]+/gu,
-      ' '
-    )
-    .trim();
-
-  // Discard anything that isn't a letter, number, or space.
-  s = s.replace(/[^\p{Letter}\p{Number} ]/gu, '').trim();
-
-  // Remove common English (only, for now) prefixes.
-  s = s.replace(/^(?:an?|the) /i, '');
-
-  return s;
-}
-
-// Component sort functions - these sort by one particular property, applying
-// unique particulars where appropriate. Usually you don't want to use these
-// directly, but if you're making a custom sort they can come in handy.
-
-// Universal method for sorting things into a predictable order, as directory
-// is taken to be unique. There are two exceptions where this function (and
-// thus any of the composite functions that start with it) *can't* be taken as
-// deterministic:
-//
-//  1) Mixed data of two different Things, as directories are only taken as
-//     unique within one given class of Things. For example, this function
-//     won't be deterministic if its array contains both <album:ithaca> and
-//     <track:ithaca>.
-//
-//  2) Duplicate directories, or multiple instances of the "same" Thing.
-//     This function doesn't differentiate between two objects of the same
-//     directory, regardless of any other properties or the overall "identity"
-//     of the object.
-//
-// These exceptions are unavoidable except for not providing that kind of data
-// in the first place, but you can still ensure the overall program output is
-// deterministic by ensuring the input is arbitrarily sorted according to some
-// other criteria - ex, although sortByDirectory itself isn't determinstic when
-// given mixed track and album data, the final output (what goes on the site)
-// will always be the same if you're doing sortByDirectory([...albumData,
-// ...trackData]), because the initial sort places albums before tracks - and
-// sortByDirectory will handle the rest, given all directories are unique
-// except when album and track directories overlap with each other.
-export function sortByDirectory(data, {
-  getDirectory = (o) => o.directory,
-} = {}) {
-  return data.sort((a, b) => {
-    const ad = getDirectory(a);
-    const bd = getDirectory(b);
-    return compareCaseLessSensitive(ad, bd);
-  });
-}
-
-export function sortByName(data, {
-  getName = (o) => o.name,
-} = {}) {
-  const nameMap = new Map();
-  const normalizedNameMap = new Map();
-  for (const o of data) {
-    const name = getName(o);
-    const normalizedName = normalizeName(name);
-    nameMap.set(o, name);
-    normalizedNameMap.set(o, normalizedName);
-  }
-
-  return data.sort((a, b) => {
-    const ann = normalizedNameMap.get(a);
-    const bnn = normalizedNameMap.get(b);
-    const comparison = compareCaseLessSensitive(ann, bnn);
-    if (comparison !== 0)
-      return comparison;
-
-    const an = nameMap.get(a);
-    const bn = nameMap.get(b);
-    return compareCaseLessSensitive(an, bn);
-  });
-}
-
-export function sortByDate(data, {
-  getDate = (o) => o.date,
-} = {}) {
-  return data.sort((a, b) => {
-    const ad = getDate(a);
-    const bd = getDate(b);
-
-    // It's possible for objects with and without dates to be mixed
-    // together in the same array. If that's the case, we put all items
-    // without dates at the end.
-    if (ad && bd) {
-      return ad - bd;
-    } else if (ad) {
-      return -1;
-    } else if (bd) {
-      return 1;
-    } else {
-      // If neither of the items being compared have a date, don't move
-      // them relative to each other. This is basically the same as
-      // filtering out all non-date items and then pushing them at the
-      // end after sorting the rest.
-      return 0;
-    }
-  });
-}
-
-export function sortByPositionInAlbum(data) {
-  return data.sort((a, b) => {
-    const aa = a.album;
-    const ba = b.album;
-
-    // Don't change the sort when the two tracks are from separate albums.
-    // This function doesn't change the order of albums or try to "merge"
-    // two separated chunks of tracks from the same album together.
-    if (aa !== ba) {
-      return 0;
-    }
-
-    // Don't change the sort when only one (or neither) item is actually
-    // a track (i.e. has an album).
-    if (!aa || !ba) {
-      return 0;
-    }
-
-    const ai = aa.tracks.indexOf(a);
-    const bi = ba.tracks.indexOf(b);
-
-    // There's no reason this two-way reference (a track's album and the
-    // album's track list) should be broken, but if for any reason it is,
-    // don't change the sort.
-    if (ai === -1 || bi === -1) {
-      return 0;
-    }
-
-    return ai - bi;
-  });
-}
-
-// Sorts data so that items are grouped together according to whichever of a
-// set of arbitrary given conditions is true first. If no conditions are met
-// for a given item, it's moved over to the end!
-export function sortByConditions(data, conditions) {
-  data.sort((a, b) => {
-    const ai = conditions.findIndex((f) => f(a));
-    const bi = conditions.findIndex((f) => f(b));
-
-    if (ai >= 0 && bi >= 0) {
-      return ai - bi;
-    } else if (ai >= 0) {
-      return -1;
-    } else if (bi >= 0) {
-      return 1;
-    } else {
-      return 0;
-    }
-  });
-}
-
-// Composite sorting functions - these consider multiple properties, generally
-// always returning the same output regardless of how the input was originally
-// sorted (or left unsorted). If you're working with arbitrarily sorted inputs
-// (typically wiki data, either in full or unsorted filter), these make sure
-// what gets put on the actual website (or wherever) is deterministic. Also
-// they're just handy sorting utilities.
-//
-// Note that because these are each comprised of multiple component sorting
-// functions, they expect more than just one property to be present for full
-// sorting (listed above each function). If you're mapping thing objects to
-// another representation, try to include all of these listed properties.
-
-// Expects thing properties:
-//  * directory (or override getDirectory)
-//  * name (or override getName)
-export function sortAlphabetically(data, {
-  getDirectory,
-  getName,
-} = {}) {
-  sortByDirectory(data, {getDirectory});
-  sortByName(data, {getName});
-  return data;
-}
-
-// Expects thing properties:
-//  * directory (or override getDirectory)
-//  * name (or override getName)
-//  * date (or override getDate)
-export function sortChronologically(data, {
-  latestFirst = false,
-  getDirectory,
-  getName,
-  getDate,
-} = {}) {
-  if (latestFirst) {
-    // Double reverse: Since we reverse after sorting by date, also reverse
-    // after sorting A-Z, so the second reverse restores A-Z relative
-    // positioning (for entries with the same date).
-    sortAlphabetically(data, {getDirectory, getName});
-    data.reverse();
-    sortByDate(data, {getDate});
-    data.reverse();
-  } else {
-    sortAlphabetically(data, {getDirectory, getName});
-    sortByDate(data, {getDate});
-  }
-  return data;
-}
-
-// Highly contextual sort functions - these are only for very specific types
-// of Things, and have appropriately hard-coded behavior.
-
-// Sorts so that tracks from the same album are generally grouped together in
-// their original (album track list) order, while prioritizing date (by default
-// release date but can be overridden) above all else.
-//
-// This function also works for data lists which contain only tracks.
-export function sortAlbumsTracksChronologically(data, {
-  getDate,
-} = {}) {
-  // Sort albums before tracks...
-  sortByConditions(data, [(t) => t.album === undefined]);
-
-  // Group tracks by album...
-  sortByDirectory(data, {
-    getDirectory: (t) => (t.album ? t.album.directory : t.directory),
-  });
-
-  // Sort tracks by position in album...
-  sortByPositionInAlbum(data);
-
-  // ...and finally sort by date. If tracks from more than one album were
-  // released on the same date, they'll still be grouped together by album,
-  // and tracks within an album will retain their relative positioning (i.e.
-  // stay in the same order as part of the album's track listing).
-  sortByDate(data, {getDate});
-
-  return data;
-}
-
-// Specific data utilities
-
-export function filterAlbumsByCommentary(albums) {
-  return albums
-    .filter((album) => [album, ...album.tracks].some((x) => x.commentary));
-}
-
-export function getAlbumCover(album, {to}) {
-  // Some albums don't have art! This function returns null in that case.
-  if (album.hasCoverArt) {
-    return to('media.albumCover', album.directory, album.coverArtFileExtension);
-  } else {
-    return null;
-  }
-}
-
-export function getAlbumListTag(album) {
-  return album.hasTrackNumbers ? 'ol' : 'ul';
-}
-
-// This gets all the track o8jects defined in every al8um, and sorts them 8y
-// date released. Generally, albumData will pro8a8ly already 8e sorted 8efore
-// you pass it to this function, 8ut individual tracks can have their own
-// original release d8, distinct from the al8um's d8. I allowed that 8ecause
-// in Homestuck, the first four Vol.'s were com8ined into one al8um really
-// early in the history of the 8andcamp, and I still want to use that as the
-// al8um listing (not the original four al8um listings), 8ut if I only did
-// that, all the tracks would 8e sorted as though they were released at the
-// same time as the compilation al8um - i.e, after some other al8ums (including
-// Vol.'s 5 and 6!) were released. That would mess with chronological listings
-// including tracks from multiple al8ums, like artist pages. So, to fix that,
-// I gave tracks an Original Date field, defaulting to the release date of the
-// al8um if not specified. Pretty reasona8le, I think! Oh, and this feature can
-// 8e used for other projects too, like if you wanted to have an al8um listing
-// compiling a 8unch of songs with radically different & interspersed release
-// d8s, 8ut still keep the al8um listing in a specific order, since that isn't
-// sorted 8y date.
-export function getAllTracks(albumData) {
-  return sortByDate(albumData.flatMap((album) => album.tracks));
-}
-
-export function getArtistNumContributions(artist) {
-  return (
-    (artist.tracksAsAny?.length ?? 0) +
-    (artist.albumsAsCoverArtist?.length ?? 0) +
-    (artist.flashesAsContributor?.length ?? 0)
-  );
-}
-
-export function getFlashCover(flash, {to}) {
-  return to('media.flashArt', flash.directory, flash.coverArtFileExtension);
-}
-
-export function getFlashLink(flash) {
-  return `https://homestuck.com/story/${flash.page}`;
-}
-
-export function getTotalDuration(tracks, {
-  originalReleasesOnly = false,
-} = {}) {
-  if (originalReleasesOnly) {
-    tracks = tracks.filter(t => !t.originalReleaseTrack);
-  }
-
-  return accumulateSum(tracks, track => track.duration);
-}
-
-export function getTrackCover(track, {to}) {
-  // Some albums don't have any track art at all, and in those, every track
-  // just inherits the album's own cover art. Note that since cover art isn't
-  // guaranteed on albums either, it's possible that this function returns
-  // null!
-  if (!track.hasCoverArt) {
-    return getAlbumCover(track.album, {to});
-  } else {
-    return to('media.trackCover', track.album.directory, track.directory, track.coverArtFileExtension);
-  }
-}
-
-export function getArtistAvatar(artist, {to}) {
-  return to('media.artistAvatar', artist.directory, artist.avatarFileExtension);
-}
-
-// Big-ass homepage row functions
-
-export function getNewAdditions(numAlbums, {wikiData}) {
-  const {albumData} = wikiData;
-
-  // Sort al8ums, in descending order of priority, 8y...
-  //
-  // * D8te of addition to the wiki (descending).
-  // * Major releases first.
-  // * D8te of release (descending).
-  //
-  // Major releases go first to 8etter ensure they show up in the list (and
-  // are usually at the start of the final output for a given d8 of release
-  // too).
-  const sortedAlbums = albumData
-    .filter((album) => album.isListedOnHomepage)
-    .sort((a, b) => {
-      if (a.dateAddedToWiki > b.dateAddedToWiki) return -1;
-      if (a.dateAddedToWiki < b.dateAddedToWiki) return 1;
-      if (a.isMajorRelease && !b.isMajorRelease) return -1;
-      if (!a.isMajorRelease && b.isMajorRelease) return 1;
-      if (a.date > b.date) return -1;
-      if (a.date < b.date) return 1;
-    });
-
-  // When multiple al8ums are added to the wiki at a time, we want to show
-  // all of them 8efore pulling al8ums from the next (earlier) date. We also
-  // want to show a diverse selection of al8ums - with limited space, we'd
-  // rather not show only the latest al8ums, if those happen to all 8e
-  // closely rel8ted!
-  //
-  // Specifically, we're concerned with avoiding too much overlap amongst
-  // the primary (first/top-most) group. We do this 8y collecting every
-  // primary group present amongst the al8ums for a given d8 into one
-  // (ordered) array, initially sorted (inherently) 8y latest al8um from
-  // the group. Then we cycle over the array, adding one al8um from each
-  // group until all the al8ums from that release d8 have 8een added (or
-  // we've met the total target num8er of al8ums). Once we've added all the
-  // al8ums for a given group, it's struck from the array (so the groups
-  // with the most additions on one d8 will have their oldest releases
-  // collected more towards the end of the list).
-
-  const albums = [];
-
-  let i = 0;
-  outerLoop: while (i < sortedAlbums.length) {
-    // 8uild up a list of groups and their al8ums 8y order of decending
-    // release, iter8ting until we're on a different d8. (We use a map for
-    // indexing so we don't have to iter8te through the entire array each
-    // time we access one of its entries. This is 8asically unnecessary
-    // since this will never 8e an expensive enough task for that to
-    // matter.... 8ut it's nicer code. BBBB) )
-    const currentDate = sortedAlbums[i].dateAddedToWiki;
-    const groupMap = new Map();
-    const groupArray = [];
-    for (let album; (album = sortedAlbums[i]) && +album.dateAddedToWiki === +currentDate; i++) {
-      const primaryGroup = album.groups[0];
-      if (groupMap.has(primaryGroup)) {
-        groupMap.get(primaryGroup).push(album);
-      } else {
-        const entry = [album];
-        groupMap.set(primaryGroup, entry);
-        groupArray.push(entry);
-      }
-    }
-
-    // Then cycle over that sorted array, adding one al8um from each to
-    // the main array until we've run out or have met the target num8er
-    // of al8ums.
-    while (!empty(groupArray)) {
-      let j = 0;
-      while (j < groupArray.length) {
-        const entry = groupArray[j];
-        const album = entry.shift();
-        albums.push(album);
-
-        // This is the only time we ever add anything to the main al8um
-        // list, so it's also the only place we need to check if we've
-        // met the target length.
-        if (albums.length === numAlbums) {
-          // If we've met it, 8r8k out of the outer loop - we're done
-          // here!
-          break outerLoop;
-        }
-
-        if (empty(entry)) {
-          groupArray.splice(j, 1);
-        } else {
-          j++;
-        }
-      }
-    }
-  }
-
-  // Finally, do some quick mapping shenanigans to 8etter display the result
-  // in a grid. (This should pro8a8ly 8e a separ8te, shared function, 8ut
-  // whatevs.)
-  return albums.map((album) => ({large: album.isMajorRelease, item: album}));
-}
-
-export function getNewReleases(numReleases, {wikiData}) {
-  const {albumData} = wikiData;
-
-  const latestFirst = albumData
-    .filter((album) => album.isListedOnHomepage)
-    .reverse();
-
-  const majorReleases = latestFirst.filter((album) => album.isMajorRelease);
-  majorReleases.splice(1);
-
-  const otherReleases = latestFirst
-    .filter((album) => !majorReleases.includes(album))
-    .slice(0, numReleases - majorReleases.length);
-
-  return [
-    ...majorReleases.map((album) => ({large: true, item: album})),
-    ...otherReleases.map((album) => ({large: false, item: album})),
-  ];
-}