diff options
Diffstat (limited to 'src/static')
-rw-r--r-- | src/static/css/site.css | 245 | ||||
-rw-r--r-- | src/static/js/client.js | 259 |
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) { |