« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/static
diff options
context:
space:
mode:
Diffstat (limited to 'src/static')
-rw-r--r--src/static/css/site.css245
-rw-r--r--src/static/js/client.js259
2 files changed, 291 insertions, 213 deletions
diff --git a/src/static/css/site.css b/src/static/css/site.css
index de28ad58..80801c85 100644
--- a/src/static/css/site.css
+++ b/src/static/css/site.css
@@ -514,23 +514,77 @@ summary .group-name {
   filter: brightness(0.7);
 }
 
-.wiki-search-input {
+.wiki-search-label {
   width: calc(100% - 4px);
   padding: 2px 4px;
   margin: 2px 2px 3px 2px;
   box-sizing: border-box;
 
+  display: flex;
+  flex-direction: row;
+
   background: transparent;
   border: 1px solid var(--dim-color);
   border-radius: 3px;
+}
+
+.wiki-search-label::before {
+  display: inline-block;
+  padding-left: 3px;
+  padding-right: 3px;
+  margin-right: 3px;
+  width: 1.8em;
+  text-align: center;
+  content: '\1f50d\fe0e';
+}
+
+.wiki-search-input {
+  background: transparent;
+  border: transparent;
   color: inherit;
+  flex-grow: 1;
 }
 
-.wiki-search-input[disabled] {
+.wiki-search-input::-webkit-search-cancel-button {
+  filter: grayscale(1) invert(1);
+}
+
+.wiki-search-label.disabled {
   opacity: 0.6;
+}
+
+.wiki-search-label.disabled,
+.wiki-search-input[disabled] {
   cursor: not-allowed;
 }
 
+.wiki-search-label:not(.disabled):hover,
+.wiki-search-label:focus-within {
+  background: var(--light-ghost-color);
+}
+
+.wiki-search-label:focus-within {
+  border-color: var(--primary-color);
+}
+
+.wiki-search-label:focus-within::before {
+  opacity: 0.7;
+}
+
+.wiki-search-input:focus {
+  border: none;
+  outline: none;
+}
+
+.wiki-search-input::placeholder {
+  color: var(--primary-color);
+  font-style: oblique;
+}
+
+.wiki-search-input:focus::placeholder {
+  color: var(--dim-color);
+}
+
 .wiki-search-sidebar-box hr {
   border-color: var(--primary-color);
   border-style: none none dotted none;
@@ -694,19 +748,6 @@ summary .group-name {
   border-color: var(--deep-color);
 }
 
-.wiki-search-input:focus {
-  border-color: var(--primary-color);
-}
-
-.wiki-search-input::placeholder {
-  color: var(--primary-color);
-  font-style: oblique;
-}
-
-.wiki-search-input:focus::placeholder {
-  color: var(--dim-color);
-}
-
 #content {
   overflow-wrap: anywhere;
 }
@@ -776,24 +817,6 @@ a:not([href]):hover {
   content: "\0020/\0020";
 }
 
-#header .chronology .heading,
-#header .chronology .buttons {
-  white-space: nowrap;
-}
-
-#header .scoped-chronology {
-  display: none;
-}
-
-#header .scoped-chronology-switcher .switcher-link {
-  text-decoration: underline;
-  text-decoration-style: dotted;
-}
-
-#header .scoped-chronology-switcher > div {
-  margin-left: 20px;
-}
-
 #secondary-nav {
   text-align: center;
 }
@@ -869,7 +892,7 @@ li:not(:first-child:last-child) .tooltip,
     0 -2px 4px -2px var(--primary-color) inset;
 }
 
-.icons-tooltip {
+.contribution-tooltip {
   padding: 3px 6px 6px 6px;
   left: -34px;
 }
@@ -890,7 +913,7 @@ li:not(:first-child:last-child) .tooltip,
   margin-right: -120px;
 }
 
