« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/data/things
diff options
context:
space:
mode:
Diffstat (limited to 'src/data/things')
-rw-r--r--src/data/things/ArtTag.js35
-rw-r--r--src/data/things/Artist.js18
-rw-r--r--src/data/things/Artwork.js11
-rw-r--r--src/data/things/NewsEntry.js18
-rw-r--r--src/data/things/StaticPage.js26
-rw-r--r--src/data/things/Track.js5
-rw-r--r--src/data/things/WikiInfo.js11
-rw-r--r--src/data/things/album/Album.js84
-rw-r--r--src/data/things/contrib/AlbumArtistContribution.js12
-rw-r--r--src/data/things/contrib/AlbumAssetArtworkArtistContribution.js12
-rw-r--r--src/data/things/contrib/AlbumBannerArtistContribution.js12
-rw-r--r--src/data/things/contrib/AlbumWallpaperArtistContribution.js12
-rw-r--r--src/data/things/contrib/ArtworkArtistContribution.js20
-rw-r--r--src/data/things/contrib/Contribution.js (renamed from src/data/things/Contribution.js)57
-rw-r--r--src/data/things/contrib/MusicalArtistContribution.js20
-rw-r--r--src/data/things/contrib/TrackArtistContribution.js12
-rw-r--r--src/data/things/contrib/index.js11
-rw-r--r--src/data/things/flash/FlashSide.js80
-rw-r--r--src/data/things/group/Group.js46
-rw-r--r--src/data/things/homepage-layout/HomepageLayout.js89
-rw-r--r--src/data/things/index.js2
-rw-r--r--src/data/things/sorting-rule/SortingRule.js16
22 files changed, 180 insertions, 429 deletions
diff --git a/src/data/things/ArtTag.js b/src/data/things/ArtTag.js
index 91248f77..9d35f54d 100644
--- a/src/data/things/ArtTag.js
+++ b/src/data/things/ArtTag.js
@@ -1,12 +1,4 @@
-const DATA_ART_TAGS_DIRECTORY = 'art-tags';
-const ART_TAG_DATA_FILE = 'tags.yaml';
-
-import {readFile} from 'node:fs/promises';
-import * as path from 'node:path';
-
 import {input, V} from '#composite';
-import {traverse} from '#node-utils';
-import {sortAlphabetically} from '#sort';
 import Thing from '#thing';
 import {unique} from '#sugar';
 import {isName} from '#validators';
@@ -200,31 +192,4 @@ export class ArtTag extends Thing {
       },
     },
   };
-
-  static [Thing.getYamlLoadingSpec] = ({
-    documentModes: {allTogether},
-    thingConstructors: {ArtTag},
-  }) => ({
-    title: `Process art tags file`,
-
-    files: dataPath =>
-      Promise.allSettled([
-        readFile(path.join(dataPath, ART_TAG_DATA_FILE))
-          .then(() => [ART_TAG_DATA_FILE]),
-
-        traverse(path.join(dataPath, DATA_ART_TAGS_DIRECTORY), {
-          filterFile: name => path.extname(name) === '.yaml',
-          prefixPath: DATA_ART_TAGS_DIRECTORY,
-        }),
-      ]).then(results => results
-          .filter(({status}) => status === 'fulfilled')
-          .flatMap(({value}) => value)),
-
-    documentMode: allTogether,
-    documentThing: ArtTag,
-
-    sort({artTagData}) {
-      sortAlphabetically(artTagData);
-    },
-  });
 }
diff --git a/src/data/things/Artist.js b/src/data/things/Artist.js
index 85bdc006..f518e31e 100644
--- a/src/data/things/Artist.js
+++ b/src/data/things/Artist.js
@@ -1,5 +1,3 @@
-const ARTIST_DATA_FILE = 'artists.yaml';
-
 import {inspect} from 'node:util';
 
 import CacheableObject from '#cacheable-object';
@@ -11,7 +9,6 @@ import {parseArtistAliases, parseArtwork} from '#yaml';
 import {
   sortAlbumsTracksChronologically,
   sortArtworksChronologically,
-  sortAlphabetically,
   sortContributionsChronologically,
 } from '#sort';
 
@@ -325,21 +322,6 @@ export class Artist extends Thing {
     },
   };
 
