diff options
Diffstat (limited to 'src/static/js/client')
-rw-r--r-- | src/static/js/client/expandable-gallery-section.js | 77 | ||||
-rw-r--r-- | src/static/js/client/hoverable-tooltip.js | 30 | ||||
-rw-r--r-- | src/static/js/client/image-overlay.js | 5 | ||||
-rw-r--r-- | src/static/js/client/index.js | 2 | ||||
-rw-r--r-- | src/static/js/client/quick-description.js | 2 | ||||
-rw-r--r-- | src/static/js/client/sidebar-search.js | 260 | ||||
-rw-r--r-- | src/static/js/client/sticky-heading.js | 15 |
7 files changed, 342 insertions, 49 deletions
diff --git a/src/static/js/client/expandable-gallery-section.js b/src/static/js/client/expandable-gallery-section.js new file mode 100644 index 00000000..dc83e8b7 --- /dev/null +++ b/src/static/js/client/expandable-gallery-section.js @@ -0,0 +1,77 @@ +/* eslint-env browser */ + +// TODO: Combine this and quick-description.js + +import {cssProp} from '../client-util.js'; + +import {stitchArrays} from '../../shared-util/sugar.js'; + +export const info = { + id: 'expandableGallerySectionInfo', + + sections: null, + + sectionContentBelowCut: null, + + sectionExpandoToggles: null, + + sectionExpandCues: null, + sectionCollapseCues: null, +}; + +export function getPageReferences() { + info.sections = + Array.from(document.querySelectorAll('.expandable-gallery-section')) + .filter(section => section.querySelector('.section-expando-toggle')); + + info.sectionContentBelowCut = + info.sections + .map(section => section.querySelector('.section-content-below-cut')); + + info.sectionExpandoToggles = + info.sections + .map(section => section.querySelector('.section-expando-toggle')); + + info.sectionExpandCues = + info.sections + .map(section => section.querySelector('.section-expand-cue')); + + info.sectionCollapseCues = + info.sections + .map(section => section.querySelector('.section-collapse-cue')); +} + +export function addPageListeners() { + for (const { + section, + contentBelowCut, + expandoToggle, + expandCue, + collapseCue, + } of stitchArrays({ + section: info.sections, + contentBelowCut: info.sectionContentBelowCut, + expandoToggle: info.sectionExpandoToggles, + expandCue: info.sectionExpandCues, + collapseCue: info.sectionCollapseCues, + })) { + expandoToggle.addEventListener('click', domEvent => { + domEvent.preventDefault(); + + const collapsed = + cssProp(contentBelowCut, 'display') === 'none'; + + if (collapsed) { + section.classList.add('expanded'); + cssProp(contentBelowCut, 'display', null); + cssProp(expandCue, 'display', 'none'); + cssProp(collapseCue, 'display', null); + } else { + section.classList.remove('expanded'); + cssProp(contentBelowCut, 'display', 'none'); + cssProp(expandCue, 'display', null); + cssProp(collapseCue, 'display', 'none'); + } + }); + } +} diff --git a/src/static/js/client/hoverable-tooltip.js b/src/static/js/client/hoverable-tooltip.js index 9569de3e..89119a47 100644 --- a/src/static/js/client/hoverable-tooltip.js +++ b/src/static/js/client/hoverable-tooltip.js @@ -118,17 +118,17 @@ export function registerTooltipElement(tooltip) { handleTooltipMouseLeft(tooltip); }); - tooltip.addEventListener('focusin', event => { - handleTooltipReceivedFocus(tooltip, event.relatedTarget); + tooltip.addEventListener('focusin', domEvent => { + handleTooltipReceivedFocus(tooltip, domEvent.relatedTarget); }); - tooltip.addEventListener('focusout', event => { + tooltip.addEventListener('focusout', domEvent => { // This event gets activated for tabbing *between* links inside the // tooltip, which is no good and certainly doesn't represent the focus // leaving the tooltip. - if (currentlyShownTooltipHasFocus(event.relatedTarget)) return; + if (currentlyShownTooltipHasFocus(domEvent.relatedTarget)) return; - handleTooltipLostFocus(tooltip, event.relatedTarget); + handleTooltipLostFocus(tooltip, domEvent.relatedTarget); }); } @@ -158,20 +158,20 @@ export function registerTooltipHoverableElement(hoverable, tooltip) { handleTooltipHoverableMouseLeft(hoverable); }); - hoverable.addEventListener('focusin', event => { - handleTooltipHoverableReceivedFocus(hoverable, event); + hoverable.addEventListener('focusin', domEvent => { + handleTooltipHoverableReceivedFocus(hoverable, domEvent); }); - hoverable.addEventListener('focusout', event => { - handleTooltipHoverableLostFocus(hoverable, event); + hoverable.addEventListener('focusout', domEvent => { + handleTooltipHoverableLostFocus(hoverable, domEvent); }); - hoverable.addEventListener('touchend', event => { - handleTooltipHoverableTouchEnded(hoverable, event); + hoverable.addEventListener('touchend', domEvent => { + handleTooltipHoverableTouchEnded(hoverable, domEvent); }); - hoverable.addEventListener('click', event => { - handleTooltipHoverableClicked(hoverable, event); + hoverable.addEventListener('click', domEvent => { + handleTooltipHoverableClicked(hoverable, domEvent); }); } @@ -416,7 +416,7 @@ function handleTooltipHoverableTouchEnded(hoverable, domEvent) { }, 1200); } -function handleTooltipHoverableClicked(hoverable) { +function handleTooltipHoverableClicked(hoverable, domEvent) { const {state} = info; // Don't navigate away from the page if the this hoverable was recently @@ -426,7 +426,7 @@ function handleTooltipHoverableClicked(hoverable) { state.currentlyActiveHoverable === hoverable && state.hoverableWasRecentlyTouched ) { - event.preventDefault(); + domEvent.preventDefault(); } } diff --git a/src/static/js/client/image-overlay.js b/src/static/js/client/image-overlay.js index da192178..e9e2708d 100644 --- a/src/static/js/client/image-overlay.js +++ b/src/static/js/client/image-overlay.js @@ -96,7 +96,10 @@ function handleContainerClicked(evt) { // If you clicked anything near the action bar, don't hide the // image overlay. const rect = info.actionContainer.getBoundingClientRect(); - if (evt.clientY >= rect.top - 40 && evt.clientY <= rect.bottom + 40) { + if ( + evt.clientY >= rect.top - 40 && evt.clientY <= rect.bottom + 40 && + evt.clientX >= rect.left + 20 && evt.clientX <= rect.right - 20 + ) { return; } diff --git a/src/static/js/client/index.js b/src/static/js/client/index.js index 81ea3415..aeb9264a 100644 --- a/src/static/js/client/index.js +++ b/src/static/js/client/index.js @@ -10,6 +10,7 @@ import * as artistExternalLinkTooltipModule from './artist-external-link-tooltip import * as cssCompatibilityAssistantModule from './css-compatibility-assistant.js'; import * as datetimestampTooltipModule from './datetimestamp-tooltip.js'; import * as draggedLinkModule from './dragged-link.js'; +import * as expandableGallerySectionModule from './expandable-gallery-section.js'; import * as hashLinkModule from './hash-link.js'; import * as hoverableTooltipModule from './hoverable-tooltip.js'; import * as imageOverlayModule from './image-overlay.js'; @@ -32,6 +33,7 @@ export const modules = [ cssCompatibilityAssistantModule, datetimestampTooltipModule, draggedLinkModule, + expandableGallerySectionModule, hashLinkModule, hoverableTooltipModule, imageOverlayModule, diff --git a/src/static/js/client/quick-description.js b/src/static/js/client/quick-description.js index cff82252..6a7a6023 100644 --- a/src/static/js/client/quick-description.js +++ b/src/static/js/client/quick-description.js @@ -1,5 +1,7 @@ /* eslint-env browser */ +// TODO: Combine this and expandable-gallery-section.js + import {stitchArrays} from '../../shared-util/sugar.js'; export const info = { diff --git a/src/static/js/client/sidebar-search.js b/src/static/js/client/sidebar-search.js index fb902636..eae1e74e 100644 --- a/src/static/js/client/sidebar-search.js +++ b/src/static/js/client/sidebar-search.js @@ -1,7 +1,7 @@ /* eslint-env browser */ import {getColors} from '../../shared-util/colors.js'; -import {accumulateSum, empty} from '../../shared-util/sugar.js'; +import {accumulateSum, empty, unique} from '../../shared-util/sugar.js'; import { cssProp, @@ -41,6 +41,14 @@ export const info = { failedRule: null, failedContainer: null, + filterContainer: null, + albumFilterLink: null, + artistFilterLink: null, + flashFilterLink: null, + groupFilterLink: null, + tagFilterLink: null, + trackFilterLink: null, + resultsRule: null, resultsContainer: null, results: null, @@ -65,6 +73,13 @@ export const info = { groupResultKindString: null, tagResultKindString: null, + albumResultFilterString: null, + artistResultFilterString: null, + flashResultFilterString: null, + groupResultFilterString: null, + tagResultFilterString: null, + trackResultFilterString: null, + state: { sidebarColumnShownForSearch: null, @@ -97,6 +112,10 @@ export const info = { maxLength: settings => settings.maxActiveResultsStorage, }, + activeFilterType: { + type: 'string', + }, + repeatQueryOnReload: { type: 'boolean', default: false, @@ -176,6 +195,24 @@ export function getPageReferences() { info.tagResultKindString = findString('tag-result-kind'); + + info.albumResultFilterString = + findString('album-result-filter'); + + info.artistResultFilterString = + findString('artist-result-filter'); + + info.flashResultFilterString = + findString('flash-result-filter'); + + info.groupResultFilterString = + findString('group-result-filter'); + + info.tagResultFilterString = + findString('tag-result-filter'); + + info.trackResultFilterString = + findString('track-result-filter'); } export function addInternalListeners() { @@ -265,6 +302,38 @@ export function mutatePageContent() { info.searchBox.appendChild(info.failedRule); info.searchBox.appendChild(info.failedContainer); + // Filter section + + info.filterContainer = + document.createElement('div'); + + info.filterContainer.classList.add('wiki-search-filter-container'); + + cssProp(info.filterContainer, 'display', 'none'); + + forEachFilter((type, _filterLink) => { + // TODO: It's probably a sin to access `session` during this step LOL + const {session} = info; + + const filterLink = document.createElement('a'); + + filterLink.href = '#'; + filterLink.classList.add('wiki-search-filter-link'); + + if (session.activeFilterType === type) { + filterLink.classList.add('active'); + } + + const string = info[type + 'ResultFilterString']; + filterLink.appendChild(templateContent(string)); + + info[type + 'FilterLink'] = filterLink; + + info.filterContainer.appendChild(filterLink); + }); + + info.searchBox.appendChild(info.filterContainer); + // Results section info.resultsRule = @@ -371,7 +440,7 @@ export function addPageListeners() { const {settings, state} = info; if (!info.searchInput.value) { - clearSidebarSearch(); + clearSidebarSearch(); // ...but don't clear filter return; } @@ -433,10 +502,18 @@ export function addPageListeners() { info.endSearchLink.addEventListener('click', domEvent => { domEvent.preventDefault(); clearSidebarSearch(); + clearSidebarFilter(); possiblyHideSearchSidebarColumn(); restoreSidebarSearchColumn(); }); + forEachFilter((type, filterLink) => { + filterLink.addEventListener('click', domEvent => { + domEvent.preventDefault(); + toggleSidebarSearchFilter(type); + }); + }); + info.resultsContainer.addEventListener('scroll', () => { const {settings, state} = info; @@ -518,6 +595,21 @@ function trackSidebarSearchDownloadEnds(event) { } } +function forEachFilter(callback) { + const filterOrder = [ + 'track', + 'album', + 'artist', + 'group', + 'flash', + 'tag', + ]; + + for (const type of filterOrder) { + callback(type, info[type + 'FilterLink']); + } +} + async function activateSidebarSearch(query) { const {session, state} = info; @@ -584,6 +676,16 @@ function clearSidebarSearch() { hideSidebarSearchResults(); } +function clearSidebarFilter() { + const {session} = info; + + toggleSidebarSearchFilter(session.activeFilterType); + + forEachFilter((_type, filterLink) => { + filterLink.classList.remove('shown', 'hidden'); + }); +} + function updateSidebarSearchStatus() { const {state} = info; @@ -670,54 +772,122 @@ function showSidebarSearchFailed() { } function showSidebarSearchResults(results) { - console.debug(`Showing search results:`, results); + const {session} = info; - showSearchSidebarColumn(); + console.debug(`Showing search results:`, tidyResults(results)); - const flatResults = - Object.entries(results) - .filter(([index]) => index === 'generic') - .flatMap(([index, results]) => results - .flatMap(({doc, id}) => ({ - index, - reference: id ?? null, - referenceType: (id ? id.split(':')[0] : null), - directory: (id ? id.split(':')[1] : null), - data: doc, - }))); + showSearchSidebarColumn(); info.searchBox.classList.add('showing-results'); info.searchSidebarColumn.classList.add('search-showing-results'); - while (info.results.firstChild) { - info.results.firstChild.remove(); + let filterType = session.activeFilterType; + let shownAnyResults = + fillResultElements(results, {filterType: session.activeFilterType}); + + showFilterElements(results); + + if (!shownAnyResults) { + shownAnyResults = toggleSidebarSearchFilter(filterType); + filterType = null; } - cssProp(info.resultsRule, 'display', 'block'); - cssProp(info.resultsContainer, 'display', 'block'); + if (shownAnyResults) { + cssProp(info.endSearchRule, 'display', 'block'); + cssProp(info.endSearchLine, 'display', 'block'); - if (empty(flatResults)) { + tidySidebarSearchColumn(); + } else { const p = document.createElement('p'); p.classList.add('wiki-search-no-results'); p.appendChild(templateContent(info.noResultsString)); info.results.appendChild(p); } - for (const result of flatResults) { + restoreSidebarSearchResultsScrollOffset(); +} + +function tidyResults(results) { + const tidiedResults = + results.results.map(({doc, id}) => ({ + reference: id ?? null, + referenceType: (id ? id.split(':')[0] : null), + directory: (id ? id.split(':')[1] : null), + data: doc, + })); + + return tidiedResults; +} + +function fillResultElements(results, { + filterType = null, +} = {}) { + const tidiedResults = tidyResults(results); + + const filteredResults = + (filterType + ? tidiedResults.filter(result => result.referenceType === filterType) + : tidiedResults); + + while (info.results.firstChild) { + info.results.firstChild.remove(); + } + + cssProp(info.resultsRule, 'display', 'block'); + cssProp(info.resultsContainer, 'display', 'block'); + + if (empty(filteredResults)) { + return false; + } + + for (const result of filteredResults) { const el = generateSidebarSearchResult(result); if (!el) continue; info.results.appendChild(el); } - if (!empty(flatResults)) { - cssProp(info.endSearchRule, 'display', 'block'); - cssProp(info.endSearchLine, 'display', 'block'); + return true; +} - tidySidebarSearchColumn(); - } +function showFilterElements(results) { + const {queriedKind} = results; - restoreSidebarSearchResultsScrollOffset(); + const tidiedResults = tidyResults(results); + + const allReferenceTypes = + unique(tidiedResults.map(result => result.referenceType)); + + let shownAny = false; + + forEachFilter((type, filterLink) => { + filterLink.classList.remove('shown', 'hidden'); + + if (allReferenceTypes.includes(type)) { + shownAny = true; + cssProp(filterLink, 'display', null); + + if (queriedKind) { + filterLink.setAttribute('inert', 'inert'); + } else { + filterLink.removeAttribute('inert'); + } + + if (type === queriedKind) { + filterLink.classList.add('active-from-query'); + } else { + filterLink.classList.remove('active-from-query'); + } + } else { + cssProp(filterLink, 'display', 'none'); + } + }); + + if (shownAny) { + cssProp(info.filterContainer, 'display', null); + } else { + cssProp(info.filterContainer, 'display', 'none'); + } } function generateSidebarSearchResult(result) { @@ -908,6 +1078,8 @@ function generateSidebarSearchResultTemplate(slots) { } function hideSidebarSearchResults() { + cssProp(info.filterContainer, 'display', 'none'); + cssProp(info.resultsRule, 'display', 'none'); cssProp(info.resultsContainer, 'display', 'none'); @@ -1040,6 +1212,36 @@ function tidySidebarSearchColumn() { } } +function toggleSidebarSearchFilter(toggleType) { + const {session} = info; + + if (!toggleType) return null; + + let shownAnyResults = null; + + forEachFilter((type, filterLink) => { + if (type === toggleType) { + const filterActive = filterLink.classList.toggle('active'); + const filterType = (filterActive ? type : null); + + if (cssProp(filterLink, 'display') !== 'none') { + filterLink.classList.add(filterActive ? 'shown' : 'hidden'); + } + + if (session.activeQueryResults) { + shownAnyResults = + fillResultElements(session.activeQueryResults, {filterType}); + } + + session.activeFilterType = filterType; + } else { + filterLink.classList.remove('active'); + } + }); + + return shownAnyResults; +} + function restoreSidebarSearchColumn() { const {state} = info; @@ -1073,6 +1275,8 @@ function forgetRecentSidebarSearch() { session.activeQuery = null; session.activeQueryResults = null; + + clearSidebarFilter(); } async function handleDroppedIntoSearchInput(domEvent) { @@ -1101,7 +1305,7 @@ async function handleDroppedIntoSearchInput(domEvent) { let droppedURL; try { droppedURL = new URL(droppedText); - } catch (error) { + } catch { droppedURL = null; } diff --git a/src/static/js/client/sticky-heading.js b/src/static/js/client/sticky-heading.js index fba05b84..b65574d0 100644 --- a/src/static/js/client/sticky-heading.js +++ b/src/static/js/client/sticky-heading.js @@ -23,6 +23,7 @@ export const info = { contentContainers: null, contentHeadings: null, + contentCoverColumns: null, contentCovers: null, contentCoversReveal: null, @@ -82,9 +83,13 @@ export function getPageReferences() { info.stickyContainers .map(el => el.closest('.content-sticky-heading-root').parentElement); - info.contentCovers = + info.contentCoverColumns = info.contentContainers - .map(el => el.querySelector('#cover-art-container')); + .map(el => el.querySelector('#artwork-column')); + + info.contentCovers = + info.contentCoverColumns + .map(el => el ? el.querySelector('.cover-artwork') : null); info.contentCoversReveal = info.contentCovers @@ -212,10 +217,10 @@ function updateCollapseStatus(index) { function updateStickyCoverVisibility(index) { const stickyCoverContainer = info.stickyCoverContainers[index]; const stickyContainer = info.stickyContainers[index]; - const contentCover = info.contentCovers[index]; + const contentCoverColumn = info.contentCoverColumns[index]; - if (contentCover && stickyCoverContainer) { - if (contentCover.getBoundingClientRect().bottom < 4) { + if (contentCoverColumn && stickyCoverContainer) { + if (contentCoverColumn.getBoundingClientRect().bottom < 4) { stickyCoverContainer.classList.add('visible'); stickyContainer.classList.add('cover-visible'); } else { |