diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/content/dependencies/generateArtistInfoPageCommentaryChunkedList.js | 141 | ||||
| -rw-r--r-- | src/content/dependencies/generateContentEntry.js | 8 | ||||
| -rw-r--r-- | src/content/dependencies/generateContentEntryDate.js | 2 | ||||
| -rw-r--r-- | src/content/dependencies/generateLyricsEntry.js | 4 | ||||
| -rw-r--r-- | src/content/dependencies/generateTrackInfoPageOtherReleaseTooltip.js | 16 | ||||
| -rw-r--r-- | src/data/checks.js | 7 | ||||
| -rw-r--r-- | src/data/composite.js | 32 | ||||
| -rw-r--r-- | src/data/composite/things/content/withExpressedOrImplicitArtistReferences.js | 29 | ||||
| -rw-r--r-- | src/data/things/content/ContentEntry.js | 53 | ||||
| -rw-r--r-- | src/search-select.js | 3 | ||||
| -rw-r--r-- | src/static/css/responsive.css | 2 | ||||
| -rw-r--r-- | src/static/css/search.css | 63 | ||||
| -rw-r--r-- | src/static/js/client/sidebar-search.js | 24 | ||||
| -rw-r--r-- | src/strings-default.yaml | 5 |
14 files changed, 276 insertions, 113 deletions
diff --git a/src/content/dependencies/generateArtistInfoPageCommentaryChunkedList.js b/src/content/dependencies/generateArtistInfoPageCommentaryChunkedList.js index 572eb982..6b603375 100644 --- a/src/content/dependencies/generateArtistInfoPageCommentaryChunkedList.js +++ b/src/content/dependencies/generateArtistInfoPageCommentaryChunkedList.js @@ -30,6 +30,10 @@ export default { flashAct, flash, + quoted: + !entry.headingArtists.includes(artist) && + entry.quotedArtists.includes(artist), + annotation: entry.annotation, annotationParts: entry.annotationParts, }, @@ -191,6 +195,11 @@ export default { query.chunks .map(({chunk}) => chunk .map(({itemType}) => itemType)), + + itemQuoted: + query.chunks + .map(({chunk}) => chunk + .map(({quoted}) => quoted)), }), generate: (data, relations, {html, language}) => @@ -206,6 +215,7 @@ export default { itemLinks: relations.itemLinks, itemAnnotations: relations.itemAnnotations, itemTypes: data.itemTypes, + itemQuoted: data.itemQuoted, }).map(({ chunk, chunkLink, @@ -215,64 +225,77 @@ export default { itemLinks, itemAnnotations, itemTypes, + itemQuoted, }) => - language.encapsulate('artistPage.creditList.entry', capsule => - (chunkType === 'album' - ? chunk.slots({ - mode: 'album', - link: chunkLink, - - list: - html.tag('ul', - stitchArrays({ - item: items, - link: itemLinks, - annotation: itemAnnotations, - type: itemTypes, - }).map(({item, link, annotation, type}) => - item.slots({ - // The citation slot, instead of annotation, gives commentary - // a specially custom look. - citation: - annotation.slots({ - mode: 'inline', - absorbPunctuationFollowingExternalLinks: false, - }), - - content: - (type === 'album' - ? html.tag('i', - language.$(capsule, 'album.commentary')) - : language.$(capsule, 'track', {track: link})), - }))), - }) - - : chunkType === 'flash-act' - ? chunk.slots({ - mode: 'flash', - link: chunkLink, - - list: - html.tag('ul', - stitchArrays({ - item: items, - link: itemLinks, - annotation: itemAnnotations, - }).map(({item, link, annotation}) => - item.slots({ - annotation: - (annotation - ? annotation.slots({ - mode: 'inline', - absorbPunctuationFollowingExternalLinks: false, - }) - : null), - - content: - language.$(capsule, 'flash', { - flash: link, - }), - }))), - }) - : null)))), + language.encapsulate('artistPage.creditList.entry', capsule => { + // The citation slot, instead of annotation, gives commentary + // a specially custom look. + const citations = + stitchArrays({annotation: itemAnnotations, quoted: itemQuoted}) + .map(({annotation, quoted}) => + language.encapsulate(capsule, workingCapsule => { + const workingOptions = {}; + + let any = false; + + annotation.setSlots({ + mode: 'inline', + absorbPunctuationFollowingExternalLinks: false, + }); + + if (!html.isBlank(annotation)) { + workingCapsule += '.citation'; + workingOptions.citation = annotation; + any = true; + } + + if (quoted) { + workingCapsule += '.quoted'; + any = true; + } + + if (any) { + return language.$(workingCapsule, workingOptions); + } else { + return html.blank(); + } + })); + + let contents; + + if (chunkType === 'album') { + chunk.setSlot('mode', 'album'); + contents = + stitchArrays({link: itemLinks, type: itemTypes}) + .map(({link, type}) => + (type === 'album' + ? html.tag('i', + language.$(capsule, 'album.commentary')) + : language.$(capsule, 'track', {track: link}))); + + } else if (chunkType === 'flash-act') { + chunk.setSlot('mode', 'flash'); + contents = + itemLinks.map(link => + language.$(capsule, 'flash', {flash: link})); + + } else { + throw new Error(`Gyeep!!`); + } + + chunk.setSlots({ + link: chunkLink, + + list: + html.tag('ul', + stitchArrays({ + item: items, + citation: citations, + content: contents, + }).map(({item, citation, content}) => + item.slots({citation, content}))), + }); + + return chunk; + }))), }; diff --git a/src/content/dependencies/generateContentEntry.js b/src/content/dependencies/generateContentEntry.js index c77f744a..40c637a3 100644 --- a/src/content/dependencies/generateContentEntry.js +++ b/src/content/dependencies/generateContentEntry.js @@ -3,14 +3,14 @@ import {empty} from '#sugar'; export default { relations: (relation, entry) => ({ artistLinks: - (!empty(entry.artists) && !entry.artistText - ? entry.artists + (!empty(entry.headingArtists) && !entry.headingArtistText + ? entry.headingArtists .map(artist => relation('linkArtist', artist)) : null), artistsContent: - (entry.artistText - ? relation('transformContent', entry.artistText) + (entry.headingArtistText + ? relation('transformContent', entry.headingArtistText) : null), annotationContent: diff --git a/src/content/dependencies/generateContentEntryDate.js b/src/content/dependencies/generateContentEntryDate.js index 845cb5ed..5255c7ea 100644 --- a/src/content/dependencies/generateContentEntryDate.js +++ b/src/content/dependencies/generateContentEntryDate.js @@ -35,7 +35,7 @@ export default { : entry.thing.isTrack && entry.thing.date === entry.thing.album.date && - entry.thing.style === 'single' + entry.thing.album.style === 'single' ? 'single' : entry.thing.isTrack && diff --git a/src/content/dependencies/generateLyricsEntry.js b/src/content/dependencies/generateLyricsEntry.js index 15f84b27..0ecf319f 100644 --- a/src/content/dependencies/generateLyricsEntry.js +++ b/src/content/dependencies/generateLyricsEntry.js @@ -4,10 +4,10 @@ export default { relation('transformContent', entry.body), artistText: - relation('transformContent', entry.artistText), + relation('transformContent', entry.headingArtistText), artistLinks: - entry.artists + entry.headingArtists .filter(artist => artist.name !== 'HSMusic Wiki') // smh .map(artist => relation('linkArtist', artist)), diff --git a/src/content/dependencies/generateTrackInfoPageOtherReleaseTooltip.js b/src/content/dependencies/generateTrackInfoPageOtherReleaseTooltip.js index fcb2e2fa..4c6bda1b 100644 --- a/src/content/dependencies/generateTrackInfoPageOtherReleaseTooltip.js +++ b/src/content/dependencies/generateTrackInfoPageOtherReleaseTooltip.js @@ -20,6 +20,9 @@ export default { (compareKebabCase(otherTrack.name, currentTrack.name) ? null : otherTrack.name), + + onSingle: + otherTrack.album.style === 'single', }), generate: (data, relations, {html, language}) => @@ -36,10 +39,17 @@ export default { ], content: [ - language.$(capsule, 'differentName', { - [language.onlyIfOptions]: ['name'], + language.encapsulate(capsule, 'differentName', workingCapsule => { + const workingOptions = { + [language.onlyIfOptions]: ['name'], + name: data.differentName, + }; + + if (data.onSingle) { + workingCapsule += '.onSingle'; + } - name: data.differentName, + return language.$(workingCapsule, workingOptions); }), data.otherDate && data.currentDate && diff --git a/src/data/checks.js b/src/data/checks.js index 0a0e7f52..01b5cf9e 100644 --- a/src/data/checks.js +++ b/src/data/checks.js @@ -352,6 +352,9 @@ export function filterReferenceErrors(wikiData, { switch (findFnKey) { case '_content': + // note: quoted artists ("Quoted Artists" field)`) currently + // not handled here at all. limitation of current code! oops! + if (value) { value = value.map(entry => @@ -721,13 +724,13 @@ export function reportContentTextErrors(wikiData, { const commentaryShape = { body: 'commentary body', - artistText: 'commentary artist text', + headingArtistText: 'commentary artist text', annotation: 'commentary annotation', }; const lyricsShape = { body: 'lyrics body', - artistText: 'lyrics artist text', + headingArtistText: 'lyrics artist text', annotation: 'lyrics annotation', }; diff --git a/src/data/composite.js b/src/data/composite.js index 8ac906c7..3b462ef5 100644 --- a/src/data/composite.js +++ b/src/data/composite.js @@ -10,17 +10,27 @@ import {TupleMap} from '#wiki-data'; const globalCompositeCache = {}; -const _valueIntoToken = shape => - (value = null) => - (value === null - ? Symbol.for(`hsmusic.composite.${shape}`) - : typeof value === 'string' - ? Symbol.for(`hsmusic.composite.${shape}:${value}`) - : { - symbol: Symbol.for(`hsmusic.composite.${shape.split('.')[0]}`), - shape, - value, - }); +const _valueIntoToken = shape => (value = null) => { + if (value === null) { + return Symbol.for(`hsmusic.composite.${shape}`); + } + + if (typeof value === 'string') { + return Symbol.for(`hsmusic.composite.${shape}:${value}`); + } + + if (typeof value === 'object') { + if (Object.values(value).some(isInputToken)) { + throw new TypeError(`Don't nest input tokens inside ${shape}()`); + } + } + + return { + symbol: Symbol.for(`hsmusic.composite.${shape.split('.')[0]}`), + shape, + value, + }; +}; export const input = _valueIntoToken('input'); input.symbol = Symbol.for('hsmusic.composite.input'); diff --git a/src/data/composite/things/content/withExpressedOrImplicitArtistReferences.js b/src/data/composite/things/content/withExpressedOrImplicitArtistReferences.js index 69da8c75..a6200ee8 100644 --- a/src/data/composite/things/content/withExpressedOrImplicitArtistReferences.js +++ b/src/data/composite/things/content/withExpressedOrImplicitArtistReferences.js @@ -8,16 +8,19 @@ export default templateCompositeFrom({ annotation: `withExpressedOrImplicitArtistReferences`, inputs: { - from: input({type: 'array', acceptsNull: true}), + fromExpressed: input({type: 'array', acceptsNull: true}), + fromContent: input({type: 'string', acceptsNull: true}), + + filterArtistTags: input({type: 'function', defaultValue: () => true}), }, outputs: ['#artistReferences'], steps: () => [ { - dependencies: [input('from')], + dependencies: [input('fromExpressed')], compute: (continuation, { - [input('from')]: expressedArtistReferences, + [input('fromExpressed')]: expressedArtistReferences, }) => (expressedArtistReferences ? continuation.raiseOutput({'#artistReferences': expressedArtistReferences}) @@ -25,12 +28,12 @@ export default templateCompositeFrom({ }, raiseOutputWithoutDependency({ - dependency: 'artistText', - output: input.value({'#artistReferences': null}), + dependency: input('fromContent'), + output: input.value({'#artistReferences': []}), }), withContentNodes({ - from: 'artistText', + from: input('fromContent'), }), withMappedList({ @@ -42,13 +45,19 @@ export default templateCompositeFrom({ '#mappedList': '#artistTagFilter', }), - withFilteredList({ - list: '#contentNodes', - filter: '#artistTagFilter', + withFilteredList('#contentNodes', '#artistTagFilter') + .outputs({'#filteredList': '#artistTags'}), + + withMappedList({ + list: '#artistTags', + map: input('filterArtistTags'), }).outputs({ - '#filteredList': '#artistTags', + '#mappedList': '#customFilter', }), + withFilteredList({list: '#artistTags', filter: '#customFilter'}) + .outputs({'#filteredList': '#artistTags'}), + withMappedList({ list: '#artistTags', map: input.value(node => diff --git a/src/data/things/content/ContentEntry.js b/src/data/things/content/ContentEntry.js index 04df303f..47f86622 100644 --- a/src/data/things/content/ContentEntry.js +++ b/src/data/things/content/ContentEntry.js @@ -1,5 +1,5 @@ import {input, V} from '#composite'; -import {transposeArrays} from '#sugar'; +import {transposeArrays, unique} from '#sugar'; import Thing from '#thing'; import {is, isDate, validateReferenceList} from '#validators'; import {parseDate} from '#yaml'; @@ -33,14 +33,43 @@ export class ContentEntry extends Thing { thing: thing(), - artists: [ + headingArtists: [ withExpressedOrImplicitArtistReferences({ - from: input.updateValue({ + fromExpressed: input.updateValue({ validate: validateReferenceList('artist'), }), + + fromContent: 'headingArtistText', + }), + + withResolvedReferenceList({ + list: '#artistReferences', + find: soupyFind.input('artist'), }), - exitWithoutDependency('#artistReferences', V([])), + exposeDependency('#resolvedReferenceList'), + ], + + quotedArtists: [ + exitWithoutDependency('body', V([])), + + { + dependencies: ['body'], + compute: (continuation, {body}) => continuation({ + ['#filterArtistTags']: node => + /(\n|^)> <i>$/.test(body.slice(0, node.i)) && + /^:<\/i>/.test(body.slice(node.iEnd)), + }), + }, + + withExpressedOrImplicitArtistReferences({ + fromExpressed: input.updateValue({ + validate: validateReferenceList('artist'), + }), + + fromContent: 'body', + filterArtistTags: '#filterArtistTags', + }), withResolvedReferenceList({ list: '#artistReferences', @@ -50,7 +79,7 @@ export class ContentEntry extends Thing { exposeDependency('#resolvedReferenceList'), ], - artistText: contentString(), + headingArtistText: contentString(), annotation: contentString(), @@ -119,6 +148,14 @@ export class ContentEntry extends Thing { isContentEntry: exposeConstant(V(true)), + artists: [ + { + dependencies: ['headingArtists', 'quotedArtists'], + compute: ({headingArtists, quotedArtists}) => + unique([...headingArtists, ...quotedArtists]), + }, + ], + annotationParts: [ withAnnotationPartNodeLists(), @@ -230,8 +267,10 @@ export class ContentEntry extends Thing { static [Thing.yamlDocumentSpec] = { fields: { - 'Artists': {property: 'artists'}, - 'Artist Text': {property: 'artistText'}, + 'Artists': {property: 'headingArtists'}, + 'Artist Text': {property: 'headingArtistText'}, + + 'Quoted Artists': {property: 'quotedArtists'}, 'Annotation': {property: 'annotation'}, diff --git a/src/search-select.js b/src/search-select.js index 3133c642..dc1508be 100644 --- a/src/search-select.js +++ b/src/search-select.js @@ -158,7 +158,8 @@ function genericSelect(wikiData) { return [ sortByGroupRank(wikiData.albumData.slice()), - wikiData.artTagData, + wikiData.artTagData + .filter(artTag => !artTag.isContentWarning), wikiData.artistData .filter(artist => !artist.isAlias), diff --git a/src/static/css/responsive.css b/src/static/css/responsive.css index 86cd7eb6..38e4188a 100644 --- a/src/static/css/responsive.css +++ b/src/static/css/responsive.css @@ -106,7 +106,7 @@ } .wiki-search-sidebar-box { - max-height: max(245px, 60vh, calc(100vh - 205px)); + --keep-viewport-visible: 205px; } /* End duplicated for "sidebars in content column" */ diff --git a/src/static/css/search.css b/src/static/css/search.css index f421803b..3c56eed6 100644 --- a/src/static/css/search.css +++ b/src/static/css/search.css @@ -5,11 +5,17 @@ padding: 1px 0 0 0; z-index: 100; - max-height: calc(100vh - 20px); + + --keep-viewport-visible: 125px; + max-height: max(245px, 60vh, calc(100vh - var(--keep-viewport-visible))); display: flex; flex-direction: column; } + + #banner.short ~ * .wiki-search-sidebar-box { + --keep-viewport-visible: 180px; + } } @layer material { @@ -32,17 +38,20 @@ /* Interactions with other sidebar boxes */ -@layer interaction { - /* This is to say, any sidebar that's *not* - * the first sidebar after the search box. +@layer layout { + /* This is to say, any sidebar that's *not* the first sidebar + * after the search box, effectively squishing the rest of the + * boxes a bit tighter together. */ .wiki-search-sidebar-box.showing-results + .sidebar ~ .sidebar { - margin-top: 5px; + margin-top: 8px; } +} +@layer interaction { .wiki-search-sidebar-box.showing-results ~ .sidebar:not(:hover) { opacity: 0.8; - filter: brightness(0.7); + filter: brightness(0.85); } } @@ -191,6 +200,8 @@ @layer layout { .wiki-search-context-container { padding: 2px 12px 4px; + padding-left: calc(12px + 1.2ch); + text-indent: -1.2ch; } } @@ -322,11 +333,51 @@ @layer layout { .wiki-search-results-container { + position: relative; margin-bottom: 0; padding: 2px; } } +@layer interaction { + .wiki-search-results-container::before, + .wiki-search-results-container::after { + content: ""; + display: block; + position: sticky; + pointer-events: none; + z-index: 1; + } + + .wiki-search-result:hover { + z-index: 2; + } + + /* Shadow along top edge */ + + .wiki-search-results-container > :first-child { + margin-top: -4px; + } + + .wiki-search-results-container::before { + height: 8px; top: -2px; + background: linear-gradient(to bottom, black, black 50%, transparent); + opacity: 0.4; + } + + /* Shadow along bottom edge */ + + .wiki-search-results-container > :last-child { + margin-bottom: -10px; + } + + .wiki-search-results-container::after { + height: 16px; bottom: -2px; + background: linear-gradient(to top, black, black 30%, transparent); + opacity: 0.4; + } +} + /* Basic result styling, including interactions */ @layer layout { diff --git a/src/static/js/client/sidebar-search.js b/src/static/js/client/sidebar-search.js index c39c38bc..7b01cb00 100644 --- a/src/static/js/client/sidebar-search.js +++ b/src/static/js/client/sidebar-search.js @@ -106,6 +106,8 @@ export const info = { recallingRecentSearch: null, recallingRecentSearchFromMouse: null, + justPerformedActiveQuery: false, + currentValue: null, workerStatus: null, @@ -585,7 +587,6 @@ export function addPageListeners() { clearSidebarSearch(); clearSidebarFilter(); possiblyHideSearchSidebarColumn(); - restoreSidebarSearchColumn(); }); forEachFilter((type, filterLink) => { @@ -731,6 +732,7 @@ async function activateSidebarSearch(query) { return; } + state.justPerformedActiveQuery = true; state.searchStage = 'complete'; updateSidebarSearchStatus(); @@ -796,6 +798,7 @@ function clearSidebarSearch() { info.searchInput.value = ''; state.searchStage = null; + state.justPerformedActiveQuery = false; clearActiveQuery(); @@ -1383,6 +1386,8 @@ function hideSidebarSearchResults() { cssProp(info.endSearchRule, 'display', 'none'); cssProp(info.endSearchLine, 'display', 'none'); + + restoreSidebarSearchColumn(); } function focusFirstSidebarSearchResult() { @@ -1466,7 +1471,7 @@ function possiblyHideSearchSidebarColumn() { // This should be called after results are shown, since it checks the // elements added to understand the current search state. function tidySidebarSearchColumn() { - const {state} = info; + const {session, state} = info; // Don't *re-tidy* the sidebar if we've already tidied it to display // some results. This flag will get cleared if the search is dismissed @@ -1475,17 +1480,24 @@ function tidySidebarSearchColumn() { return; } - const here = location.href.replace(/\/$/, ''); + const hrefHere = location.href.replace(/\/$/, ''); const currentPageIsResult = Array.from(info.results.querySelectorAll('a')) .some(link => { - const there = link.href.replace(/\/$/, ''); - return here === there; + const hrefThere = link.href.replace(/\/$/, ''); + return hrefHere === hrefThere; }); + const currentPageIsContext = + location.pathname === session.activeQueryContextPagePathname; + // Don't tidy the sidebar if you've navigated to some other page than // what's in the current result list. - if (!currentPageIsResult) { + if ( + !state.justPerformedActiveQuery && + !currentPageIsResult && + !currentPageIsContext + ) { return; } diff --git a/src/strings-default.yaml b/src/strings-default.yaml index 54741239..ed8f236f 100644 --- a/src/strings-default.yaml +++ b/src/strings-default.yaml @@ -310,6 +310,7 @@ releaseInfo: tooltip: differentName: "as {NAME}" + differentName.onSingle: "{NAME}" tracksReferenced: _: "Tracks that {TRACK} references:" @@ -1395,6 +1396,10 @@ artistPage: withCitation: "{ENTRY} ({CITATION})" + citation: "{CITATION}" + citation.quoted: "quoted: {CITATION}" + quoted: "quoted" + # rerelease: # Tracks which aren't the original release don't display co- # artists or contributors, and get dimmed a little compared |