diff options
Diffstat (limited to 'src/content')
14 files changed, 346 insertions, 152 deletions
diff --git a/src/content/dependencies/generateAlbumTrackListItem.js b/src/content/dependencies/generateAlbumTrackListItem.js index ab8d477d..68722a83 100644 --- a/src/content/dependencies/generateAlbumTrackListItem.js +++ b/src/content/dependencies/generateAlbumTrackListItem.js @@ -40,7 +40,7 @@ export default { generate: (data, relations, slots) => relations.item.slots({ - showArtists: true, + showArtists: 'auto', showDuration: (slots.collapseDurationScope === 'track' diff --git a/src/content/dependencies/generateCoverArtwork.js b/src/content/dependencies/generateCoverArtwork.js index 89b66ce0..616b3c95 100644 --- a/src/content/dependencies/generateCoverArtwork.js +++ b/src/content/dependencies/generateCoverArtwork.js @@ -122,6 +122,30 @@ export default { thumb: 'medium', reveal: true, link: true, + + responsiveThumb: true, + responsiveSizes: + // No clamp(), min(), or max() here because Safari. + // The boundaries here are mostly experimental, apart from + // the ones which flat-out switch layouts. + + // Layout - Thin (phones) + // Most of viewport width + '(max-width: 600px) 90vw,\n' + + + // Layout - Medium + // Sidebar is hidden; content area is by definition + // most of the viewport + '(max-width: 640px) 220px,\n' + + '(max-width: 800px) 36vw,\n' + + '(max-width: 850px) 280px,\n' + + + // Layout - Wide + // Sidebar is visible; content area has its own maximum + // Assume the sidebar is at minimum width + '(max-width: 880px) 220px,\n' + + '(max-width: 1050pz) calc(0.40 * (90vw - 150px - 10px)),\n' + + '280px', }), slots.showOriginDetails && diff --git a/src/content/dependencies/generateFlashInfoPage.js b/src/content/dependencies/generateFlashInfoPage.js index 86ec6648..935ffdc6 100644 --- a/src/content/dependencies/generateFlashInfoPage.js +++ b/src/content/dependencies/generateFlashInfoPage.js @@ -63,7 +63,7 @@ export default { relation('generateFlashNavAccent', flash), featuredTracksList: - relation('generateTrackList', flash.featuredTracks), + relation('generateTrackList', flash.featuredTracks, []), contributorContributionList: relation('generateContributionList', flash.contributorContribs), diff --git a/src/content/dependencies/generateNearbyTrackList.js b/src/content/dependencies/generateNearbyTrackList.js new file mode 100644 index 00000000..56ab2df5 --- /dev/null +++ b/src/content/dependencies/generateNearbyTrackList.js @@ -0,0 +1,44 @@ +export default { + query: (tracks, contextTrack, _contextContributions) => ({ + presentedTracks: + (contextTrack + ? tracks.map(track => + track.otherReleases.find(({album}) => album === contextTrack.album) ?? + track) + : tracks), + }), + + relations: (relation, query, _tracks, _contextTrack, contextContributions) => ({ + items: + query.presentedTracks + .map(track => relation('generateTrackListItem', track, contextContributions)), + }), + + slots: { + showArtists: { + validate: v => v.is(true, false, 'auto'), + default: 'auto', + }, + + showDuration: { + type: 'boolean', + default: false, + }, + + colorMode: { + validate: v => v.is('none', 'track', 'line'), + default: 'track', + }, + }, + + generate: (relations, slots, {html}) => + html.tag('ul', + {[html.onlyIfContent]: true}, + + relations.items.map(item => + item.slots({ + showArtists: slots.showArtists, + showDuration: slots.showDuration, + colorMode: slots.colorMode, + }))), +}; diff --git a/src/content/dependencies/generateReferencedTracksList.js b/src/content/dependencies/generateReferencedTracksList.js new file mode 100644 index 00000000..1d566ce9 --- /dev/null +++ b/src/content/dependencies/generateReferencedTracksList.js @@ -0,0 +1,29 @@ +export default { + relations: (relation, track) => ({ + previousProductionTrackList: + relation('generateNearbyTrackList', + track.previousProductionTracks, + track, + track.artistContribs), + + referencedTrackList: + relation('generateNearbyTrackList', + track.referencedTracks, + track, + []), + }), + + generate: (relations, {html, language}) => + html.tag('ul', {[html.onlyIfContent]: true}, [ + html.inside(relations.previousProductionTrackList) + .map(li => html.inside(li)) + .map(label => + html.tag('li', + language.$('trackList.item.previousProduction', + {track: label}))), + + html.inside(relations.referencedTrackList), + ]), +}; + + diff --git a/src/content/dependencies/generateTrackInfoPage.js b/src/content/dependencies/generateTrackInfoPage.js index 92e00a41..d3c2d766 100644 --- a/src/content/dependencies/generateTrackInfoPage.js +++ b/src/content/dependencies/generateTrackInfoPage.js @@ -80,17 +80,20 @@ export default { readCommentaryLine: relation('generateReadCommentaryLine', track), - otherReleasesList: - relation('generateTrackInfoPageOtherReleasesList', track), + otherReleasesLine: + relation('generateTrackInfoPageOtherReleasesLine', track), + + previousProductionLine: + relation('generateTrackInfoPagePreviousProductionLine', track), contributorContributionList: relation('generateContributionList', track.contributorContribs), referencedTracksList: - relation('generateTrackList', track.referencedTracks, track), + relation('generateReferencedTracksList', track), sampledTracksList: - relation('generateTrackList', track.sampledTracks, track), + relation('generateNearbyTrackList', track.sampledTracks, track, []), referencedByTracksList: relation('generateTrackListDividedByGroups', @@ -228,7 +231,11 @@ export default { })), ])), - relations.otherReleasesList, + html.tag('p', {[html.onlyIfContent]: true}, + relations.otherReleasesLine), + + html.tag('p', {[html.onlyIfContent]: true}, + relations.previousProductionLine), html.tags([ relations.contentHeading.clone() diff --git a/src/content/dependencies/generateTrackInfoPageOtherReleasesLine.js b/src/content/dependencies/generateTrackInfoPageOtherReleasesLine.js new file mode 100644 index 00000000..1793b73f --- /dev/null +++ b/src/content/dependencies/generateTrackInfoPageOtherReleasesLine.js @@ -0,0 +1,80 @@ +import {onlyItem, stitchArrays} from '#sugar'; + +export default { + query(track) { + const query = {}; + + query.singleSingle = + onlyItem( + track.otherReleases.filter(track => track.album.style === 'single')); + + query.regularReleases = + (query.singleSingle + ? track.otherReleases.filter(track => track !== query.singleSingle) + : track.otherReleases); + + return query; + }, + + relations: (relation, query, _track) => ({ + singleLink: + (query.singleSingle + ? relation('linkTrack', query.singleSingle) + : null), + + trackLinks: + query.regularReleases + .map(track => relation('linkTrack', track)), + }), + + data: (query, _track) => ({ + albumNames: + query.regularReleases + .map(track => track.album.name), + + albumColors: + query.regularReleases + .map(track => track.album.color), + }), + + generate: (data, relations, {html, language}) => + language.encapsulate('releaseInfo.alsoReleased', capsule => + language.encapsulate(capsule, workingCapsule => { + const workingOptions = {}; + + let any = false; + + const albumList = + language.formatConjunctionList( + stitchArrays({ + trackLink: relations.trackLinks, + albumName: data.albumNames, + albumColor: data.albumColors, + }).map(({trackLink, albumName, albumColor}) => + trackLink.slots({ + content: language.sanitize(albumName), + color: albumColor, + }))); + + if (!html.isBlank(albumList)) { + any = true; + workingCapsule += '.onAlbums'; + workingOptions.albums = albumList; + } + + if (relations.singleLink) { + any = true; + workingCapsule += '.asSingle'; + workingOptions.single = + relations.singleLink.slots({ + content: language.$(capsule, 'single'), + }); + } + + if (any) { + return language.$(workingCapsule, workingOptions); + } else { + return html.blank(); + } + })), +}; diff --git a/src/content/dependencies/generateTrackInfoPageOtherReleasesList.js b/src/content/dependencies/generateTrackInfoPageOtherReleasesList.js deleted file mode 100644 index ca6c3fb7..00000000 --- a/src/content/dependencies/generateTrackInfoPageOtherReleasesList.js +++ /dev/null @@ -1,83 +0,0 @@ -import {onlyItem, stitchArrays} from '#sugar'; - -export default { - query(track) { - const query = {}; - - query.singleSingle = - onlyItem( - track.otherReleases.filter(track => track.album.style === 'single')); - - query.regularReleases = - (query.singleSingle - ? track.otherReleases.filter(track => track !== query.singleSingle) - : track.otherReleases); - - return query; - }, - - relations: (relation, query, _track) => ({ - singleLink: - (query.singleSingle - ? relation('linkTrack', query.singleSingle) - : null), - - trackLinks: - query.regularReleases - .map(track => relation('linkTrack', track)), - }), - - data: (query, _track) => ({ - albumNames: - query.regularReleases - .map(track => track.album.name), - - albumColors: - query.regularReleases - .map(track => track.album.color), - }), - - generate: (data, relations, {html, language}) => - html.tag('p', - {[html.onlyIfContent]: true}, - - language.encapsulate('releaseInfo.alsoReleased', capsule => - language.encapsulate(capsule, workingCapsule => { - const workingOptions = {}; - - let any = false; - - const albumList = - language.formatConjunctionList( - stitchArrays({ - trackLink: relations.trackLinks, - albumName: data.albumNames, - albumColor: data.albumColors, - }).map(({trackLink, albumName, albumColor}) => - trackLink.slots({ - content: language.sanitize(albumName), - color: albumColor, - }))); - - if (!html.isBlank(albumList)) { - any = true; - workingCapsule += '.onAlbums'; - workingOptions.albums = albumList; - } - - if (relations.singleLink) { - any = true; - workingCapsule += '.asSingle'; - workingOptions.single = - relations.singleLink.slots({ - content: language.$(capsule, 'single'), - }); - } - - if (any) { - return language.$(workingCapsule, workingOptions); - } else { - return html.blank(); - } - }))), -}; diff --git a/src/content/dependencies/generateTrackInfoPagePreviousProductionLine.js b/src/content/dependencies/generateTrackInfoPagePreviousProductionLine.js new file mode 100644 index 00000000..b2f50cf3 --- /dev/null +++ b/src/content/dependencies/generateTrackInfoPagePreviousProductionLine.js @@ -0,0 +1,37 @@ +import {stitchArrays} from '#sugar'; +import {getKebabCase} from '#wiki-data'; + +export default { + relations: (relation, track) => ({ + trackLinks: + track.followingProductionTracks + .map(track => relation('linkTrack', track)), + + albumLinks: + track.followingProductionTracks + .map(following => + (following.album !== track.album && + getKebabCase(following.name) === getKebabCase(track.name) + + ? relation('linkAlbum', following.album) + : null)), + }), + + generate: (relations, {language}) => + language.encapsulate('releaseInfo.previousProduction', capsule => + language.$(capsule, { + [language.onlyIfOptions]: ['tracks'], + + tracks: + stitchArrays({ + trackLink: relations.trackLinks, + albumLink: relations.albumLinks, + }).map(({trackLink, albumLink}) => + (albumLink + ? language.$(capsule, 'trackOnAlbum', { + track: trackLink, + album: albumLink, + }) + : trackLink)), + })), +}; diff --git a/src/content/dependencies/generateTrackList.js b/src/content/dependencies/generateTrackList.js index e30feb23..c259c914 100644 --- a/src/content/dependencies/generateTrackList.js +++ b/src/content/dependencies/generateTrackList.js @@ -1,20 +1,21 @@ export default { - query: (tracks, contextTrack) => ({ - presentedTracks: - (contextTrack - ? tracks.map(track => - track.otherReleases.find(({album}) => album === contextTrack.album) ?? - track) - : tracks), - }), - - relations: (relation, query, _tracks, _contextTrack) => ({ + relations: (relation, tracks, contextContributions) => ({ items: - query.presentedTracks - .map(track => relation('generateTrackListItem', track, [])), + tracks.map(track => + relation('generateTrackListItem', track, contextContributions)), }), slots: { + showArtists: { + validate: v => v.is(true, false, 'auto'), + default: 'auto', + }, + + showDuration: { + type: 'boolean', + default: false, + }, + colorMode: { validate: v => v.is('none', 'track', 'line'), default: 'track', @@ -27,8 +28,8 @@ export default { relations.items.map(item => item.slots({ - showArtists: true, - showDuration: false, + showArtists: slots.showArtists, + showDuration: slots.showDuration, colorMode: slots.colorMode, }))), }; diff --git a/src/content/dependencies/generateTrackListDividedByGroups.js b/src/content/dependencies/generateTrackListDividedByGroups.js index d7342891..419d7c0f 100644 --- a/src/content/dependencies/generateTrackListDividedByGroups.js +++ b/src/content/dependencies/generateTrackListDividedByGroups.js @@ -45,7 +45,7 @@ export default { relations: (relation, query, sprawl, tracks, contextTrack) => ({ flatList: (empty(sprawl.divideTrackListsByGroups) - ? relation('generateTrackList', tracks, contextTrack) + ? relation('generateNearbyTrackList', tracks, contextTrack, []) : null), contentHeading: @@ -57,12 +57,12 @@ export default { groupedTrackLists: query.groupedTracks - .map(tracks => relation('generateTrackList', tracks, contextTrack)), + .map(tracks => relation('generateNearbyTrackList', tracks, contextTrack, [])), ungroupedTrackList: (empty(query.ungroupedTracks) ? null - : relation('generateTrackList', query.ungroupedTracks, contextTrack)), + : relation('generateNearbyTrackList', query.ungroupedTracks, contextTrack, [])), }), data: (query, _sprawl, _tracks) => ({ diff --git a/src/content/dependencies/generateTrackListItem.js b/src/content/dependencies/generateTrackListItem.js index 9de9c3a6..c4b7c21e 100644 --- a/src/content/dependencies/generateTrackListItem.js +++ b/src/content/dependencies/generateTrackListItem.js @@ -3,12 +3,18 @@ export default { trackLink: relation('linkTrack', track), - credit: + contextualCredit: relation('generateArtistCredit', track.artistContribs, contextContributions, track.artistText), + acontextualCredit: + relation('generateArtistCredit', + track.artistContribs, + [], + track.artistText), + colorStyle: relation('generateColorStyleAttribute', track.color), @@ -27,12 +33,11 @@ export default { }), slots: { - // showArtists enables showing artists *at all.* It doesn't take precedence - // over behavior which automatically collapses (certain) artists because of - // provided context contributions. + // true always shows artists, false never does; 'auto' shows only if + // the track's artists differ from the given context contributions. showArtists: { - type: 'boolean', - default: true, + validate: v => v.is(true, false, 'auto'), + default: 'auto', }, // If true and the track doesn't have a duration, a missing-duration cue @@ -72,24 +77,33 @@ export default { : relations.missingDuration); } - const artistCapsule = language.encapsulate(itemCapsule, 'withArtists'); - - relations.credit.setSlots({ - normalStringKey: - artistCapsule + '.by', - - featuringStringKey: - artistCapsule + '.featuring', - - normalFeaturingStringKey: - artistCapsule + '.by.featuring', - }); - - if (!html.isBlank(relations.credit)) { - workingCapsule += '.withArtists'; - workingOptions.by = - html.tag('span', {class: 'by'}, - relations.credit); + const chosenCredit = + (slots.showArtists === true + ? relations.acontextualCredit + : slots.showArtists === 'auto' + ? relations.contextualCredit + : null); + + if (chosenCredit) { + const artistCapsule = language.encapsulate(itemCapsule, 'withArtists'); + + chosenCredit.setSlots({ + normalStringKey: + artistCapsule + '.by', + + featuringStringKey: + artistCapsule + '.featuring', + + normalFeaturingStringKey: + artistCapsule + '.by.featuring', + }); + + if (!html.isBlank(chosenCredit)) { + workingCapsule += '.withArtists'; + workingOptions.by = + html.tag('span', {class: 'by'}, + chosenCredit); + } } return language.$(workingCapsule, workingOptions); diff --git a/src/content/dependencies/image.js b/src/content/dependencies/image.js index 1b6b08dd..d979b0bc 100644 --- a/src/content/dependencies/image.js +++ b/src/content/dependencies/image.js @@ -28,6 +28,8 @@ export default { slots: { thumb: {type: 'string'}, + responsiveThumb: {type: 'boolean', default: false}, + responsiveSizes: {type: 'string'}, reveal: {type: 'boolean', default: true}, lazy: {type: 'boolean', default: false}, @@ -199,31 +201,29 @@ export default { // so it won't be set if thumbnails aren't available. let revealSrc = null; + let originalDimensions; + let availableThumbs; + let selectedThumbtack; + + const getThumbSrc = (thumbtack) => + to('thumb.path', mediaSrc.replace(/\.(png|jpg)$/, `.${thumbtack}.jpg`)); + // If thumbnails are available *and* being used, calculate thumbSrc, // and provide some attributes relevant to the large image overlay. if (hasThumbnails && slots.thumb) { - const selectedSize = + selectedThumbtack = getThumbnailEqualOrSmaller(slots.thumb, mediaSrc); - const mediaSrcJpeg = - mediaSrc.replace(/\.(png|jpg)$/, `.${selectedSize}.jpg`); - displaySrc = - to('thumb.path', mediaSrcJpeg); + getThumbSrc(selectedThumbtack); if (willReveal) { - const miniSize = - getThumbnailEqualOrSmaller('mini', mediaSrc); - - const mediaSrcJpeg = - mediaSrc.replace(/\.(png|jpg)$/, `.${miniSize}.jpg`); - revealSrc = - to('thumb.path', mediaSrcJpeg); + getThumbSrc(getThumbnailEqualOrSmaller('mini', mediaSrc)); } - const originalDimensions = getDimensionsOfImagePath(mediaSrc); - const availableThumbs = getThumbnailsAvailableForDimensions(originalDimensions); + originalDimensions = getDimensionsOfImagePath(mediaSrc); + availableThumbs = getThumbnailsAvailableForDimensions(originalDimensions); const fileSize = (willLink && mediaSrc @@ -239,11 +239,54 @@ export default { !empty(availableThumbs) && {'data-thumbs': availableThumbs - .map(([name, size]) => `${name}:${size}`) + .map(([tack, size]) => `${tack}:${size}`) .join(' ')}, ]); } + let displayStaticImg = + html.tag('img', + imgAttributes, + {src: displaySrc}); + + if (hasThumbnails && slots.responsiveThumb) responsive: { + if (slots.lazy) { + logWarn`${'responsiveThumb'} and ${'lazy'} are used together, but not compatible`; + break responsive; + } + + if (!slots.thumb) { + logWarn`${'responsiveThumb'} must be used alongside a default ${'thumb'}`; + break responsive; + } + + const srcset = [ + // Never load the original source, which might be a very large + // uncompressed file. Bah! + /* [originalSrc, `${Math.min(...originalDimensions)}w`], */ + + ...availableThumbs.map(([tack, size]) => + [getThumbSrc(tack), `${Math.floor(0.95 * size)}w`]), + + // fallback + [displaySrc], + ].map(line => line.join(' ')).join(',\n'); + + displayStaticImg = + html.tag('img', + imgAttributes, + + {sizes: + (slots.responsiveSizes.match(/(?=(?:,|^))\s*\S/) + // slot provided fallback size + ? slots.responsiveSizes + // default fallback size + : slots.responsiveSizes + ',\n' + + new Map(availableThumbs).get(selectedThumbtack) + 'px')}, + + {srcset}); + } + if (!displaySrc) { return ( prepare( @@ -252,10 +295,7 @@ export default { } const images = { - displayStatic: - html.tag('img', - imgAttributes, - {src: displaySrc}), + displayStatic: displayStaticImg, displayLazy: slots.lazy && diff --git a/src/content/dependencies/linkThing.js b/src/content/dependencies/linkThing.js index 7784afe7..166a857d 100644 --- a/src/content/dependencies/linkThing.js +++ b/src/content/dependencies/linkThing.js @@ -77,14 +77,15 @@ export default { const linkAttributes = slots.attributes; const wrapperAttributes = html.attributes(); + const name = + relations.name.slot('preferShortName', slots.preferShortName); + const showShortName = slots.preferShortName && !data.nameText && data.nameShort && data.nameShort !== data.name; - const name = relations.name; - const showWikiTooltip = (slots.tooltipStyle === 'auto' ? showShortName |