« 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/common-util/wiki-data.js9
-rw-r--r--src/content/dependencies/generateArtistCredit.js26
-rw-r--r--src/content/dependencies/generateCommentaryEntry.js6
-rw-r--r--src/content/dependencies/transformContent.js99
-rw-r--r--src/data/checks.js4
-rw-r--r--src/data/yaml.js2
-rw-r--r--src/replacer.js7
-rw-r--r--src/static/css/site.css53
8 files changed, 182 insertions, 24 deletions
diff --git a/src/common-util/wiki-data.js b/src/common-util/wiki-data.js
index a4c6b3bd..c993f4ac 100644
--- a/src/common-util/wiki-data.js
+++ b/src/common-util/wiki-data.js
@@ -60,7 +60,7 @@ export function getKebabCase(name) {
 // Matches heading details from commentary data in roughly the formats:
 //
 //    <i>artistReference:</i> (annotation, date)
-//    <i>artistReference|artistDisplayText:</i> (annotation, date)
+//    <i>artistReference|artistText:</i> (annotation, date)
 //
 // where capturing group "annotation" can be any text at all, except that the
 // last entry (past a comma or the only content within parentheses), if parsed
@@ -83,8 +83,9 @@ export function getKebabCase(name) {
 // parentheses can be part of the actual annotation content.
 //
 // Capturing group "artistReference" is all the characters between <i> and </i>
-// (apart from the pipe and "artistDisplayText" text, if present), and is either
-// the name of an artist or an "artist:directory"-style reference.
+// (apart from the pipe and the "artistText" group, if present), and is either
+// the name of one or more artist or "artist:directory"-style references,
+// joined by commas, if multiple.
 //
 // This regular expression *doesn't* match bodies, which will need to be parsed
 // out of the original string based on the indices matched using this.
@@ -94,7 +95,7 @@ const dateRegex = groupName =>
   String.raw`(?<${groupName}>[a-zA-Z]+ [0-9]{1,2}, [0-9]{4,4}|[0-9]{1,2} [^,]*[0-9]{4,4}|[0-9]{1,4}[-/][0-9]{1,4}[-/][0-9]{1,4})`;
 
 const commentaryRegexRaw =
-  String.raw`^<i>(?<artistReferences>.+?)(?:\|(?<artistDisplayText>.+))?:<\/i>(?: \((?<annotation>(?:.*?(?=,|\)[^)]*$))*?)(?:,? ?(?:(?<dateKind>sometime|throughout|around) )?${dateRegex('date')}(?: ?- ?${dateRegex('secondDate')})?(?: (?<accessKind>captured|accessed) ${dateRegex('accessDate')})?)?\))?`;
+  String.raw`^<i>(?<artistReferences>.+?)(?:\|(?<artistText>.+))?:<\/i>(?: \((?<annotation>(?:.*?(?=,|\)[^)]*$))*?)(?:,? ?(?:(?<dateKind>sometime|throughout|around) )?${dateRegex('date')}(?: ?- ?${dateRegex('secondDate')})?(?: (?<accessKind>captured|accessed) ${dateRegex('accessDate')})?)?\))?`;
 export const commentaryRegexCaseInsensitive =
   new RegExp(commentaryRegexRaw, 'gmi');
 export const commentaryRegexCaseSensitive =
diff --git a/src/content/dependencies/generateArtistCredit.js b/src/content/dependencies/generateArtistCredit.js
index 6bdbeb23..bab32f7d 100644
--- a/src/content/dependencies/generateArtistCredit.js
+++ b/src/content/dependencies/generateArtistCredit.js
@@ -36,11 +36,18 @@ export default {
     // Note that the normal contributions will implicitly *always*
     // "differ from context" if no context contributions are given,
     // as in release info lines.
-    query.normalContributionsDifferFromContext =
+
+    query.normalContributionArtistsDifferFromContext =
       !compareArrays(
         query.normalContributions.map(({artist}) => artist),
         contextNormalContributions.map(({artist}) => artist),
-        {checkOrder: false});
+        {checkOrder: true});
+
+    query.normalContributionAnnotationsDifferFromContext =
+      !compareArrays(
+        query.normalContributions.map(({annotation}) => annotation),
+        contextNormalContributions.map(({annotation}) => annotation),
+        {checkOrder: true});
 
     return query;
   },
@@ -60,8 +67,11 @@ export default {
   }),
 
   data: (query, _creditContributions, _contextContributions) => ({
-    normalContributionsDifferFromContext:
-      query.normalContributionsDifferFromContext,
+    normalContributionArtistsDifferFromContext:
+      query.normalContributionArtistsDifferFromContext,
+
+    normalContributionAnnotationsDifferFromContext:
+      query.normalContributionAnnotationsDifferFromContext,
 
     hasWikiEdits:
       !empty(query.wikiEditContributions),
@@ -148,8 +158,12 @@ export default {
         ...relations.featuringContributionLinks,
       ]);
 
+    const effectivelyDiffers =
+      (slots.showAnnotation && data.normalContributionAnnotationsDifferFromContext) ||
+      (data.normalContributionArtistsDifferFromContext);
+
     if (empty(relations.featuringContributionLinks)) {
-      if (data.normalContributionsDifferFromContext) {
+      if (effectivelyDiffers) {
         return language.$(slots.normalStringKey, {
           ...slots.additionalStringOptions,
           artists: artistsList,
@@ -159,7 +173,7 @@ export default {
       }
     }
 
-    if (data.normalContributionsDifferFromContext && slots.normalFeaturingStringKey) {
+    if (effectivelyDiffers && slots.normalFeaturingStringKey) {
       return language.$(slots.normalFeaturingStringKey, {
         ...slots.additionalStringOptions,
         artists: artistsList,
diff --git a/src/content/dependencies/generateCommentaryEntry.js b/src/content/dependencies/generateCommentaryEntry.js
index c93020f3..367de506 100644
--- a/src/content/dependencies/generateCommentaryEntry.js
+++ b/src/content/dependencies/generateCommentaryEntry.js
@@ -12,14 +12,14 @@ export default {
 
   relations: (relation, entry) => ({
     artistLinks:
-      (!empty(entry.artists) && !entry.artistDisplayText
+      (!empty(entry.artists) && !entry.artistText
         ? entry.artists
             .map(artist => relation('linkArtist', artist))
         : null),
 
     artistsContent:
-      (entry.artistDisplayText
-        ? relation('transformContent', entry.artistDisplayText)
+      (entry.artistText
+        ? relation('transformContent', entry.artistText)
         : null),
 
     annotationContent:
diff --git a/src/content/dependencies/transformContent.js b/src/content/dependencies/transformContent.js
index 1bbd45e2..805c3625 100644
--- a/src/content/dependencies/transformContent.js
+++ b/src/content/dependencies/transformContent.js
@@ -46,6 +46,14 @@ function getPlaceholder(node, content) {
   return {type: 'text', data: content.slice(node.i, node.iEnd)};
 }
 
+function getArg(node, argKey) {
+  return (
+    node.data.args
+      ?.find(({key}) => key.data === argKey)
+      ?.value ??
+    null);
+}
+
 export default {
   contentDependencies: [
     ...(
@@ -53,6 +61,8 @@ export default {
         .map(description => description.link)
         .filter(Boolean)),
     'image',
+    'generateTextWithTooltip',
+    'generateTooltip',
     'linkExternal',
   ],
 
@@ -134,6 +144,30 @@ export default {
             return {i: node.i, iEnd: node.iEnd, type: 'internal-link', data};
           }
 
+          if (replacerKey === 'tooltip') {
+            // TODO: Again, no recursive nodes. Sorry!
+            // const enteredLabel = node.data.label && transformNode(node.data.label, opts);
+            const enteredLabel = node.data.label?.data;
+
+            return {
+              i: node.i,
+              iEnd: node.iEnd,
+              type: 'tooltip',
+              data: {
+                tooltip:
+                  replacerValue ?? '(empty tooltip...)',
+
+                label:
+                  enteredLabel ?? '(tooltip without label)',
+
+                link:
+                  (getArg(node, 'link')
+                    ? getArg(node, 'link')[0].data
+                    : null),
+              },
+            };
+          }
+
           // This will be another {type: 'tag'} node which gets processed in
           // generate. Extract replacerKey and replacerValue now, since it'd
           // be a pain to deal with later.
@@ -191,6 +225,12 @@ export default {
           : getPlaceholder(node, content));
 
     return {
+      textWithTooltip:
+        relation('generateTextWithTooltip'),
+
+      tooltip:
+        relation('generateTooltip'),
+
       internalLinks:
         nodes
           .filter(({type}) => type === 'internal-link')
@@ -209,11 +249,15 @@ export default {
       externalLinks:
         nodes
           .filter(({type}) => type === 'external-link')
-          .map(node => {
-            const {href} = node.data;
+          .map(({data: {href}}) =>
+            relation('linkExternal', href)),
 
-            return relation('linkExternal', href);
-          }),
+      externalLinksForTooltipNodes:
+        nodes
+          .filter(({type}) => type === 'tooltip')
+          .filter(({data}) => data.link)
+          .map(({data: {link: href}}) =>
+            relation('linkExternal', href)),
 
       images:
         nodes
@@ -259,6 +303,7 @@ export default {
     let imageIndex = 0;
     let internalLinkIndex = 0;
     let externalLinkIndex = 0;
+    let externalLinkForTooltipNodeIndex = 0;
 
     let offsetTextNode = 0;
 
@@ -548,6 +593,52 @@ export default {
             return {type: 'processed-external-link', data: externalLink};
           }
 
+          case 'tooltip': {
+            const {label, link, tooltip: tooltipContent} = node.data;
+
+            const externalLink =
+              (link
+                ? relations.externalLinksForTooltipNodes
+                    .at(externalLinkForTooltipNodeIndex++)
+                : null);
+
+            if (externalLink) {
+              externalLink.setSlots({
+                content: label,
+                fromContent: true,
+              });
+
+              if (slots.indicateExternalLinks) {
+                externalLink.setSlots({
+                  indicateExternal: true,
+                  disableBrowserTooltip: true,
+                  tab: 'separate',
+                  style: 'platform',
+                });
+              }
+            }
+
+            const textWithTooltip = relations.textWithTooltip.clone();
+            const tooltip = relations.tooltip.clone();
+
+            tooltip.setSlots({
+              attributes: {class: 'content-tooltip'},
+              content: tooltipContent, // Not sanitized!
+            });
+
+            textWithTooltip.setSlots({
+              attributes: [
+                {class: 'content-tooltip-guy'},
+                externalLink && {class: 'has-link'},
+              ],
+
+              text: externalLink ?? label,
+              tooltip,
+            });
+
+            return {type: 'processed-tooltip', data: textWithTooltip};
+          }
+
           case 'tag': {
             const {replacerKey, replacerValue} = node.data;
 
diff --git a/src/data/checks.js b/src/data/checks.js
index 52024144..c395f2f0 100644
--- a/src/data/checks.js
+++ b/src/data/checks.js
@@ -567,13 +567,13 @@ export function reportContentTextErrors(wikiData, {
 
   const commentaryShape = {
     body: 'commentary body',
-    artistDisplayText: 'commentary artist display text',
+    artistText: 'commentary artist text',
     annotation: 'commentary annotation',
   };
 
   const lyricsShape = {
     body: 'lyrics body',
-    artistDisplayText: 'lyrics artist display text',
+    artistText: 'lyrics artist text',
     annotation: 'lyrics annotation',
   };
 
diff --git a/src/data/yaml.js b/src/data/yaml.js
index 036fe8a7..c6600121 100644
--- a/src/data/yaml.js
+++ b/src/data/yaml.js
@@ -835,7 +835,7 @@ export function parseContentEntries(thingClass, sourceText, {subdoc}) {
         .map(ref => ref.trim()),
 
     'Artist Text':
-      matchEntry.artistDisplayText,
+      matchEntry.artistText,
 
     'Annotation':
       matchEntry.annotation,
diff --git a/src/replacer.js b/src/replacer.js
index 32657a5a..d285a7ea 100644
--- a/src/replacer.js
+++ b/src/replacer.js
@@ -174,6 +174,11 @@ export const replacerSpec = {
     find: 'trackWithArtwork',
     link: 'linkTrackReferencingArtworks',
   },
+
+  'tooltip': {
+    value: (ref) => ref,
+    link: null,
+  }
 };
 
 // Syntax literals.
@@ -649,6 +654,7 @@ export function postprocessVideos(inputNodes) {
 
       if (attributes.get('width')) node.width = parseInt(attributes.get('width'));
       if (attributes.get('height')) node.height = parseInt(attributes.get('height'));
+      if (attributes.get('align')) node.align = attributes.get('align');
       if (attributes.get('pixelate')) node.pixelate = true;
 
       return node;
@@ -662,6 +668,7 @@ export function postprocessAudios(inputNodes) {
 
       node.src = attributes.get('src');
       node.inline = attributes.get('inline') ?? inline;
+      if (attributes.get('align')) node.align = attributes.get('align');
 
       return node;
     });
diff --git a/src/static/css/site.css b/src/static/css/site.css
index 962dc896..60e93708 100644
--- a/src/static/css/site.css
+++ b/src/static/css/site.css
@@ -1246,7 +1246,8 @@ li:not(:first-child:last-child) .tooltip:where(:not(.cover-artwork .tooltip)),
 .missing-duration-tooltip,
 .commentary-date-tooltip,
 .rerelease-tooltip,
-.first-release-tooltip {
+.first-release-tooltip,
+.content-tooltip {
   padding: 3px 4px 2px 2px;
   left: -10px;
 }
@@ -1428,6 +1429,30 @@ li:not(:first-child:last-child) .tooltip:where(:not(.cover-artwork .tooltip)),
   font-size: 0.9em;
 }
 
+.content-tooltip-guy .hoverable a {
+  text-decoration-color: transparent;
+  text-decoration-style: dotted;
+}
+
+.content-tooltip-guy {
+  display: inline-block;
+}
+
+.content-tooltip-guy.has-link .text-with-tooltip-interaction-cue {
+  text-decoration-color: var(--primary-color);
+}
+
+.content-tooltip .tooltip-content {
+  padding: 3px 4.5px;
+  width: 240px;
+}
+
+.cover-artwork .content-tooltip {
+  font-size: 0.85rem;
+  padding: 2px 3px;
+  width: 220px;
+}
+
 .external-icon {
   display: inline-block;
   padding: 0 3px;
@@ -1478,6 +1503,22 @@ s.spoiler::-moz-selection {
   background: white;
 }
 
+span.path {
+  font-size: 0.9em;
+  font-family: "courier new", monospace;
+  font-weight: 800;
+}
+
+span.path i {
+  display: inline-block;
+  font-style: normal;
+}
+
+span.path i::before {
+  content: "\0020/\0020";
+  color: #ccc;
+}
+
 progress {
   accent-color: var(--primary-color);
 }
@@ -1741,9 +1782,7 @@ p.image-details.origin-details {
   margin-bottom: 1em;
 }
 
-.content-image-container.align-center,
-.content-video-container.align-center,
-.content-audio-container.align-center {
+.content-image-container.align-center {
   text-align: center;
   margin-top: 1.5em;
   margin-bottom: 1.5em;
@@ -2469,6 +2508,12 @@ html[data-url-key="localized.listing"][data-url-value0="tags/network"] dl dt:las
   display: block;
 }
 
+.content-video-container.align-center,
+.content-audio-container.align-center {
+  margin-left: auto;
+  margin-right: auto;
+}
+
 .image-text-area {
   position: absolute;
   top: 0;