« 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
diff options
context:
space:
mode:
Diffstat (limited to 'src/data')
-rw-r--r--src/data/composite/wiki-properties/additionalNameList.js13
-rw-r--r--src/data/composite/wiki-properties/index.js1
-rw-r--r--src/data/language.js14
-rw-r--r--src/data/things/language.js40
-rw-r--r--src/data/things/track.js2
-rw-r--r--src/data/things/validators.js22
-rw-r--r--src/data/yaml.js50
7 files changed, 113 insertions, 29 deletions
diff --git a/src/data/composite/wiki-properties/additionalNameList.js b/src/data/composite/wiki-properties/additionalNameList.js
new file mode 100644
index 00000000..d1302224
--- /dev/null
+++ b/src/data/composite/wiki-properties/additionalNameList.js
@@ -0,0 +1,13 @@
+// A list of additional names! These can be used for a variety of purposes,
+// e.g. providing extra searchable titles, localizations, romanizations or
+// original titles, and so on. Each item has a name and, optionally, a
+// descriptive annotation.
+
+import {isAdditionalNameList} from '#validators';
+
+export default function() {
+  return {
+    flags: {update: true, expose: true},
+    update: {validate: isAdditionalNameList},
+  };
+}
diff --git a/src/data/composite/wiki-properties/index.js b/src/data/composite/wiki-properties/index.js
index 3a8b51d5..17d51bb8 100644
--- a/src/data/composite/wiki-properties/index.js
+++ b/src/data/composite/wiki-properties/index.js
@@ -4,6 +4,7 @@
 // #composite/data, and #composite/wiki-data.
 
 export {default as additionalFiles} from './additionalFiles.js';
+export {default as additionalNameList} from './additionalNameList.js';
 export {default as color} from './color.js';
 export {default as commentary} from './commentary.js';
 export {default as commentatorArtists} from './commentatorArtists.js';
diff --git a/src/data/language.js b/src/data/language.js
index 3fc14da7..6f774f27 100644
--- a/src/data/language.js
+++ b/src/data/language.js
@@ -7,15 +7,11 @@ import chokidar from 'chokidar';
 import he from 'he'; // It stands for "HTML Entities", apparently. Cursed.
 import yaml from 'js-yaml';
 
-import T from '#things';
+import {externalLinkSpec} from '#external-links';
 import {colors, logWarn} from '#cli';
-
-import {
-  annotateError,
-  annotateErrorWithFile,
-  showAggregate,
-  withAggregate,
-} from '#sugar';
+import {annotateError, annotateErrorWithFile, showAggregate, withAggregate}
+  from '#sugar';
+import T from '#things';
 
 const {Language} = T;
 
@@ -114,6 +110,8 @@ export function initializeLanguageObject() {
   language.escapeHTML = string =>
     he.encode(string, {useNamedReferences: true});
 
+  language.externalLinkSpec = externalLinkSpec;
+
   return language;
 }
 
diff --git a/src/data/things/language.js b/src/data/things/language.js
index 646eb6d1..70481299 100644
--- a/src/data/things/language.js
+++ b/src/data/things/language.js
@@ -1,5 +1,13 @@
-import {Tag} from '#html';
 import {isLanguageCode} from '#validators';
