diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/common-util/search-spec.js | 120 | ||||
-rw-r--r-- | src/content/dependencies/generateCoverGrid.js | 37 | ||||
-rw-r--r-- | src/content/dependencies/generateExpandableGallerySection.js | 92 | ||||
-rw-r--r-- | src/content/dependencies/generateGridExpando.js | 39 | ||||
-rw-r--r-- | src/content/dependencies/generateGroupGalleryPageSeriesSection.js | 147 | ||||
-rw-r--r-- | src/content/dependencies/generateLyricsEntry.js | 31 | ||||
-rw-r--r-- | src/content/dependencies/generateSearchSidebarBox.js | 17 | ||||
-rw-r--r-- | src/content/dependencies/index.js | 7 | ||||
-rw-r--r-- | src/static/css/site.css | 102 | ||||
-rw-r--r-- | src/static/js/client-util.js | 22 | ||||
-rw-r--r-- | src/static/js/client/expandable-gallery-section.js | 77 | ||||
-rw-r--r-- | src/static/js/client/expandable-grid-section.js | 85 | ||||
-rw-r--r-- | src/static/js/client/index.js | 4 | ||||
-rw-r--r-- | src/static/js/client/quick-description.js | 2 | ||||
-rw-r--r-- | src/static/js/client/sidebar-search.js | 56 | ||||
-rw-r--r-- | src/static/js/search-worker.js | 3 | ||||
-rw-r--r-- | src/strings-default.yaml | 17 |
17 files changed, 494 insertions, 364 deletions
diff --git a/src/common-util/search-spec.js b/src/common-util/search-spec.js index af5ec201..731e5495 100644 --- a/src/common-util/search-spec.js +++ b/src/common-util/search-spec.js @@ -1,57 +1,19 @@ // Index structures shared by client and server, and relevant interfaces. -function getArtworkPath(thing) { - switch (thing.constructor[Symbol.for('Thing.referenceType')]) { - case 'album': { - return [ - 'media.albumCover', - thing.directory, - thing.coverArtFileExtension, - ]; - } - - case 'flash': { - return [ - 'media.flashArt', - thing.directory, - thing.coverArtFileExtension, - ]; - } - - case 'track': { - if (thing.hasUniqueCoverArt) { - return [ - 'media.trackCover', - thing.album.directory, - thing.directory, - thing.coverArtFileExtension, - ]; - } else if (thing.album.hasCoverArt) { - return [ - 'media.albumCover', - thing.album.directory, - thing.album.coverArtFileExtension, - ]; - } else { - return null; - } - } - - default: - return null; - } -} - -function prepareArtwork(thing, { +function prepareArtwork(artwork, thing, { checkIfImagePathHasCachedThumbnails, getThumbnailEqualOrSmaller, urls, }) { + if (!artwork) { + return undefined; + } + const hasWarnings = - thing.artTags?.some(artTag => artTag.isContentWarning); + artwork.artTags?.some(artTag => artTag.isContentWarning); const artworkPath = - getArtworkPath(thing); + artwork.path; if (!artworkPath) { return undefined; @@ -92,23 +54,48 @@ function baselineProcess(thing, opts) { thing.name; fields.artwork = - prepareArtwork(thing, opts); + null; fields.color = thing.color; + fields.disambiguator = + null; + return fields; } const baselineStore = [ 'primaryName', + 'disambiguator', 'artwork', 'color', ]; function genericQuery(wikiData) { + const groupOrder = + wikiData.wikiInfo.divideTrackListsByGroups; + + const getGroupRank = thing => { + const relevantRanks = + Array.from(groupOrder.entries()) + .filter(({1: group}) => thing.groups.includes(group)) + .map(({0: index}) => index); + + if (relevantRanks.length === 0) { + return Infinity; + } else if (relevantRanks.length === 1) { + return relevantRanks[0]; + } else { + return relevantRanks[0] + 0.5; + } + } + + const sortByGroupRank = things => + things.sort((a, b) => getGroupRank(a) - getGroupRank(b)); + return [ - wikiData.albumData, + sortByGroupRank(wikiData.albumData.slice()), wikiData.artTagData, @@ -119,10 +106,9 @@ function genericQuery(wikiData) { wikiData.groupData, - wikiData.trackData - // Exclude rereleases - there's no reasonable way to differentiate - // them from the main release as part of this query. - .filter(track => !track.mainReleaseTrack), + sortByGroupRank( + wikiData.trackData + .filter(track => !track.mainReleaseTrack)), ].flat(); } @@ -132,6 +118,20 @@ function genericProcess(thing, opts) { const kind = thing.constructor[Symbol.for('Thing.referenceType')]; + const boundPrepareArtwork = artwork => + prepareArtwork(artwork, thing, opts); + + fields.artwork = + (kind === 'track' && thing.hasUniqueCoverArt + ? boundPrepareArtwork(thing.trackArtworks[0]) + : kind === 'track' + ? boundPrepareArtwork(thing.album.coverArtworks[0]) + : kind === 'album' + ? boundPrepareArtwork(thing.coverArtworks[0]) + : kind === 'flash' + ? boundPrepareArtwork(thing.coverArtwork) + : null); + fields.parentName = (kind === 'track' ? thing.album.name @@ -141,10 +141,18 @@ function genericProcess(thing, opts) { ? thing.act.name : null); + fields.disambiguator = + fields.parentName; + fields.artTags = - (thing.constructor.hasPropertyDescriptor('artTags') - ? thing.artTags.map(artTag => artTag.nameShort) - : []); + (Array.from(new Set( + (kind === 'track' + ? thing.trackArtworks.flatMap(artwork => artwork.artTags) + : kind === 'album' + ? thing.coverArtworks.flatMap(artwork => artwork.artTags) + : [])))) + + .map(artTag => artTag.nameShort); fields.additionalNames = (thing.constructor.hasPropertyDescriptor('additionalNames') @@ -230,6 +238,10 @@ export function makeSearchIndex(descriptor, {FlexSearch}) { id: 'reference', index: descriptor.index, store: descriptor.store, + + // Disable scoring, always return results according to provided order + // (specified above in `genericQuery`, etc). + resolution: 1, }); } diff --git a/src/content/dependencies/generateCoverGrid.js b/src/content/dependencies/generateCoverGrid.js index 89371015..53b2b8b8 100644 --- a/src/content/dependencies/generateCoverGrid.js +++ b/src/content/dependencies/generateCoverGrid.js @@ -1,14 +1,16 @@ import {empty, stitchArrays, unique} from '#sugar'; export default { - contentDependencies: ['generateGridActionLinks'], + contentDependencies: ['generateGridActionLinks', 'generateGridExpando'], extraDependencies: ['html', 'language'], - relations(relation) { - return { - actionLinks: relation('generateGridActionLinks'), - }; - }, + relations: (relation) => ({ + actionLinks: + relation('generateGridActionLinks'), + + expando: + relation('generateGridExpando'), + }), slots: { attributes: {type: 'attributes', mutable: false}, @@ -45,6 +47,13 @@ export default { revealAllWarnings: { validate: v => v.looseArrayOf(v.isString), }, + + bottomCaption: { + type: 'html', + mutable: false, + }, + + cutIndex: {validate: v => v.isWholeNumber}, }, generate: (relations, slots, {html, language}) => @@ -121,6 +130,10 @@ export default { (classes ? {class: classes} : null), + + slots.cutIndex >= 1 && + index >= slots.cutIndex && + {class: 'hidden-by-expandable-cut'}, ], colorContext: 'image-box', @@ -168,5 +181,17 @@ export default { relations.actionLinks .slot('actionLinks', slots.actionLinks), + + (slots.cutIndex >= 1 && + slots.cutIndex < slots.links.length + ? relations.expando.slots({ + caption: slots.bottomCaption, + }) + + : !html.isBlank(relations.bottomCaption) + ? html.tag('p', {class: 'grid-caption'}, + slots.caption) + + : html.blank()), ]), }; diff --git a/src/content/dependencies/generateExpandableGallerySection.js b/src/content/dependencies/generateExpandableGallerySection.js deleted file mode 100644 index 122ca4b1..00000000 --- a/src/content/dependencies/generateExpandableGallerySection.js +++ /dev/null @@ -1,92 +0,0 @@ -export default { - contentDependencies: ['generateContentHeading'], - extraDependencies: ['html', 'language'], - - relations: (relation) => ({ - contentHeading: - relation('generateContentHeading'), - }), - - slots: { - title: { - type: 'html', - mutable: false, - }, - - contentAboveCut: { - type: 'html', - mutable: false, - }, - - contentBelowCut: { - type: 'html', - mutable: false, - }, - - caption: { - type: 'html', - mutable: false, - }, - - expandCue: { - type: 'html', - mutable: false, - }, - - collapseCue: { - type: 'html', - mutable: false, - }, - }, - - generate: (relations, slots, {html, language}) => - html.tag('section', {class: 'expandable-gallery-section'}, [ - relations.contentHeading.slots({ - tag: 'h2', - title: slots.title, - }), - - html.tag('div', {class: 'section-content-above-cut'}, - {[html.onlyIfContent]: true}, - - slots.contentAboveCut), - - html.tag('div', {class: 'section-content-below-cut'}, - {[html.onlyIfContent]: true}, - - !html.isBlank(slots.contentBelowCut) && - {style: 'display: none'}, - - slots.contentBelowCut), - - html.tag('div', {class: 'section-expando'}, - {[html.onlyIfSiblings]: true}, - - html.tag('div', {class: 'section-expando-content'}, - {[html.joinChildren]: html.tag('br')}, - - [ - html.tag('span', {class: 'section-caption'}, - slots.caption), - - !html.isBlank(slots.contentBelowCut) && - language.$('misc.coverGrid.expandCollapseCue', { - cue: - html.tag('a', {class: 'section-expando-toggle'}, - {href: '#'}, - - {[html.joinChildren]: ''}, - {[html.noEdgeWhitespace]: true}, - - [ - html.tag('span', {class: 'section-expand-cue'}, - slots.expandCue), - - html.tag('span', {class: 'section-collapse-cue'}, - {style: 'display: none'}, - slots.collapseCue), - ]), - }), - ])), - ]), -}; diff --git a/src/content/dependencies/generateGridExpando.js b/src/content/dependencies/generateGridExpando.js new file mode 100644 index 00000000..71c2f970 --- /dev/null +++ b/src/content/dependencies/generateGridExpando.js @@ -0,0 +1,39 @@ +export default { + extraDependencies: ['html', 'language'], + + slots: { + caption: {type: 'html', mutable: false}, + }, + + generate: (slots, {html, language}) => + language.encapsulate('misc.coverGrid', capsule => + html.tag('div', {class: 'grid-expando'}, + {[html.onlyIfSiblings]: true}, + + html.tag('p', {class: 'grid-expando-content'}, + {[html.joinChildren]: html.tag('br')}, + + [ + html.tag('span', {class: 'grid-caption'}, + slots.caption), + + !html.isBlank(slots.contentBelowCut) && + language.$(capsule, 'expandCollapseCue', { + cue: + html.tag('a', {class: 'grid-expando-toggle'}, + {href: '#'}, + + {[html.joinChildren]: ''}, + {[html.noEdgeWhitespace]: true}, + + [ + html.tag('span', {class: 'grid-expand-cue'}, + language.$(capsule, 'expand')), + + html.tag('span', {class: 'grid-collapse-cue'}, + {style: 'display: none'}, + language.$(capsule, 'collapse')), + ]), + }), + ]))), +}; diff --git a/src/content/dependencies/generateGroupGalleryPageSeriesSection.js b/src/content/dependencies/generateGroupGalleryPageSeriesSection.js index 2ccead5d..b88adfa3 100644 --- a/src/content/dependencies/generateGroupGalleryPageSeriesSection.js +++ b/src/content/dependencies/generateGroupGalleryPageSeriesSection.js @@ -2,7 +2,7 @@ import {sortChronologically} from '#sort'; export default { contentDependencies: [ - 'generateExpandableGallerySection', + 'generateContentHeading', 'generateGroupGalleryPageAlbumGrid', ], @@ -11,12 +11,8 @@ export default { query(series) { const query = {}; - // Includes undated albums. - const albumsLatestFirst = - sortChronologically(series.albums, {latestFirst: true}); - - query.albumsAboveCut = albumsLatestFirst.slice(0, 4); - query.albumsBelowCut = albumsLatestFirst.slice(4); + query.albums = + sortChronologically(series.albums.slice(), {latestFirst: true}); query.allAlbumsDated = series.albums.every(album => album.date); @@ -25,13 +21,13 @@ export default { series.albums.some(album => !album.groups.includes(series.group)); query.latestAlbum = - albumsLatestFirst + query.albums .filter(album => album.date) .at(0) ?? null; query.earliestAlbum = - albumsLatestFirst + query.albums .filter(album => album.date) .at(-1) ?? null; @@ -40,17 +36,12 @@ export default { }, relations: (relation, query, series) => ({ - gallerySection: - relation('generateExpandableGallerySection'), + contentHeading: + relation('generateContentHeading'), - gridAboveCut: + grid: relation('generateGroupGalleryPageAlbumGrid', - query.albumsAboveCut, - series.group), - - gridBelowCut: - relation('generateGroupGalleryPageAlbumGrid', - query.albumsBelowCut, + query.albums, series.group), }), @@ -88,69 +79,67 @@ export default { generate: (data, relations, {html, language}) => language.encapsulate('groupGalleryPage.albumSection', capsule => - relations.gallerySection.slots({ - title: data.name, - - contentAboveCut: relations.gridAboveCut, - contentBelowCut: relations.gridBelowCut, - - caption: - language.encapsulate(capsule, 'caption', captionCapsule => - html.tags([ - data.anyAlbumNotFromThisGroup && - language.$(captionCapsule, 'seriesAlbumsNotFromGroup', { - marker: - language.$('misc.coverGrid.details.notFromThisGroup.marker'), - - series: - html.tag('i', data.name), - - group: data.groupName, - }), - - language.encapsulate(captionCapsule, workingCapsule => { - const workingOptions = {}; - - workingOptions.tracks = - html.tag('b', - language.countTracks(data.tracks, {unit: true})); - - workingOptions.albums = - html.tag('b', - language.countAlbums(data.albums, {unit: true})); - - if (data.allAlbumsDated) { - const earliestDate = data.earliestAlbumDate; - const latestDate = data.latestAlbumDate; - - const earliestYear = earliestDate.getFullYear(); - const latestYear = latestDate.getFullYear(); - - if (earliestYear === latestYear) { - if (data.albums === 1) { - workingCapsule += '.withDate'; - workingOptions.date = - language.formatDate(earliestDate); + html.tags([ + relations.contentHeading.slots({ + tag: 'h2', + title: language.sanitize(data.name), + }), + + relations.grid.slots({ + cutIndex: 4, + + bottomCaption: + language.encapsulate(capsule, 'caption', captionCapsule => + html.tags([ + data.anyAlbumNotFromThisGroup && + language.$(captionCapsule, 'seriesAlbumsNotFromGroup', { + marker: + language.$('misc.coverGrid.details.notFromThisGroup.marker'), + + series: + html.tag('i', data.name), + + group: data.groupName, + }), + + language.encapsulate(captionCapsule, workingCapsule => { + const workingOptions = {}; + + workingOptions.tracks = + html.tag('b', + language.countTracks(data.tracks, {unit: true})); + + workingOptions.albums = + html.tag('b', + language.countAlbums(data.albums, {unit: true})); + + if (data.allAlbumsDated) { + const earliestDate = data.earliestAlbumDate; + const latestDate = data.latestAlbumDate; + + const earliestYear = earliestDate.getFullYear(); + const latestYear = latestDate.getFullYear(); + + if (earliestYear === latestYear) { + if (data.albums === 1) { + workingCapsule += '.withDate'; + workingOptions.date = + language.formatDate(earliestDate); + } else { + workingCapsule += '.withYear'; + workingOptions.year = + language.formatYear(earliestDate); + } } else { - workingCapsule += '.withYear'; - workingOptions.year = - language.formatYear(earliestDate); + workingCapsule += '.withYearRange'; + workingOptions.yearRange = + language.formatYearRange(earliestDate, latestDate); } - } else { - workingCapsule += '.withYearRange'; - workingOptions.yearRange = - language.formatYearRange(earliestDate, latestDate); } - } - - return language.$(workingCapsule, workingOptions); - }), - ], {[html.joinChildren]: html.tag('br')})), - expandCue: - language.$(capsule, 'expand'), - - collapseCue: - language.$(capsule, 'collapse'), - })), + return language.$(workingCapsule, workingOptions); + }), + ], {[html.joinChildren]: html.tag('br')})), + }), + ])), }; diff --git a/src/content/dependencies/generateLyricsEntry.js b/src/content/dependencies/generateLyricsEntry.js index 0c91ce0c..1379ae06 100644 --- a/src/content/dependencies/generateLyricsEntry.js +++ b/src/content/dependencies/generateLyricsEntry.js @@ -28,6 +28,30 @@ export default { hasSquareBracketAnnotations: entry.hasSquareBracketAnnotations, + + numStanzas: + 1 + + + (Array.from( + entry.body + .matchAll(/\n\n|<br><br>/g)) + + .length) + + + (entry.body.includes('<br') + ? entry.body.split('\n').length + : 0), + + numLines: + 1 + + + (Array.from( + entry.body + .replaceAll(/(<br>){1,}/g, '\n') + .replaceAll(/\n{2,}/g, '\n') + .matchAll(/\n/g)) + + .length), }), slots: { @@ -42,6 +66,13 @@ export default { html.tag('div', {class: 'lyrics-entry'}, slots.attributes, + {'data-stanzas': data.numStanzas}, + {'data-lines': data.numLines}, + + (data.numStanzas > 1 || + data.numLines > 8) && + {class: 'long-lyrics'}, + [ html.tag('p', {class: 'lyrics-details'}, {[html.onlyIfContent]: true}, diff --git a/src/content/dependencies/generateSearchSidebarBox.js b/src/content/dependencies/generateSearchSidebarBox.js index 308a1105..87785906 100644 --- a/src/content/dependencies/generateSearchSidebarBox.js +++ b/src/content/dependencies/generateSearchSidebarBox.js @@ -58,6 +58,23 @@ export default { language.$(capsule, 'artTag')), ]), + language.encapsulate(capsule, 'resultDisambiguator', capsule => [ + html.tag('template', {class: 'wiki-search-group-result-disambiguator-string'}, + language.$(capsule, 'group', { + disambiguator: html.tag('slot', {name: 'disambiguator'}), + })), + + html.tag('template', {class: 'wiki-search-flash-result-disambiguator-string'}, + language.$(capsule, 'flash', { + disambiguator: html.tag('slot', {name: 'disambiguator'}), + })), + + html.tag('template', {class: 'wiki-search-track-result-disambiguator-string'}, + language.$(capsule, 'track', { + disambiguator: html.tag('slot', {name: 'disambiguator'}), + })), + ]), + language.encapsulate(capsule, 'resultFilter', capsule => [ html.tag('template', {class: 'wiki-search-album-result-filter-string'}, language.$(capsule, 'album')), diff --git a/src/content/dependencies/index.js b/src/content/dependencies/index.js index 25d7324f..cfa6346c 100644 --- a/src/content/dependencies/index.js +++ b/src/content/dependencies/index.js @@ -94,6 +94,8 @@ export function watchContentDependencies({ const filePaths = files.map(file => path.join(watchPath, file)); for (const filePath of filePaths) { if (filePath === metaPath) continue; + if (filePath.endsWith('.DS_Store')) continue; + const functionName = getFunctionName(filePath); if (!isMocked(functionName)) { contentDependencies[functionName] = null; @@ -105,8 +107,9 @@ export function watchContentDependencies({ watcher.on('all', (event, filePath) => { if (!['add', 'change'].includes(event)) return; if (filePath === metaPath) return; - handlePathUpdated(filePath); + if (filePath.endsWith('.DS_Store')) return; + handlePathUpdated(filePath); }); watcher.on('unlink', (filePath) => { @@ -115,6 +118,8 @@ export function watchContentDependencies({ return; } + if (filePath.endsWith('.DS_Store')) return; + handlePathRemoved(filePath); }); diff --git a/src/static/css/site.css b/src/static/css/site.css index 8872bde8..e584f918 100644 --- a/src/static/css/site.css +++ b/src/static/css/site.css @@ -918,6 +918,11 @@ summary.underline-white > span:hover a:not(:hover) { display: inline-block; } +.wiki-search-result-disambiguator { + opacity: 0.9; + display: inline-block; +} + .wiki-search-result-image-container { align-self: flex-start; flex-shrink: 0; @@ -1232,7 +1237,12 @@ label > input[type=checkbox]:not(:checked) + span { white-space: nowrap; } -.isolate-tooltip-z-indexing > * { +:where(.isolate-tooltip-z-indexing) { + position: relative; + z-index: 1; +} + +:where(.isolate-tooltip-z-indexing > *) { position: relative; z-index: -1; } @@ -1640,9 +1650,11 @@ hr.cute, } #artwork-column .cover-artwork { + --normal-shadow: 0 0 12px 12px #00000080; + box-shadow: 0 2px 14px -6px var(--primary-color), - 0 0 12px 12px #00000080; + var(--normal-shadow); } #artwork-column .cover-artwork:not(:first-child), @@ -1651,6 +1663,10 @@ hr.cute, margin-right: 5px; } +#artwork-column .cover-artwork:not(:first-child) { + --normal-shadow: 0 0 9px 9px #00000068; +} + #artwork-column .cover-artwork:first-child + .cover-artwork-joiner, #artwork-column .cover-artwork.attached-artwork-is-main-artwork, #artwork-column .cover-artwork.attached-artwork-is-main-artwork + .cover-artwork-joiner { @@ -1870,11 +1886,11 @@ p.image-details.origin-details .origin-details { margin-top: 0.25em; } -.lyrics-entry { +.lyrics-entry.long-lyrics { clip-path: inset(-15px -20px); } -.lyrics-entry::after { +.lyrics-entry.long-lyrics::after { content: ""; pointer-events: none; display: block; @@ -2076,39 +2092,6 @@ ul.quick-info li:not(:last-child)::after { margin-bottom: 1.5em; } -.expandable-gallery-section .section-expando { - margin-top: 1em; - margin-bottom: 2em; - - display: flex; - flex-direction: row; - justify-content: space-around; -} - -.expandable-gallery-section .section-expando-content { - text-align: center; - line-height: 1.5; -} - -.expandable-gallery-section .section-expando-toggle { - text-decoration: underline; - text-decoration-style: dotted; -} - -.expandable-gallery-section.expanded .section-content-below-cut { - animation: expand-gallery-section 0.8s forwards; -} - -@keyframes expand-gallery-section { - from { - opacity: 0; - } - - to { - opacity: 1; - } -} - .quick-description:not(.has-external-links-only) { --clamped-padding-ratio: max(var(--responsive-padding-ratio), 0.06); margin-left: auto; @@ -3251,6 +3234,47 @@ video.pixelate, .pixelate video { --dim-color: inherit !important; } +.grid-caption { + flex-basis: 100%; + text-align: center; + line-height: 1.5; +} + +.grid-expando { + margin-top: 1em; + margin-bottom: 2em; + flex-basis: 100%; + + display: flex; + flex-direction: row; + justify-content: space-around; +} + +.grid-expando-content { + margin: 0; + text-align: center; + line-height: 1.5; +} + +.grid-expando-toggle { + text-decoration: underline; + text-decoration-style: dotted; +} + +.grid-item.shown-by-expandable-cut { + animation: expand-cover-grid 0.8s forwards; +} + +@keyframes expand-cover-grid { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + /* Carousel */ .carousel-container { @@ -4193,6 +4217,10 @@ main.long-content .content-sticky-heading-container .content-sticky-subheading-r max-width: unset; } + #artwork-column .cover-artwork { + --normal-shadow: 0 0 transparent; + } + #artwork-column .cover-artwork:not(:first-child), #artwork-column .cover-artwork-joiner { margin-left: 30px; diff --git a/src/static/js/client-util.js b/src/static/js/client-util.js index 5a35bcf2..396c4889 100644 --- a/src/static/js/client-util.js +++ b/src/static/js/client-util.js @@ -37,7 +37,7 @@ export function cssProp(el, ...args) { } } -export function templateContent(el) { +export function templateContent(el, slots = {}) { if (el === null) { return null; } @@ -46,7 +46,25 @@ export function templateContent(el) { throw new Error(`Expected a <template> element`); } - return el.content.cloneNode(true); + const content = el.content.cloneNode(true); + + for (const [key, value] of Object.entries(slots)) { + const slot = content.querySelector(`slot[name="${key}"]`); + + if (!slot) { + console.warn(`Slot ${key} missing in template:`, el); + continue; + } + + if (value === null || value === undefined) { + console.warn(`Valueless slot ${key} in template:`, el); + continue; + } + + slot.replaceWith(value); + } + + return content; } // Curry-style, so multiple points can more conveniently be tested at once. diff --git a/src/static/js/client/expandable-gallery-section.js b/src/static/js/client/expandable-gallery-section.js deleted file mode 100644 index dc83e8b7..00000000 --- a/src/static/js/client/expandable-gallery-section.js +++ /dev/null @@ -1,77 +0,0 @@ -/* 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/expandable-grid-section.js b/src/static/js/client/expandable-grid-section.js new file mode 100644 index 00000000..ce9a4c06 --- /dev/null +++ b/src/static/js/client/expandable-grid-section.js @@ -0,0 +1,85 @@ +/* eslint-env browser */ + +import {cssProp} from '../client-util.js'; + +import {stitchArrays} from '../../shared-util/sugar.js'; + +export const info = { + id: 'expandableGallerySectionInfo', + + items: null, + toggles: null, + expandCues: null, + collapseCues: null, +}; + +export function getPageReferences() { + const expandos = + Array.from(document.querySelectorAll('.grid-expando')); + + const grids = + expandos + .map(expando => expando.closest('.grid-listing')); + + info.items = + grids + .map(grid => grid.querySelectorAll('.grid-item')) + .map(items => Array.from(items)); + + info.toggles = + expandos + .map(expando => expando.querySelector('.grid-expando-toggle')); + + info.expandCues = + info.toggles + .map(toggle => toggle.querySelector('.grid-expand-cue')); + + info.collapseCues = + info.toggles + .map(toggle => toggle.querySelector('.grid-collapse-cue')); +} + +export function addPageListeners() { + stitchArrays({ + items: info.items, + toggle: info.toggles, + expandCue: info.expandCues, + collapseCue: info.collapseCues, + }).forEach(({ + items, + toggle, + expandCue, + collapseCue, + }) => { + toggle.addEventListener('click', domEvent => { + domEvent.preventDefault(); + + const collapsed = + items.some(item => + item.classList.contains('hidden-by-expandable-cut')); + + for (const item of items) { + if ( + !item.classList.contains('hidden-by-expandable-cut') && + !item.classList.contains('shown-by-expandable-cut') + ) continue; + + if (collapsed) { + item.classList.remove('hidden-by-expandable-cut'); + item.classList.add('shown-by-expandable-cut'); + } else { + item.classList.add('hidden-by-expandable-cut'); + item.classList.remove('shown-by-expandable-cut'); + } + } + + if (collapsed) { + cssProp(expandCue, 'display', 'none'); + cssProp(collapseCue, 'display', null); + } else { + cssProp(expandCue, 'display', null); + cssProp(collapseCue, 'display', 'none'); + } + }); + }); +} diff --git a/src/static/js/client/index.js b/src/static/js/client/index.js index 016ce9ad..86081b5d 100644 --- a/src/static/js/client/index.js +++ b/src/static/js/client/index.js @@ -11,7 +11,7 @@ import * as artistRollingWindowModule from './artist-rolling-window.js'; 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 expandableGridSectionModule from './expandable-grid-section.js'; import * as galleryStyleSelectorModule from './gallery-style-selector.js'; import * as hashLinkModule from './hash-link.js'; import * as hoverableTooltipModule from './hoverable-tooltip.js'; @@ -37,7 +37,7 @@ export const modules = [ cssCompatibilityAssistantModule, datetimestampTooltipModule, draggedLinkModule, - expandableGallerySectionModule, + expandableGridSectionModule, galleryStyleSelectorModule, hashLinkModule, hoverableTooltipModule, diff --git a/src/static/js/client/quick-description.js b/src/static/js/client/quick-description.js index 6a7a6023..cff82252 100644 --- a/src/static/js/client/quick-description.js +++ b/src/static/js/client/quick-description.js @@ -1,7 +1,5 @@ /* 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 eae1e74e..4467766c 100644 --- a/src/static/js/client/sidebar-search.js +++ b/src/static/js/client/sidebar-search.js @@ -73,6 +73,10 @@ export const info = { groupResultKindString: null, tagResultKindString: null, + groupResultDisambiguatorString: null, + flashResultDisambiguatorString: null, + trackResultDisambiguatorString: null, + albumResultFilterString: null, artistResultFilterString: null, flashResultFilterString: null, @@ -196,6 +200,15 @@ export function getPageReferences() { info.tagResultKindString = findString('tag-result-kind'); + info.groupResultDisambiguatorString = + findString('group-result-disambiguator'); + + info.flashResultDisambiguatorString = + findString('flash-result-disambiguator'); + + info.trackResultDisambiguatorString = + findString('track-result-disambiguator'); + info.albumResultFilterString = findString('album-result-filter'); @@ -841,7 +854,7 @@ function fillResultElements(results, { } for (const result of filteredResults) { - const el = generateSidebarSearchResult(result); + const el = generateSidebarSearchResult(result, filteredResults); if (!el) continue; info.results.appendChild(el); @@ -890,13 +903,13 @@ function showFilterElements(results) { } } -function generateSidebarSearchResult(result) { +function generateSidebarSearchResult(result, results) { const preparedSlots = { color: result.data.color ?? null, name: - result.data.name ?? result.data.primaryName ?? null, + getSearchResultName(result), imageSource: getSearchResultImageSource(result), @@ -961,9 +974,37 @@ function generateSidebarSearchResult(result) { return null; } + const compareReferenceType = otherResult => + otherResult.referenceType === result.referenceType; + + const compareName = otherResult => + getSearchResultName(otherResult) === getSearchResultName(result); + + const ambiguous = + results.some(otherResult => + otherResult !== result && + compareReferenceType(otherResult) && + compareName(otherResult)); + + if (ambiguous) { + preparedSlots.disambiguate = + result.data.disambiguator; + + preparedSlots.disambiguatorString = + info[result.referenceType + 'ResultDisambiguatorString']; + } + return generateSidebarSearchResultTemplate(preparedSlots); } +function getSearchResultName(result) { + return ( + result.data.name ?? + result.data.primaryName ?? + null + ); +} + function getSearchResultImageSource(result) { const {artwork} = result.data; if (!artwork) return null; @@ -1039,6 +1080,15 @@ function generateSidebarSearchResultTemplate(slots) { } } + if (!accentSpan && slots.disambiguate) { + accentSpan = document.createElement('span'); + accentSpan.classList.add('wiki-search-result-disambiguator'); + accentSpan.appendChild( + templateContent(slots.disambiguatorString, { + disambiguator: slots.disambiguate, + })); + } + if (!accentSpan && slots.kindString) { accentSpan = document.createElement('span'); accentSpan.classList.add('wiki-search-result-kind'); diff --git a/src/static/js/search-worker.js b/src/static/js/search-worker.js index e32b4ad5..3e9fbfca 100644 --- a/src/static/js/search-worker.js +++ b/src/static/js/search-worker.js @@ -391,6 +391,8 @@ function performSearchAction({query, options}) { } const interestingFieldCombinations = [ + ['primaryName'], + ['primaryName', 'parentName', 'groups'], ['primaryName', 'parentName'], ['primaryName', 'groups', 'contributors'], @@ -412,7 +414,6 @@ const interestingFieldCombinations = [ ['contributors', 'parentName'], ['contributors', 'groups'], ['primaryName', 'contributors'], - ['primaryName'], ]; function queryGenericIndex(query, options) { diff --git a/src/strings-default.yaml b/src/strings-default.yaml index 4969bcff..93881aa2 100644 --- a/src/strings-default.yaml +++ b/src/strings-default.yaml @@ -857,6 +857,11 @@ misc: artist: "(artist)" group: "(group)" + resultDisambiguator: + group: "({DISAMBIGUATOR})" + flash: "(in {DISAMBIGUATOR})" + track: "(from {DISAMBIGUATOR})" + resultFilter: album: "Albums" artTag: "Art Tags" @@ -1007,6 +1012,10 @@ misc: conceal: "Conceal all artworks" warnings: "In this gallery: {WARNINGS}" + expandCollapseCue: "({CUE})" + expand: "Show the rest!" + collapse: "Collapse these" + noCoverArt: "{ALBUM}" tab: @@ -1028,8 +1037,6 @@ misc: otherCoverArtists: "With {ARTISTS}" - expandCollapseCue: "({CUE})" - albumGalleryGrid: noCoverArt: "{NAME}" @@ -1797,12 +1804,6 @@ groupGalleryPage: caption.seriesAlbumsNotFromGroup: >- Albums marked {MARKER} are part of {SERIES}, but not from {GROUP}. - expand: >- - Show the rest! - - collapse: >- - Collapse these - # # listingIndex: # The listing index page shows all available listings on the wiki, |