-  static [Thing.getYamlLoadingSpec] = ({
-    documentModes: {allInOne},
-    thingConstructors: {Artist},
-  }) => ({
-    title: `Process artists file`,
-    file: ARTIST_DATA_FILE,
-
-    documentMode: allInOne,
-    documentThing: Artist,
-
-    sort({artistData}) {
-      sortAlphabetically(artistData);
-    },
-  });
-
   [inspect.custom]() {
     const parts = [];
 
diff --git a/src/data/things/Artwork.js b/src/data/things/Artwork.js
index c1aafa8f..7beb3567 100644
--- a/src/data/things/Artwork.js
+++ b/src/data/things/Artwork.js
@@ -73,7 +73,10 @@ export class Artwork extends Thing {
     // 'artistContribs', // from attached artwork or thing
   ];
 
-  static [Thing.getPropertyDescriptors] = ({ArtTag}) => ({
+  static [Thing.getPropertyDescriptors] = ({
+    ArtTag,
+    ArtworkArtistContribution,
+  }) => ({
     // Update & expose
 
     unqualifiedDirectory: directory({
@@ -128,6 +131,12 @@ export class Artwork extends Thing {
     artistContribs: [
       withResolvedContribs({
         from: input.updateValue({validate: isContributionList}),
+
+        // XXX: All artwork artist contributions, as resolved from update value
+        // (*not* those constituted from thing), are generic artwork contribs.
+        // The class should be specified by whatever the artwork is placed on!!
+        class: input.value(ArtworkArtistContribution),
+
         date: 'date',
         thingProperty: input.thisProperty(),
         artistProperty: 'artistContribsArtistProperty',
diff --git a/src/data/things/NewsEntry.js b/src/data/things/NewsEntry.js
index 65fd125b..7cbbfc4b 100644
--- a/src/data/things/NewsEntry.js
+++ b/src/data/things/NewsEntry.js
@@ -1,7 +1,4 @@
-const NEWS_DATA_FILE = 'news.yaml';
-
 import {V} from '#composite';
-import {sortChronologically} from '#sort';
 import Thing from '#thing';
 import {parseDate} from '#yaml';
 
@@ -58,19 +55,4 @@ export class NewsEntry extends Thing {
       'Content': {property: 'content'},
     },
   };
-
-  static [Thing.getYamlLoadingSpec] = ({
-    documentModes: {allInOne},
-    thingConstructors: {NewsEntry},
-  }) => ({
-    title: `Process news data file`,
-    file: NEWS_DATA_FILE,
-
-    documentMode: allInOne,
-    documentThing: NewsEntry,
-
-    sort({newsData}) {
-      sortChronologically(newsData, {latestFirst: true});
-    },
-  });
 }
diff --git a/src/data/things/StaticPage.js b/src/data/things/StaticPage.js
index daa77a7e..5ddddb9d 100644
--- a/src/data/things/StaticPage.js
+++ b/src/data/things/StaticPage.js
@@ -1,10 +1,4 @@
-const DATA_STATIC_PAGE_DIRECTORY = 'static-page';
-
-import * as path from 'node:path';
-
 import {V} from '#composite';
-import {traverse} from '#node-utils';
-import {sortAlphabetically} from '#sort';
 import Thing from '#thing';
 import {isName} from '#validators';
 
@@ -67,24 +61,4 @@ export class StaticPage extends Thing {
       'Review Points': {ignore: true},
     },
   };
-
-  static [Thing.getYamlLoadingSpec] = ({
-    documentModes: {onePerFile},
-    thingConstructors: {StaticPage},
-  }) => ({
-    title: `Process static page files`,
-
-    files: dataPath =>
-      traverse(path.join(dataPath, DATA_STATIC_PAGE_DIRECTORY), {
-        filterFile: name => path.extname(name) === '.yaml',
-        prefixPath: DATA_STATIC_PAGE_DIRECTORY,
-      }),
-
-    documentMode: onePerFile,
-    documentThing: StaticPage,
-
-    sort({staticPageData}) {
-      sortAlphabetically(staticPageData);
-    },
-  });
 }
diff --git a/src/data/things/Track.js b/src/data/things/Track.js
index d776581f..36e073b6 100644
--- a/src/data/things/Track.js
+++ b/src/data/things/Track.js
@@ -116,6 +116,7 @@ export class Track extends Thing {
     LyricsEntry,
     MusicVideo,
     ReferencingSourcesEntry,
+    TrackArtistContribution,
     TrackSection,
     WikiInfo,
   }) => ({
@@ -262,6 +263,7 @@ export class Track extends Thing {
     artistContribs: [
       withResolvedContribs({
         from: input.updateValue({validate: isContributionList}),
+        class: input.value(TrackArtistContribution),
         date: 'date',
         thingProperty: input.thisProperty(),
         artistProperty: input.value('trackArtistContributions'),
@@ -1254,9 +1256,6 @@ export class Track extends Thing {
     },
   };
 
-  // Track YAML loading is handled in album.js.
-  static [Thing.getYamlLoadingSpec] = null;
-
   getOwnAdditionalFilePath(_file, filename) {
     if (!this.album) return null;
 
diff --git a/src/data/things/WikiInfo.js b/src/data/things/WikiInfo.js
index 1d1f90e6..ffb18cd8 100644
--- a/src/data/things/WikiInfo.js
+++ b/src/data/things/WikiInfo.js
@@ -1,5 +1,3 @@
-export const WIKI_INFO_FILE = 'wiki-info.yaml';
-
 import {input, V} from '#composite';
 import Thing from '#thing';
 import {parseContributionPresets, parseWallpaperParts} from '#yaml';
@@ -156,14 +154,5 @@ export class WikiInfo extends Thing {
     },
   };
 
-  static [Thing.getYamlLoadingSpec] = ({
-    documentModes: {oneDocumentTotal},
-    thingConstructors: {WikiInfo},
-  }) => ({
-    title: `Process wiki info file`,
-    file: WIKI_INFO_FILE,
 
-    documentMode: oneDocumentTotal,
-    documentThing: WikiInfo,
-  });
 }
diff --git a/src/data/things/album/Album.js b/src/data/things/album/Album.js
index d5fd1682..48d52223 100644
--- a/src/data/things/album/Album.js
+++ b/src/data/things/album/Album.js
@@ -1,10 +1,4 @@
-export const DATA_ALBUM_DIRECTORY = 'album';
-
-import * as path from 'node:path';
-
 import {input, V} from '#composite';
-import {traverse} from '#node-utils';
-import {sortAlbumsTracksChronologically, sortChronologically} from '#sort';
 import {empty} from '#sugar';
 import Thing from '#thing';
 import {is, isContributionList, isDate, isDirectory, isNumber}
@@ -74,11 +68,15 @@ export class Album extends Thing {
   static [Thing.getPropertyDescriptors] = ({
     AdditionalFile,
     AdditionalName,
+    AlbumArtistContribution,
+    AlbumBannerArtistContribution,
+    AlbumWallpaperArtistContribution,
     ArtTag,
     Artwork,
     CommentaryEntry,
     CreditingSourcesEntry,
     Group,
+    TrackArtistContribution,
     TrackSection,
     WikiInfo,
   }) => ({
@@ -125,6 +123,7 @@ export class Album extends Thing {
     // > Update & expose - Credits and contributors
 
     artistContribs: contributionList({
+      class: input.value(AlbumArtistContribution),
       artistProperty: input.value('albumArtistContributions'),
     }),
 
@@ -133,6 +132,7 @@ export class Album extends Thing {
     trackArtistContribs: [
       withResolvedContribs({
         from: input.updateValue({validate: isContributionList}),
+        class: input.value(TrackArtistContribution),
         thingProperty: input.thisProperty(),
         artistProperty: input.value('albumTrackArtistContributions'),
       }).outputs({
@@ -142,6 +142,7 @@ export class Album extends Thing {
       exposeDependencyOrContinue('#trackArtistContribs', V('empty')),
 
       withRecontextualizedContributionList('artistContribs', {
+        reclass: input.value(TrackArtistContribution),
         artistProperty: input.value('albumTrackArtistContributions'),
       }),
 
@@ -267,6 +268,7 @@ export class Album extends Thing {
     ],
 
     wallpaperArtistContribs: contributionList({
+      class: input.value(AlbumWallpaperArtistContribution),
       date: 'coverArtDate',
       artistProperty: input.value('albumWallpaperArtistContributions'),
     }),
@@ -309,6 +311,7 @@ export class Album extends Thing {
     ],
 
     bannerArtistContribs: contributionList({
+      class: input.value(AlbumBannerArtistContribution),
       date: 'coverArtDate',
       artistProperty: input.value('albumBannerArtistContributions'),
     }),
@@ -794,75 +797,6 @@ export class Album extends Thing {
     ],
   };
 
-  static [Thing.getYamlLoadingSpec] = ({
-    documentModes: {headerAndEntries},
-    thingConstructors: {Album, Track, TrackSection},
-  }) => ({
-    title: `Process album files`,
-
-    files: dataPath =>
-      traverse(path.join(dataPath, DATA_ALBUM_DIRECTORY), {
-        filterFile: name => path.extname(name) === '.yaml',
-        prefixPath: DATA_ALBUM_DIRECTORY,
-      }),
-
-    documentMode: headerAndEntries,
-    headerDocumentThing: Album,
-    entryDocumentThing: document =>
-      ('Section' in document
-        ? TrackSection
-        : Track),
-
-    connect({header: album, entries}) {
-      const trackSections = [];
-
-      let currentTrackSection = new TrackSection();
-      let currentTrackSectionTracks = [];
-
-      Object.assign(currentTrackSection, {
-        name: `Default Track Section`,
-        isDefaultTrackSection: true,
-      });
-
-      const closeCurrentTrackSection = () => {
-        if (
-          currentTrackSection.isDefaultTrackSection &&
-          empty(currentTrackSectionTracks)
-        ) {
-          return;
-        }
-
-        currentTrackSection.tracks = currentTrackSectionTracks;
-        currentTrackSection.album = album;
-
-        trackSections.push(currentTrackSection);
-      };
-
-      for (const entry of entries) {
-        if (entry instanceof TrackSection) {
-          closeCurrentTrackSection();
-          currentTrackSection = entry;
-          currentTrackSectionTracks = [];
-          continue;
-        }
-
-        entry.album = album;
-        entry.trackSection = currentTrackSection;
-
-        currentTrackSectionTracks.push(entry);
-      }
-
-      closeCurrentTrackSection();
-
-      album.trackSections = trackSections;
-    },
-
-    sort({albumData, trackData}) {
-      sortChronologically(albumData);
-      sortAlbumsTracksChronologically(trackData);
-    },
-  });
-
   getOwnAdditionalFilePath(_file, filename) {
     return [
       'media.albumAdditionalFile',
diff --git a/src/data/things/contrib/AlbumArtistContribution.js b/src/data/things/contrib/AlbumArtistContribution.js
new file mode 100644
index 00000000..7b6bc9da
--- /dev/null
+++ b/src/data/things/contrib/AlbumArtistContribution.js
@@ -0,0 +1,12 @@
+import {V} from '#composite';
+import Thing from '#thing';
+
+import {exposeConstant} from '#composite/control-flow';
+
+import {MusicalArtistContribution} from './MusicalArtistContribution.js';
+
+export class AlbumArtistContribution extends MusicalArtistContribution {
+  static [Thing.getPropertyDescriptors] = () => ({
+    isAlbumArtistContribution: exposeConstant(V(true)),
+  });
+}
diff --git a/src/data/things/contrib/AlbumAssetArtworkArtistContribution.js b/src/data/things/contrib/AlbumAssetArtworkArtistContribution.js
new file mode 100644
index 00000000..fbc3f719
--- /dev/null
+++ b/src/data/things/contrib/AlbumAssetArtworkArtistContribution.js
@@ -0,0 +1,12 @@
+import {V} from '#composite';
+import Thing from '#thing';
+
+import {exposeConstant} from '#composite/control-flow';
+
+import {ArtworkArtistContribution} from './ArtworkArtistContribution.js';
+
+export class AlbumAssetArtworkArtistContribution extends ArtworkArtistContribution {
+  static [Thing.getPropertyDescriptors] = () => ({
+    isAlbumAssetArtworkArtistContribution: exposeConstant(V(true)),
+  });
+}
diff --git a/src/data/things/contrib/AlbumBannerArtistContribution.js b/src/data/things/contrib/AlbumBannerArtistContribution.js
new file mode 100644
index 00000000..16f1c9bb
--- /dev/null
+++ b/src/data/things/contrib/AlbumBannerArtistContribution.js
@@ -0,0 +1,12 @@
+import {V} from '#composite';
+import Thing from '#thing';
+
+import {exposeConstant} from '#composite/control-flow';
+
+import {AlbumAssetArtworkArtistContribution} from './AlbumAssetArtworkArtistContribution.js';
+
+export class AlbumBannerArtistContribution extends AlbumAssetArtworkArtistContribution {
+  static [Thing.getPropertyDescriptors] = () => ({
+    isAlbumBannerArtistContribution: exposeConstant(V(true)),
+  });
+}
diff --git a/src/data/things/contrib/AlbumWallpaperArtistContribution.js b/src/data/things/contrib/AlbumWallpaperArtistContribution.js
new file mode 100644
index 00000000..acd29cf8
--- /dev/null
+++ b/src/data/things/contrib/AlbumWallpaperArtistContribution.js
@@ -0,0 +1,12 @@
+import {V} from '#composite';
+import Thing from '#thing';
+
+import {exposeConstant} from '#composite/control-flow';
+
+import {AlbumAssetArtworkArtistContribution} from './AlbumAssetArtworkArtistContribution.js';
+
+export class AlbumWallpaperArtistContribution extends AlbumAssetArtworkArtistContribution {
+  static [Thing.getPropertyDescriptors] = () => ({
+    isAlbumWallpaperArtistContribution: exposeConstant(V(true)),
+  });
+}
diff --git a/src/data/things/contrib/ArtworkArtistContribution.js b/src/data/things/contrib/ArtworkArtistContribution.js
new file mode 100644
index 00000000..a47f2391
--- /dev/null
+++ b/src/data/things/contrib/ArtworkArtistContribution.js
@@ -0,0 +1,20 @@
+import {V} from '#composite';
+import Thing from '#thing';
+
+import {exposeConstant} from '#composite/control-flow';
+
+import {hasAnnotationFront} from '#composite/things/contribution';
+
+import {Contribution} from './Contribution.js';
+
+export class ArtworkArtistContribution extends Contribution {
+  static [Thing.getPropertyDescriptors] = () => ({
+    isArtworkArtistContribution: exposeConstant(V(true)),
+
+    recognizedAnnotationFronts:
+      exposeConstant(V(['edits for wiki'])),
+
+    isEditsForWikiCredit:
+      hasAnnotationFront(V('edits for wiki')),
+  });
+}
diff --git a/src/data/things/Contribution.js b/src/data/things/contrib/Contribution.js
index 4048709b..4352b58a 100644
--- a/src/data/things/Contribution.js
+++ b/src/data/things/contrib/Contribution.js
@@ -7,7 +7,7 @@ import {empty} from '#sugar';
 import Thing from '#thing';
 import {isBoolean, isStringNonEmpty, isThing} from '#validators';
 
-import {simpleDate, singleReference, soupyFind}
+import {simpleDate, singleReference, simpleString, soupyFind}
   from '#composite/wiki-properties';
 
 import {
@@ -56,6 +56,8 @@ export class Contribution extends Thing {
       find: soupyFind.input('artist'),
     }),
 
+    artistText: simpleString(),
+
     annotation: {
       flags: {update: true, expose: true},
       update: {validate: isStringNonEmpty},
@@ -121,16 +123,51 @@ export class Contribution extends Thing {
 
     isContribution: exposeConstant(V(true)),
 
-    annotationParts: {
-      flags: {expose: true},
-      expose: {
-        dependencies: ['annotation'],
-        compute: ({annotation}) =>
-          (annotation
-            ? annotation.split(',').map(part => part.trim())
-            : []),
+    recognizedAnnotationFronts: exposeConstant(V([])),
+
+    annotationFront: [
+      exitWithoutDependency('annotation'),
+
+      {
+        dependencies: ['recognizedAnnotationFronts', 'annotation'],
+        compute: ({recognizedAnnotationFronts, annotation}) =>
+          recognizedAnnotationFronts
+            .find(front =>
+              annotation.startsWith(front) && (
+                annotation === front ||
+                annotation.at(front.length) === ':' ||
+                annotation.at(front.length) === ','
+              )) ?? null,
       },
-    },
+    ],
+
+    annotationBack: [
+      exitWithoutDependency('annotation'),
+
+      exitWithoutDependency({
+        dependency: 'annotationFront',
+        value: 'annotation',
+      }),
+
+      {
+        dependencies: ['annotation', 'annotationFront'],
+        compute: ({annotation, annotationFront}) =>
+          annotation.slice(annotationFront.length + 1).trim()
+            || null,
+      },
+    ],
+
+    annotationParts: [
+      exitWithoutDependency('annotationBack', V([])),
+
+      {
+        dependencies: ['annotationBack'],
+        compute: ({annotationBack}) =>
+          annotationBack
+            .split(',')
+            .map(part => part.trim()),
+      },
+    ],
 
     context: [
       withContributionContext(),
diff --git a/src/data/things/contrib/MusicalArtistContribution.js b/src/data/things/contrib/MusicalArtistContribution.js
new file mode 100644
index 00000000..df26850b
--- /dev/null
+++ b/src/data/things/contrib/MusicalArtistContribution.js
@@ -0,0 +1,20 @@
+import {V} from '#composite';
+import Thing from '#thing';
+
+import {exposeConstant} from '#composite/control-flow';
+
+import {hasAnnotationFront} from '#composite/things/contribution';
+
+import {Contribution} from './Contribution.js';
+
+export class MusicalArtistContribution extends Contribution {
+  static [Thing.getPropertyDescriptors] = () => ({
+    isMusicalArtistContribution: exposeConstant(V(true)),
+
+    recognizedAnnotationFronts:
+      exposeConstant(V(['featuring'])),
+
+    isFeaturingCredit:
+      hasAnnotationFront(V('featuring')),
+  });
+}
diff --git a/src/data/things/contrib/TrackArtistContribution.js b/src/data/things/contrib/TrackArtistContribution.js
new file mode 100644
index 00000000..ecbe9b34
--- /dev/null
+++ b/src/data/things/contrib/TrackArtistContribution.js
@@ -0,0 +1,12 @@
+import {V} from '#composite';
+import Thing from '#thing';
+
+import {exposeConstant} from '#composite/control-flow';
+
+import {MusicalArtistContribution} from './MusicalArtistContribution.js';
+
+export class TrackArtistContribution extends MusicalArtistContribution {
+  static [Thing.getPropertyDescriptors] = () => ({
+    isTrackArtistContribution: exposeConstant(V(true)),
+  });
+}
diff --git a/src/data/things/contrib/index.js b/src/data/things/contrib/index.js
new file mode 100644
index 00000000..187ddb2c
--- /dev/null
+++ b/src/data/things/contrib/index.js
@@ -0,0 +1,11 @@
+export * from './Contribution.js';
+
+export * from './MusicalArtistContribution.js';
+export * from './AlbumArtistContribution.js';
+export * from './TrackArtistContribution.js';
+
+export * from './ArtworkArtistContribution.js';
+
+export * from './AlbumAssetArtworkArtistContribution.js';
+export * from './AlbumBannerArtistContribution.js';
+export * from './AlbumWallpaperArtistContribution.js';
diff --git a/src/data/things/flash/FlashSide.js b/src/data/things/flash/FlashSide.js
index 72782bdd..5e2ea3de 100644
--- a/src/data/things/flash/FlashSide.js
+++ b/src/data/things/flash/FlashSide.js
@@ -1,7 +1,4 @@
-const FLASH_DATA_FILE = 'flashes.yaml';
-
 import {V} from '#composite';
-import {sortFlashesChronologically} from '#sort';
 import Thing from '#thing';
 
 import {exposeConstant} from '#composite/control-flow';
@@ -56,81 +53,4 @@ export class FlashSide extends Thing {
       referenced: flashSide => flashSide.acts,
     },
   };
-
-  static [Thing.getYamlLoadingSpec] = ({
-    documentModes: {allInOne},
-    thingConstructors: {Flash, FlashAct},
-  }) => ({
-    title: `Process flashes file`,
-    file: FLASH_DATA_FILE,
-
-    documentMode: allInOne,
-    documentThing: document =>
-      ('Side' in document
-        ? FlashSide
-     : 'Act' in document
-        ? FlashAct
-        : Flash),
-
-    connect(results) {
-      let thing, i;
-
-      for (i = 0; thing = results[i]; i++) {
-        if (thing.isFlashSide) {
-          const side = thing;
-          const acts = [];
-
-          for (i++; thing = results[i]; i++) {
-            if (thing.isFlashAct) {
-              const act = thing;
-              const flashes = [];
-
-              for (i++; thing = results[i]; i++) {
-                if (thing.isFlash) {
-                  const flash = thing;
-
-                  flash.act = act;
-                  flashes.push(flash);
-
-                  continue;
-                }
-
-                i--;
-                break;
-              }
-
-              act.side = side;
-              act.flashes = flashes;
-              acts.push(act);
-
-              continue;
-            }
-
-            if (thing.isFlash) {
-              throw new Error(`Flashes must be under an act`);
-            }
-
-            i--;
-            break;
-          }
-
-          side.acts = acts;
-
-          continue;
-        }
-
-        if (thing.isFlashAct) {
-          throw new Error(`Acts must be under a side`);
-        }
-
-        if (thing.isFlash) {
-          throw new Error(`Flashes must be under a side and act`);
-        }
-      }
-    },
-
-    sort({flashData}) {
-      sortFlashesChronologically(flashData);
-    },
-  });
 }
diff --git a/src/data/things/group/Group.js b/src/data/things/group/Group.js
index b065f9a3..6f698682 100644
--- a/src/data/things/group/Group.js
+++ b/src/data/things/group/Group.js
@@ -1,5 +1,3 @@
-const GROUP_DATA_FILE = 'groups.yaml';
-
 import {input, V} from '#composite';
 import Thing from '#thing';
 import {isBoolean} from '#validators';
@@ -185,48 +183,4 @@ export class Group extends Thing {
       'Review Points': {ignore: true},
     },
   };
-
-  static [Thing.getYamlLoadingSpec] = ({
-    documentModes: {allInOne},
-    thingConstructors: {Group, GroupCategory},
-  }) => ({
-    title: `Process groups file`,
-    file: GROUP_DATA_FILE,
-
-    documentMode: allInOne,
-    documentThing: document =>
-      ('Category' in document
-        ? GroupCategory
-        : Group),
-
-    connect(results) {
-      let groupCategory;
-      let groupRefs = [];
-
-      if (results[0] && !(results[0] instanceof GroupCategory)) {
-        throw new Error(`Expected a category at top of group data file`);
-      }
-
-      for (const thing of results) {
-        if (thing instanceof GroupCategory) {
-          if (groupCategory) {
-            Object.assign(groupCategory, {groups: groupRefs});
-          }
-
-          groupCategory = thing;
-          groupRefs = [];
-        } else {
-          groupRefs.push(Thing.getReference(thing));
-        }
-      }
-
-      if (groupCategory) {
-        Object.assign(groupCategory, {groups: groupRefs});
-      }
-    },
-
-    // Groups aren't sorted at all, always preserving the order in the data
-    // file as-is.
-    sort: null,
-  });
 }
diff --git a/src/data/things/homepage-layout/HomepageLayout.js b/src/data/things/homepage-layout/HomepageLayout.js
index e144bf80..1c432b53 100644
--- a/src/data/things/homepage-layout/HomepageLayout.js
+++ b/src/data/things/homepage-layout/HomepageLayout.js
@@ -1,8 +1,5 @@
-const HOMEPAGE_LAYOUT_DATA_FILE = 'homepage.yaml';
-
 import {V} from '#composite';
 import Thing from '#thing';
-import {empty} from '#sugar';
 import {isStringNonEmpty, validateArrayItems} from '#validators';
 
 import {exposeConstant} from '#composite/control-flow';
@@ -39,90 +36,4 @@ export class HomepageLayout extends Thing {
       'Navbar Links': {property: 'navbarLinks'},
     },
   };
-
-  static [Thing.getYamlLoadingSpec] = ({
-    documentModes: {allInOne},
-    thingConstructors: {
-      HomepageLayout,
-      HomepageLayoutActionsRow,
-      HomepageLayoutAlbumCarouselRow,
-      HomepageLayoutAlbumGridRow,
-      HomepageLayoutRow,
-      HomepageLayoutSection,
-    },
-  }) => ({
-    title: `Process homepage layout file`,
-    file: HOMEPAGE_LAYOUT_DATA_FILE,
-
-    documentMode: allInOne,
-    documentThing: document => {
-      if (document['Homepage']) {
-        return HomepageLayout;
-      }
-
-      if (document['Section']) {
-        return HomepageLayoutSection;
-      }
-
-      if (document['Row']) {
-        switch (document['Row']) {
-          case 'actions':
-            return HomepageLayoutActionsRow;
-          case 'album carousel':
-            return HomepageLayoutAlbumCarouselRow;
-          case 'album grid':
-            return HomepageLayoutAlbumGridRow;
-          default:
-            throw new TypeError(`Unrecognized row type ${document['Row']}`);
-        }
-      }
-
-      return null;
-    },
-
-    connect(results) {
-      if (!empty(results) && !(results[0] instanceof HomepageLayout)) {
-        throw new Error(`Expected 'Homepage' document at top of homepage layout file`);
-      }
-
-      const homepageLayout = results[0];
-      const sections = [];
-
-      let currentSection = null;
-      let currentSectionRows = [];
-
-      const closeCurrentSection = () => {
-        if (currentSection) {
-          for (const row of currentSectionRows) {
-            row.section = currentSection;
-          }
-
-          currentSection.rows = currentSectionRows;
-          sections.push(currentSection);
-
-          currentSection = null;
-          currentSectionRows = [];
-        }
-      };
-
-      for (const entry of results.slice(1)) {
-        if (entry instanceof HomepageLayout) {
-          throw new Error(`Expected only one 'Homepage' document in total`);
-        } else if (entry instanceof HomepageLayoutSection) {
-          closeCurrentSection();
-          currentSection = entry;
-        } else if (entry instanceof HomepageLayoutRow) {
-          if (currentSection) {
-            currentSectionRows.push(entry);
-          } else {
-            throw new Error(`Expected a 'Section' document to add following rows into`);
-          }
-        }
-      }
-
-      closeCurrentSection();
-
-      homepageLayout.sections = sections;
-    },
-  });
 }
diff --git a/src/data/things/index.js b/src/data/things/index.js
index bf8a5a33..3773864b 100644
--- a/src/data/things/index.js
+++ b/src/data/things/index.js
@@ -2,6 +2,7 @@
 
 export * from './album/index.js';
 export * from './content/index.js';
+export * from './contrib/index.js';
 export * from './flash/index.js';
 export * from './group/index.js';
 export * from './homepage-layout/index.js';
@@ -12,7 +13,6 @@ export * from './AdditionalName.js';
 export * from './ArtTag.js';
 export * from './Artist.js';
 export * from './Artwork.js';
-export * from './Contribution.js';
 export * from './Language.js';
 export * from './MusicVideo.js';
 export * from './NewsEntry.js';
diff --git a/src/data/things/sorting-rule/SortingRule.js b/src/data/things/sorting-rule/SortingRule.js
index 4ce9d97a..5d4bba99 100644
--- a/src/data/things/sorting-rule/SortingRule.js
+++ b/src/data/things/sorting-rule/SortingRule.js
@@ -1,5 +1,3 @@
-const SORTING_RULE_DATA_FILE = 'sorting-rules.yaml';
-
 import {V} from '#composite';
 import {unique} from '#sugar';
 import Thing from '#thing';
@@ -34,20 +32,6 @@ export class SortingRule extends Thing {
     },
   };
 
-  static [Thing.getYamlLoadingSpec] = ({
-    documentModes: {allInOne},
-    thingConstructors: {DocumentSortingRule},
-  }) => ({
-    title: `Process sorting rules file`,
-    file: SORTING_RULE_DATA_FILE,
-
-    documentMode: allInOne,
-    documentThing: document =>
-      (document['Sort Documents']
-        ? DocumentSortingRule
-        : null),
-  });
-
   check(opts) {
     return this.constructor.check(this, opts);
   }