+import {Tag} from '#html';
+
+import {
+  getExternalLinkStringOfStyleFromDescriptors,
+  getExternalLinkStringsFromDescriptors,
+  isExternalLinkContext,
+  isExternalLinkSpec,
+  isExternalLinkStyle,
+} from '#external-links';
 
 import {
   externalFunction,
@@ -72,6 +80,13 @@ export class Language extends Thing {
       update: {validate: (t) => typeof t === 'object'},
     },
 
+    // List of descriptors for providing to external link utilities when using
+    // language.formatExternalLink - refer to util/external-links.js for info.
+    externalLinkSpec: {
+      flags: {update: true, expose: true},
+      update: {validate: isExternalLinkSpec},
+    },
+
     // Update only
 
     escapeHTML: externalFunction(),
@@ -299,6 +314,29 @@ export class Language extends Thing {
       : duration;
   }
 
+  formatExternalLink(url, {
+    style = 'normal',
+    context = 'generic',
+  } = {}) {
+    if (!this.externalLinkSpec) {
+      throw new TypeError(`externalLinkSpec unavailable`);
+    }
+
+    isExternalLinkContext(context);
+
+    if (style === 'all') {
+      return getExternalLinkStringsFromDescriptors(url, this.externalLinkSpec, {
+        language: this,
+        context,
+      });
+    }
+
+    return getExternalLinkStringOfStyleFromDescriptors(url, style, this.externalLinkSpec, {
+      language: this,
+      context,
+    });
+  }
+
   formatIndex(value) {
     this.assertIntlAvailable('intl_pluralOrdinal');
     return this.formatString('count.index.' + this.intl_pluralOrdinal.select(value), {index: value});
diff --git a/src/data/things/track.js b/src/data/things/track.js
index 8d310611..f6320677 100644
--- a/src/data/things/track.js
+++ b/src/data/things/track.js
@@ -24,6 +24,7 @@ import {
 
 import {
   additionalFiles,
+  additionalNameList,
   commentary,
   commentatorArtists,
   contributionList,
@@ -63,6 +64,7 @@ export class Track extends Thing {
 
     name: name('Unnamed Track'),
     directory: directory(),
+    additionalNames: additionalNameList(),
 
     duration: duration(),
     urls: urls(),
diff --git a/src/data/things/validators.js b/src/data/things/validators.js
index 569a7b34..55eedbcf 100644
--- a/src/data/things/validators.js
+++ b/src/data/things/validators.js
@@ -101,7 +101,10 @@ export function isStringNonEmpty(value) {
 }
 
 export function optional(validator) {
-  return value => value === null || value === undefined || validator(value);
+  return value =>
+    value === null ||
+    value === undefined ||
+    validator(value);
 }
 
 // Complex types (non-primitives)
@@ -341,20 +344,14 @@ export function validateProperties(spec) {
 
 export const isContribution = validateProperties({
   who: isArtistRef,
-  what: (value) =>
-    value === undefined ||
-    value === null ||
-    isStringNonEmpty(value),
+  what: optional(isStringNonEmpty),
 });
 
 export const isContributionList = validateArrayItems(isContribution);
 
 export const isAdditionalFile = validateProperties({
   title: isString,
-  description: (value) =>
-    value === undefined ||
-    value === null ||
-    isString(value),
+  description: optional(isStringNonEmpty),
   files: validateArrayItems(isString),
 });
 
@@ -432,6 +429,13 @@ export function isURL(string) {
   return true;
 }
 
+export const isAdditionalName = validateProperties({
+  name: isName,
+  annotation: optional(isStringNonEmpty),
+});
+
+export const isAdditionalNameList = validateArrayItems(isAdditionalName);
+
 export function validateReference(type = 'track') {
   return (ref) => {
     isStringNonEmpty(ref);
diff --git a/src/data/yaml.js b/src/data/yaml.js
index dddf5fb2..2c600341 100644
--- a/src/data/yaml.js
+++ b/src/data/yaml.js
@@ -462,6 +462,7 @@ export const processTrackSectionDocument = makeProcessDocument(T.TrackSectionHel
 
 export const processTrackDocument = makeProcessDocument(T.Track, {
   fieldTransformations: {
+    'Additional Names': parseAdditionalNames,
     'Duration': parseDuration,
 
     'Date First Released': (value) => new Date(value),
@@ -483,6 +484,7 @@ export const processTrackDocument = makeProcessDocument(T.Track, {
   propertyFieldMapping: {
     name: 'Track',
     directory: 'Directory',
+    additionalNames: 'Additional Names',
     duration: 'Duration',
     color: 'Color',
     urls: 'URLs',
@@ -743,26 +745,52 @@ export function parseAdditionalFiles(array) {
   }));
 }
 
-export function parseContributors(contributors) {
+const extractAccentRegex =
+  /^(?<main>.*?)(?: \((?<accent>.*)\))?$/;
+
+export function parseContributors(contributionStrings) {
   // If this isn't something we can parse, just return it as-is.
   // The Thing object's validators will handle the data error better
   // than we're able to here.
-  if (!Array.isArray(contributors)) {
-    return contributors;
+  if (!Array.isArray(contributionStrings)) {
+    return contributionStrings;
   }
 
-  contributors = contributors.map((contrib) => {
-    if (typeof contrib !== 'string') return contrib;
+  return contributionStrings.map(item => {
+    if (typeof item === 'object' && item['Who'])
+      return {who: item['Who'], what: item['What'] ?? null};
+
+    if (typeof item !== 'string') return item;
 
-    const match = contrib.match(/^(.*?)( \((.*)\))?$/);
-    if (!match) return contrib;
+    const match = item.match(extractAccentRegex);
+    if (!match) return item;
 
-    const who = match[1];
-    const what = match[3] || null;
-    return {who, what};
+    return {
+      who: match.groups.main,
+      what: match.groups.accent ?? null,
+    };
   });
+}
+
+export function parseAdditionalNames(additionalNameStrings) {
+  if (!Array.isArray(additionalNameStrings)) {
+    return additionalNameStrings;
+  }
 
-  return contributors;
+  return additionalNameStrings.map(item => {
+    if (typeof item === 'object' && item['Name'])
+      return {name: item['Name'], annotation: item['Annotation'] ?? null};
+
+    if (typeof item !== 'string') return item;
+
+    const match = item.match(extractAccentRegex);
+    if (!match) return item;
+
+    return {
+      name: match.groups.main,
+      annotation: match.groups.accent ?? null,
+    };
+  });
 }
 
 function parseDimensions(string) {