-.icons-tooltip .tooltip-content {
+.contribution-tooltip .tooltip-content {
   padding: 6px 2px 2px 2px;
 
   -webkit-user-select: none;
@@ -901,42 +924,122 @@ li:not(:first-child:last-child) .tooltip,
   display: grid;
 
   grid-template-columns:
-    [icon-start] auto [icon-end domain-start] auto [domain-end];
+    [icon-start] 26px [icon-end handle-start] auto [handle-end platform-start] auto [platform-end];
 }
 
-.icons-tooltip .icon {
+.contribution-tooltip .external-link {
+  display: grid;
+  grid-column-start: icon-start;
+  grid-column-end: handle-end;
+  grid-template-columns: subgrid;
+
+  height: 1.4em;
+}
+
+.contribution-tooltip .chronology-link {
+  display: grid;
+  grid-column-start: icon-start;
+  grid-column-end: handle-end;
+  grid-template-columns: subgrid;
+
+  height: 1.2em;
+}
+
+.contribution-tooltip .external-icon,
+.contribution-tooltip .chronology-symbol {
   grid-column-start: icon-start;
   grid-column-end: icon-end;
 }
 
-.icons-tooltip .icon-platform {
+.contribution-tooltip .external-icon svg {
+  width: 18px;
+  height: 18px;
+  top: -0.1em;
+}
+
+.contribution-tooltip .chronology-symbol {
+  text-align: center;
+}
+
+.contribution-tooltip .external-handle,
+.contribution-tooltip .chronology-text {
+  grid-column-start: handle-start;
+  grid-column-end: handle-end;
+
+  width: max-content;
+  max-width: 200px;
+
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}
+
+.contribution-tooltip .external-handle {
+  padding-right: 8px;
+}
+
+.contribution-tooltip .chronology-text {
+  padding-right: 6px;
+}
+
+.contribution-tooltip .chronology-text,
+.contribution-tooltip .chronology-info {
+  font-size: 0.85em;
+}
+
+.contribution-tooltip .tooltip-divider {
+  grid-column-start: icon-start;
+  grid-column-end: platform-end;
+
+  border-top: 1px dotted var(--primary-color);
+  margin-top: 3px;
+  margin-bottom: 4px;
+}
+
+.contribution-tooltip .external-platform,
+.contribution-tooltip .chronology-info {
   display: none;
 
-  grid-column-start: domain-start;
-  grid-column-end: domain-end;
+  grid-column-start: platform-start;
+  grid-column-end: platform-end;
 
-  --icon-platform-opacity: 0.8;
-  padding-right: 4px;
+  --external-platform-opacity: 0.8;
   opacity: 0.8;
+  padding-right: 4px;
+
+  white-space: nowrap;
 }
 
-.icons-tooltip.show-info .icon-platform {
+.contribution-tooltip.show-info .external-platform,
+.contribution-tooltip.show-info .chronology-info {
   display: inline;
-  animation: icon-platform 0.2s forwards linear;
+  animation: external-platform 0.2s forwards linear;
 }
 
-@keyframes icon-platform {
+@keyframes external-platform {
   from {
     opacity: 0;
   }
 
   to {
-    opacity: var(--icon-platform-opacity);
+    opacity: var(--external-platform-opacity);
   }
 }
 
-.icons-tooltip .icon:hover + .icon-platform {
-  --icon-platform-opacity: 1;
+.contribution-tooltip .external-link:hover,
+.contribution-tooltip .chronology-link:hover {
+  filter: brightness(1.4);
+  text-decoration: none;
+}
+
+.contribution-tooltip .external-link:hover .external-handle,
+.contribution-tooltip .chronology-link:hover .chronology-text {
+  text-decoration: underline;
+}
+
+.contribution-tooltip .external-link:hover + .external-platform,
+.contribution-tooltip .chronology-link:hover + .chronology-info {
+  --external-platform-opacity: 1;
   text-decoration: underline;
   text-decoration-color: #ffffffaa;
 }
@@ -952,27 +1055,15 @@ li:not(:first-child:last-child) .tooltip,
   padding: 3px 4.5px;
 }
 
-.icons {
-  font-style: normal;
-  white-space: nowrap;
-}
-
-.icons a:hover {
-  filter: brightness(1.4);
-}
-
-.icons a {
-  padding: 0 3px;
-}
-
-.icon {
+.external-icon {
   display: inline-block;
+  padding: 0 3px;
   width: 24px;
   height: 1em;
   position: relative;
 }
 
-.icon > svg {
+.external-icon svg {
   width: 24px;
   height: 24px;
   top: -0.25em;
@@ -980,23 +1071,6 @@ li:not(:first-child:last-child) .tooltip,
   fill: var(--primary-color);
 }
 
-.icon.has-text {
-  display: block;
-  width: unset;
-  height: 1.4em;
-}
-
-.icon.has-text > svg {
-  width: 18px;
-  height: 18px;
-  top: -0.1em;
-}
-
-.icon.has-text > .icon-text {
-  margin-left: 24px;
-  padding-right: 8px;
-}
-
 .rerelease,
 .other-group-accent {
   opacity: 0.7;
@@ -1268,10 +1342,13 @@ ul.quick-info li:not(:last-child)::after {
 }
 
 .quick-description:not(.has-external-links-only) {
-  margin-left: 8%;
-  margin-right: 8%;
-  padding-left: 4%;
-  padding-right: 4%;
+  --clamped-padding-ratio: max(var(--responsive-padding-ratio), 0.06);
+  margin-left: auto;
+  margin-right: auto;
+  padding-left: calc(0.40 * var(--clamped-padding-ratio) * 100%);
+  padding-right: calc(0.40 * var(--clamped-padding-ratio) * 100%);
+  max-width: 500px;
+
   padding-top: 0.25em;
   padding-bottom: 0.75em;
   border-left: 1px solid var(--dim-color);
diff --git a/src/static/js/client.js b/src/static/js/client.js
index 935a9d87..21c3911a 100644
--- a/src/static/js/client.js
+++ b/src/static/js/client.js
@@ -1271,6 +1271,11 @@ const hoverableTooltipInfo = initInfo('hoverableTooltipInfo', {
     // from causing the current tooltip to be hidden.
     currentTouchIdentifiers: new Set(),
     touchIdentifiersBanishedByScrolling: new Set(),
+
+    // This is a two-item array that tracks the direction we've already
+    // dynamically placed the current tooltip. If we *reposition* the tooltip
+    // (because its dimensions changed), we'll try to follow this anchor first.
+    dynamicTooltipAnchorDirection: null,
   },
 
   event: {
@@ -1731,6 +1736,8 @@ function hideCurrentlyShownTooltip(intendingToReplace = false) {
   state.currentlyShownTooltip = null;
   state.currentlyActiveHoverable = null;
 
+  state.dynamicTooltipAnchorDirection = null;
+
   // Set this for one tick of the event cycle.
   state.tooltipWasJustHidden = true;
   setTimeout(() => {
@@ -1758,6 +1765,11 @@ function showTooltipFromHoverable(hoverable) {
 
   positionTooltipFromHoverableWithBrains(hoverable);
 
+  // After a tooltip is shown, if we *didn't* specify an anchor,
+  // assume it was shown in its default position - generally presented
+  // as down and to the right. Successive repositioning will base on this.
+  state.dynamicTooltipAnchorDirection ??= ['down', 'right'];
+
   cssProp(tooltip, 'display', 'block');
   tooltip.inert = false;
 
@@ -1792,10 +1804,23 @@ function peekTooltipClientRect(tooltip) {
   }
 }
 
+function repositionCurrentTooltip() {
+  const {state} = hoverableTooltipInfo;
+  const {currentlyActiveHoverable} = state;
+
+  if (!currentlyActiveHoverable) {
+    throw new Error(`No hoverable active to reposition tooltip from`);
+  }
+
+  positionTooltipFromHoverableWithBrains(currentlyActiveHoverable);
+}
+
 function positionTooltipFromHoverableWithBrains(hoverable) {
   const {state} = hoverableTooltipInfo;
   const {tooltip} = state.registeredHoverables.get(hoverable);
 
+  const anchorDirection = state.dynamicTooltipAnchorDirection;
+
   // Reset before doing anything else. We're going to adapt to
   // its natural placement, adjusted by CSS, which otherwise
   // could be obscured by a placement we've previously provided.
@@ -1817,23 +1842,42 @@ function positionTooltipFromHoverableWithBrains(hoverable) {
     return;
   }
 
-  let selectedRect = null;
-  for (let i = 0; i < numBaselineRects; i++) {
-    selectedRect = opportunities.right.down[i];
-    if (selectedRect) break;
+  const tryDirection = (dir1, dir2, i) => {
+    selectedRect = opportunities[dir1][dir2][i];
+    return !!selectedRect;
+  };
 
-    selectedRect = opportunities.left.down[i];
-    if (selectedRect) break;
+  let selectedRect = null;
+  selectRect: {
+    if (anchorDirection) {
+      for (let i = 0; i < numBaselineRects; i++) {
+        if (tryDirection(...anchorDirection, i)) {
+          break selectRect;
+        }
+      }
+    }
 
-    selectedRect = opportunities.right.up[i];
-    if (selectedRect) break;
+    for (let i = 0; i < numBaselineRects; i++) {
+      for (const [dir1, dir2] of [
+        ['right', 'down'],
+        ['left', 'down'],
+        ['right', 'up'],
+        ['left', 'up'],
+        ['down', 'right'],
+        ['down', 'left'],
+        ['up', 'right'],
+        ['up', 'left'],
+      ]) {
+        if (tryDirection(dir1, dir2, i)) {
+          state.dynamicTooltipAnchorDirection = [dir1, dir2];
+          break selectRect;
+        }
+      }
+    }
 
-    selectedRect = opportunities.left.up[i];
-    if (selectedRect) break;
+    selectedRect = baselineRect;
   }
 
-  selectedRect ??= baselineRect;
-
   positionTooltip(tooltip, selectedRect.x, selectedRect.y);
 }
 
@@ -1929,18 +1973,18 @@ function getTooltipFromHoverablePlacementOpportunityAreas(hoverable) {
   const neededVerticalOverlap = 30;
   const neededHorizontalOverlap = 30;
 
+  const upTopDown =
+    WikiRect.beneath(
+      hoverableRect.top + neededVerticalOverlap - tooltipRect.height);
+
+  const downBottomUp =
+    WikiRect.above(
+      hoverableRect.bottom - neededVerticalOverlap + tooltipRect.height);
+
   // Please don't ask us to make this but horizontal?
   const prepareVerticalOrientationRects = (regionRects) => {
     const orientations = {};
 
-    const upTopDown =
-      WikiRect.beneath(
-        hoverableRect.top + neededVerticalOverlap - tooltipRect.height);
-
-    const downBottomUp =
-      WikiRect.above(
-        hoverableRect.bottom - neededVerticalOverlap + tooltipRect.height);
-
     const orientHorizontally = (rect, i) => {
       if (!rect) return null;
 
@@ -1996,9 +2040,67 @@ function getTooltipFromHoverablePlacementOpportunityAreas(hoverable) {
     return orientations;
   };
 
+  const rightRightLeft =
+    WikiRect.leftOf(
+      hoverableRect.left - neededHorizontalOverlap + tooltipRect.width);
+
+  const leftLeftRight =
+    WikiRect.rightOf(
+      hoverableRect.left + neededHorizontalOverlap - tooltipRect.width);
+
+  // Oops.
+  const prepareHorizontalOrientationRects = (regionRects) => {
+    const orientations = {};
+
+    const orientVertically = (rect, i) => {
+      if (!rect) return null;
+
+      const regionRect = regionRects[i];
+
+      if (regionRect.height > 0) {
+        return rect;
+      } else {
+        return WikiRect.fromRect({
+          x: rect.x,
+          y: regionRect.bottom - tooltipRect.height,
+          width: rect.width,
+          height: rect.height,
+        });
+      }
+    };
+
+    orientations.left =
+      regionRects
+        .map(rect => rect?.intersectionWith(leftLeftRight))
+        .map(orientVertically)
+        .map(keepIfFits);
+
+    orientations.right =
+      regionRects
+        .map(rect => rect?.intersectionWith(rightRightLeft))
+        .map(rect =>
+          (rect
+            ? rect.intersectionWith(WikiRect.fromRect({
+                x: rect.right - tooltipRect.width,
+                y: rect.y,
+                width: rect.width,
+                height: tooltipRect.height,
+              }))
+            : null))
+        .map(orientVertically)
+        .map(keepIfFits);
+
+    // No analogous center because we don't actually use
+    // center alignment...
+
+    return orientations;
+  };
+
   const orientationRects = {
     left: prepareVerticalOrientationRects(regionRects.left),
     right: prepareVerticalOrientationRects(regionRects.right),
+    down: prepareHorizontalOrientationRects(regionRects.bottom),
+    up: prepareHorizontalOrientationRects(regionRects.top),
   };
 
   return {
@@ -3106,114 +3208,6 @@ clientSteps.getPageReferences.push(getAdditionalNamesBoxReferences);
 clientSteps.addInternalListeners.push(addAdditionalNamesBoxInternalListeners);
 clientSteps.addPageListeners.push(addAdditionalNamesBoxListeners);
 
-// Scoped chronology links --------------------------------
-
-const scopedChronologyLinksInfo = initInfo('scopedChronologyLinksInfo', {
-  switcher: null,
-  containers: null,
-  switcherLinks: null,
-  modes: null,
-
-  session: {
-    selectedMode: 'wiki',
-  },
-});
-
-function getScopedChronologyLinksReferences() {
-  const info = scopedChronologyLinksInfo;
-
-  info.switcher =
-    document.querySelector('.scoped-chronology-switcher');
-
-  if (!info.switcher) {
-    return;
-  }
-
-  info.containers =
-    Array.from(info.switcher.querySelectorAll(':scope > div'));
-
-  info.switcherLinks =
-    Array.from(info.switcher.querySelectorAll('.switcher-link'));
-
-  info.modes =
-    info.containers
-      .map(container =>
-        Array.from(container.classList)
-          .find(className => className.startsWith('scope-'))
-          .slice('scope-'.length));
-}
-
-function addScopedChronologyLinksPageHandlers() {
-  const info = scopedChronologyLinksInfo;
-  const {session} = scopedChronologyLinksInfo;
-
-  if (!info.switcher) {
-    return;
-  }
-
-  for (const [index, {
-    container: currentContainer,
-    switcherLink: currentSwitcherLink,
-  }] of stitchArrays({
-    container: info.containers,
-    switcherLink: info.switcherLinks,
-  }).entries()) {
-    const nextContainer =
-      atOffset(info.containers, index, +1, {wrap: true});
-
-    const nextSwitcherLink =
-      atOffset(info.switcherLinks, index, +1, {wrap: true});
-
-    const nextMode =
-      atOffset(info.modes, index, +1, {wrap: true});
-
-    currentSwitcherLink.addEventListener('click', domEvent => {
-      domEvent.preventDefault();
-
-      cssProp(currentContainer, 'display', 'none');
-      cssProp(currentSwitcherLink, 'display', 'none');
-      cssProp(nextContainer, 'display', 'block');
-      cssProp(nextSwitcherLink, 'display', 'inline');
-
-      session.selectedMode = nextMode;
-    });
-  }
-}
-
-function mutateScopedChronologyLinksContent() {
-  const info = scopedChronologyLinksInfo;
-
-  if (!info.switcher) {
-    return;
-  }
-
-  const {selectedMode} = info.session;
-
-  if (info.modes.includes(selectedMode)) {
-    const selectedIndex = info.modes.indexOf(selectedMode);
-
-    for (const [index, {
-      container,
-      switcherLink,
-    }] of stitchArrays({
-      container: info.containers,
-      switcherLink: info.switcherLinks,
-    }).entries()) {
-      if (index === selectedIndex) {
-        cssProp(container, 'display', 'block');
-        cssProp(switcherLink, 'display', 'inline');
-      } else {
-        cssProp(container, 'display', 'none');
-        cssProp(switcherLink, 'display', 'none');
-      }
-    }
-  }
-}
-
-clientSteps.getPageReferences.push(getScopedChronologyLinksReferences);
-clientSteps.mutatePageContent.push(mutateScopedChronologyLinksContent);
-clientSteps.addPageListeners.push(addScopedChronologyLinksPageHandlers);
-
 // Group contributions table ------------------------------
 
 // TODO: Update to clientSteps style.
@@ -3363,7 +3357,7 @@ function getArtistExternalLinkTooltipPageReferences() {
   const info = artistExternalLinkTooltipInfo;
 
   info.tooltips =
-    Array.from(document.getElementsByClassName('icons-tooltip'));
+    Array.from(document.getElementsByClassName('contribution-tooltip'));
 
   info.tooltipRows =
     info.tooltips.map(tooltip =>
@@ -3507,6 +3501,8 @@ function showArtistExternalLinkTooltipInfo() {
   for (const tooltip of info.tooltips) {
     tooltip.classList.add('show-info');
   }
+
+  repositionCurrentTooltip();
 }
 
 function hideArtistExternalLinkTooltipInfo() {
@@ -3831,6 +3827,7 @@ const sidebarSearchInfo = initInfo('sidebarSearchInfo', {
 
   searchSidebarColumn: null,
   searchBox: null,
+  searchLabel: null,
   searchInput: null,
 
   progressRule: null,
@@ -3919,6 +3916,9 @@ function getSidebarSearchReferences() {
     return;
   }
 
+  info.searchLabel =
+    info.searchBox.querySelector('.wiki-search-label');
+
   info.searchInput =
     info.searchBox.querySelector('.wiki-search-input');
 
@@ -4360,6 +4360,7 @@ function showSidebarSearchFailed() {
   cssProp(info.failedRule, 'display', null);
   cssProp(info.failedContainer, 'display', null);
 
+  info.searchLabel.classList.add('disabled');
   info.searchInput.disabled = true;
 
   if (state.stoppedTypingTimeout) {