diff options
Diffstat (limited to 'src')
28 files changed, 1215 insertions, 222 deletions
diff --git a/src/common-util/wiki-data.js b/src/common-util/wiki-data.js index f47a48ff..a8c9ac8a 100644 --- a/src/common-util/wiki-data.js +++ b/src/common-util/wiki-data.js @@ -235,6 +235,15 @@ export function getArtistNumContributions(artist) { artist.flashContributorContributions .filter(keep), + + artist.miscellaneousAdditionalFileArtistContributions + .filter(keep), + + artist.sheetMusicFileArtistContributions + .filter(keep), + + artist.midiProjectFileArtistContributions + .filter(keep), ], ({length}) => length); } diff --git a/src/content/dependencies/generateAdditionalFilesList.js b/src/content/dependencies/generateAdditionalFilesList.js index 699c5f86..9eb602f7 100644 --- a/src/content/dependencies/generateAdditionalFilesList.js +++ b/src/content/dependencies/generateAdditionalFilesList.js @@ -6,6 +6,7 @@ export default { }), slots: { + string: {type: 'string', default: 'miscellaneousAdditionalFiles'}, showFileSizes: {type: 'boolean', default: true}, }, @@ -14,6 +15,7 @@ export default { {[html.onlyIfContent]: true}, relations.chunks.map(chunk => chunk.slots({ + string: slots.string, showFileSizes: slots.showFileSizes, }))), }; diff --git a/src/content/dependencies/generateAdditionalFilesListChunk.js b/src/content/dependencies/generateAdditionalFilesListChunk.js index 466a5d8d..7d0e41c1 100644 --- a/src/content/dependencies/generateAdditionalFilesListChunk.js +++ b/src/content/dependencies/generateAdditionalFilesListChunk.js @@ -8,6 +8,9 @@ export default { links: file.filenames .map(filename => relation('linkAdditionalFile', file, filename)), + + artistCredit: + relation('generateArtistCredit', file.artistContribs, []), }), data: (file) => ({ @@ -19,6 +22,11 @@ export default { }), slots: { + string: { + type: 'string', + default: 'miscellaneousAdditionalFiles', + }, + showFileSizes: { type: 'boolean', }, @@ -34,9 +42,29 @@ export default { [ html.tag('summary', html.tag('span', - language.$(capsule, 'entry', { - title: - html.tag('b', data.title), + language.encapsulate(capsule, 'entry', workingCapsule => { + const workingOptions = {}; + const entryCapsule = workingCapsule; + + const titlePart = + (data.title + ? language.sanitize(data.title) + : language.$('releaseInfo', slots.string, 'entry.placeholderTitle')); + + workingOptions.title = + html.tag('b', titlePart); + + relations.artistCredit.setSlots({ + normalStringKey: + entryCapsule + '.credit', + }); + + if (!html.isBlank(relations.artistCredit)) { + workingCapsule += '.withCredit'; + workingOptions.credit = relations.artistCredit; + } + + return language.$(workingCapsule, workingOptions); }))), html.tag('ul', [ diff --git a/src/content/dependencies/generateArtistInfoPage.js b/src/content/dependencies/generateArtistInfoPage.js index ae21b361..c3ac0b9f 100644 --- a/src/content/dependencies/generateArtistInfoPage.js +++ b/src/content/dependencies/generateArtistInfoPage.js @@ -92,6 +92,15 @@ export default { flashesChunkedList: relation('generateArtistInfoPageFlashesChunkedList', artist), + sheetMusicFilesChunkedList: + relation('generateArtistInfoPageSheetMusicFilesChunkedList', artist), + + midiProjectFilesChunkedList: + relation('generateArtistInfoPageMidiProjectFilesChunkedList', artist), + + miscellaneousAdditionalFilesChunkedList: + relation('generateArtistInfoPageMiscellaneousAdditionalFilesChunkedList', artist), + commentaryChunkedList: relation('generateArtistInfoPageCommentaryChunkedList', artist, false), @@ -225,6 +234,21 @@ export default { {href: '#music-videos'}, language.$(pageCapsule, 'musicVideoList.title')), + !html.isBlank(relations.sheetMusicFilesChunkedList) && + html.tag('a', + {href: '#sheet-music-files'}, + language.$(pageCapsule, 'sheetMusicFileList.title')), + + !html.isBlank(relations.midiProjectFilesChunkedList) && + html.tag('a', + {href: '#midi-project-files'}, + language.$(pageCapsule, 'midiProjectFileList.title')), + + !html.isBlank(relations.miscellaneousAdditionalFilesChunkedList) && + html.tag('a', + {href: '#additional-files'}, + language.$(pageCapsule, 'miscellaneousAdditionalFileList.title')), + !html.isBlank(relations.flashesChunkedList) && html.tag('a', {href: '#flashes'}, @@ -358,6 +382,39 @@ export default { relations.contentHeading.clone() .slots({ tag: 'h2', + attributes: {id: 'sheet-music-files'}, + title: language.$(pageCapsule, 'sheetMusicFileList.title'), + }), + + relations.sheetMusicFilesChunkedList, + ]), + + html.tags([ + relations.contentHeading.clone() + .slots({ + tag: 'h2', + attributes: {id: 'midi-project-files'}, + title: language.$(pageCapsule, 'midiProjectFileList.title'), + }), + + relations.midiProjectFilesChunkedList, + ]), + + html.tags([ + relations.contentHeading.clone() + .slots({ + tag: 'h2', + attributes: {id: 'additional-files'}, + title: language.$(pageCapsule, 'miscellaneousAdditionalFileList.title'), + }), + + relations.miscellaneousAdditionalFilesChunkedList, + ]), + + html.tags([ + relations.contentHeading.clone() + .slots({ + tag: 'h2', attributes: {id: 'flashes'}, title: language.$(pageCapsule, 'flashList.title'), }), diff --git a/src/content/dependencies/generateArtistInfoPageAdditionalFilesChunk.js b/src/content/dependencies/generateArtistInfoPageAdditionalFilesChunk.js new file mode 100644 index 00000000..353ad047 --- /dev/null +++ b/src/content/dependencies/generateArtistInfoPageAdditionalFilesChunk.js @@ -0,0 +1,41 @@ +export default { + relations: (relation, artist, album, contribs) => ({ + template: + relation('generateArtistInfoPageChunk'), + + albumLink: + relation('linkAlbum', album), + + items: + contribs.map(contribs => + relation('generateArtistInfoPageAdditionalFilesChunkItem', + artist, + contribs)), + }), + + slots: { + string: { + type: 'string', + default: 'additionalFile', + }, + + disableStandaloneWithFiles: { + type: 'boolean', + default: false, + }, + }, + + generate: (relations, slots, {html}) => + relations.template.slots({ + mode: 'album', + link: relations.albumLink, + + list: + html.tag('ul', + relations.items + .map(item => item.slots({ + string: slots.string, + disableStandaloneWithFiles: slots.disableStandaloneWithFiles, + }))), + }), +}; diff --git a/src/content/dependencies/generateArtistInfoPageAdditionalFilesChunkItem.js b/src/content/dependencies/generateArtistInfoPageAdditionalFilesChunkItem.js new file mode 100644 index 00000000..8352edba --- /dev/null +++ b/src/content/dependencies/generateArtistInfoPageAdditionalFilesChunkItem.js @@ -0,0 +1,115 @@ +export default { + query(_artist, contribs) { + const query = {}; + + query.additionalFile = contribs[0].thing; + + query.albumOrTrack = query.additionalFile.thing; + + query.album = + (query.albumOrTrack.isAlbum + ? query.albumOrTrack + : query.albumOrTrack.album); + + return query; + }, + + relations: (relation, query, artist, _contribs) => ({ + template: + relation('generateArtistInfoPageChunkItem'), + + trackLink: + (query.albumOrTrack.isTrack + ? relation('linkTrack', query.albumOrTrack) + : null), + + artistCredit: + relation('generateArtistCredit', + query.additionalFile.artistContribs, + [artist.mockSimpleContribution]), + }), + + data: (query, _artist, contribs) => ({ + for: + (query.albumOrTrack.isAlbum + ? 'album' + : 'track'), + + title: + query.additionalFile.title, + + files: + query.additionalFile.filenames.length, + + contribAnnotationParts: + contribs.flatMap(contrib => contrib.annotationParts), + }), + + slots: { + string: { + type: 'string', + default: 'additionalFile', + }, + + disableStandaloneWithFiles: { + type: 'boolean', + default: false, + }, + }, + + generate: (data, relations, slots, {html, language}) => + relations.template.slots({ + annotation: + (data.contribAnnotationParts + ? language.formatUnitList(data.contribAnnotationParts) + : html.blank()), + + content: + language.encapsulate('artistPage.creditList.entry', entryCapsule => { + let workingCapsule = entryCapsule; + let workingOptions = {}; + + workingCapsule += '.' + data.for + '.' + slots.string; + + const additionalFileCapsule = workingCapsule; + + if (data.for === 'track') { + workingOptions.track = + relations.trackLink; + } + + if (data.title) { + relations.artistCredit.setSlots({ + normalStringKey: + additionalFileCapsule + '.credit.alongsideTitle', + }); + } else if (data.files && !slots.disableStandaloneWithFiles) { + relations.artistCredit.setSlots({ + normalStringKey: + additionalFileCapsule + '.credit.standaloneWithFiles', + + additionalStringOptions: { + files: language.countFiles(data.files, {unitOnly: true}), + }, + }); + } else { + relations.artistCredit.setSlots({ + normalStringKey: + additionalFileCapsule + '.credit', + }); + } + + if (!html.isBlank(relations.artistCredit)) { + workingCapsule += '.withCredit'; + workingOptions.credit = relations.artistCredit; + } + + if (data.title) { + workingCapsule += '.withTitle'; + workingOptions.title = language.sanitize(data.title); + } + + return language.$(workingCapsule, workingOptions); + }), + }), +}; diff --git a/src/content/dependencies/generateArtistInfoPageMidiProjectFilesChunkedList.js b/src/content/dependencies/generateArtistInfoPageMidiProjectFilesChunkedList.js new file mode 100644 index 00000000..475350a7 --- /dev/null +++ b/src/content/dependencies/generateArtistInfoPageMidiProjectFilesChunkedList.js @@ -0,0 +1,68 @@ +import {chunkByConditions, stitchArrays} from '#sugar'; +import {sortAlbumsTracksChronologically, sortContributionsChronologically} + from '#sort'; + +export default { + query(artist) { + const query = {}; + + const allContributions = [ + ...artist.midiProjectFileArtistContributions, + ]; + + const getAdditionalFile = contrib => + contrib.thing; + + const getAlbumOrTrack = contrib => + getAdditionalFile(contrib).thing; + + sortContributionsChronologically( + allContributions, + sortAlbumsTracksChronologically, + {getThing: getAlbumOrTrack}); + + const getAlbum = contrib => + (getAlbumOrTrack(contrib).isTrack + ? getAlbumOrTrack(contrib).album + : getAlbumOrTrack(contrib)); + + query.contribs = + chunkByConditions(allContributions, [ + (a, b) => getAlbum(a) !== getAlbum(b), + ]).map(contribs => + chunkByConditions(contribs, [ + (a, b) => getAdditionalFile(a) !== getAdditionalFile(b), + ])); + + query.albums = + query.contribs + .map(contribs => contribs[0][0]) + .map(contrib => getAlbum(contrib)); + + return query; + }, + + relations: (relation, query, artist) => ({ + template: + relation('generateArtistInfoPageChunkedList'), + + chunks: + stitchArrays({ + album: query.albums, + contribs: query.contribs, + }).map(({album, contribs}) => + relation('generateArtistInfoPageAdditionalFilesChunk', + artist, + album, + contribs)), + }), + + generate: (relations) => + relations.template.slots({ + chunks: + relations.chunks.map(chunk => + chunk.slots({ + string: 'midiProjectFile', + })), + }), +}; diff --git a/src/content/dependencies/generateArtistInfoPageMiscellaneousAdditionalFilesChunkedList.js b/src/content/dependencies/generateArtistInfoPageMiscellaneousAdditionalFilesChunkedList.js new file mode 100644 index 00000000..55a3119e --- /dev/null +++ b/src/content/dependencies/generateArtistInfoPageMiscellaneousAdditionalFilesChunkedList.js @@ -0,0 +1,68 @@ +import {chunkByConditions, stitchArrays} from '#sugar'; +import {sortAlbumsTracksChronologically, sortContributionsChronologically} + from '#sort'; + +export default { + query(artist) { + const query = {}; + + const allContributions = [ + ...artist.miscellaneousAdditionalFileArtistContributions, + ]; + + const getAdditionalFile = contrib => + contrib.thing; + + const getAlbumOrTrack = contrib => + getAdditionalFile(contrib).thing; + + sortContributionsChronologically( + allContributions, + sortAlbumsTracksChronologically, + {getThing: getAlbumOrTrack}); + + const getAlbum = contrib => + (getAlbumOrTrack(contrib).isTrack + ? getAlbumOrTrack(contrib).album + : getAlbumOrTrack(contrib)); + + query.contribs = + chunkByConditions(allContributions, [ + (a, b) => getAlbum(a) !== getAlbum(b), + ]).map(contribs => + chunkByConditions(contribs, [ + (a, b) => getAdditionalFile(a) !== getAdditionalFile(b), + ])); + + query.albums = + query.contribs + .map(contribs => contribs[0][0]) + .map(contrib => getAlbum(contrib)); + + return query; + }, + + relations: (relation, query, artist) => ({ + template: + relation('generateArtistInfoPageChunkedList'), + + chunks: + stitchArrays({ + album: query.albums, + contribs: query.contribs, + }).map(({album, contribs}) => + relation('generateArtistInfoPageAdditionalFilesChunk', + artist, + album, + contribs)), + }), + + generate: (relations) => + relations.template.slots({ + chunks: + relations.chunks.map(chunk => + chunk.slots({ + string: 'miscellaneousAdditionalFile', + })), + }), +}; diff --git a/src/content/dependencies/generateArtistInfoPageSheetMusicFilesChunkedList.js b/src/content/dependencies/generateArtistInfoPageSheetMusicFilesChunkedList.js new file mode 100644 index 00000000..63a6b23a --- /dev/null +++ b/src/content/dependencies/generateArtistInfoPageSheetMusicFilesChunkedList.js @@ -0,0 +1,69 @@ +import {chunkByConditions, stitchArrays} from '#sugar'; +import {sortAlbumsTracksChronologically, sortContributionsChronologically} + from '#sort'; + +export default { + query(artist) { + const query = {}; + + const allContributions = [ + ...artist.sheetMusicFileArtistContributions, + ]; + + const getAdditionalFile = contrib => + contrib.thing; + + const getAlbumOrTrack = contrib => + getAdditionalFile(contrib).thing; + + sortContributionsChronologically( + allContributions, + sortAlbumsTracksChronologically, + {getThing: getAlbumOrTrack}); + + const getAlbum = contrib => + (getAlbumOrTrack(contrib).isTrack + ? getAlbumOrTrack(contrib).album + : getAlbumOrTrack(contrib)); + + query.contribs = + chunkByConditions(allContributions, [ + (a, b) => getAlbum(a) !== getAlbum(b), + ]).map(contribs => + chunkByConditions(contribs, [ + (a, b) => getAdditionalFile(a) !== getAdditionalFile(b), + ])); + + query.albums = + query.contribs + .map(contribs => contribs[0][0]) + .map(contrib => getAlbum(contrib)); + + return query; + }, + + relations: (relation, query, artist) => ({ + template: + relation('generateArtistInfoPageChunkedList'), + + chunks: + stitchArrays({ + album: query.albums, + contribs: query.contribs, + }).map(({album, contribs}) => + relation('generateArtistInfoPageAdditionalFilesChunk', + artist, + album, + contribs)), + }), + + generate: (relations) => + relations.template.slots({ + chunks: + relations.chunks.map(chunk => + chunk.slots({ + string: 'sheetMusicFile', + disableStandaloneWithFiles: true, + })), + }), +}; diff --git a/src/content/dependencies/generateListAllAdditionalFilesChunk.js b/src/content/dependencies/generateListAllAdditionalFilesChunk.js index d68e3bc1..fea565cb 100644 --- a/src/content/dependencies/generateListAllAdditionalFilesChunk.js +++ b/src/content/dependencies/generateListAllAdditionalFilesChunk.js @@ -6,6 +6,11 @@ export default { additionalFiles .map(file => file.filenames .map(filename => relation('linkAdditionalFile', file, filename))), + + artistCredits: + additionalFiles + .map(file => + relation('generateArtistCredit', file.artistContribs, [])), }), data: (additionalFiles) => ({ @@ -42,55 +47,76 @@ export default { stitchArrays({ title: data.titles, + artistCredit: relations.artistCredits, links: relations.links, filenames: data.filenames, }).map(({ title, + artistCredit, links, filenames, }) => - language.encapsulate(pageCapsule, 'file', capsule => - (links.length === 1 - ? html.tag('li', - links[0].slots({ - content: - language.$(capsule, { - title: title, - }), - })) - - : links.length === 0 - ? html.tag('li', - language.$(capsule, 'withNoFiles', { - title: title, - })) - - : html.tag('li', {class: 'has-details'}, - html.tag('details', [ - html.tag('summary', - html.tag('span', - language.$(capsule, 'withMultipleFiles', { - title: - html.tag('b', title), - - files: - language.countAdditionalFiles( - links.length, - {unit: true}), - }))), - - html.tag('ul', - stitchArrays({ - link: links, - filename: filenames, - }).map(({link, filename}) => - html.tag('li', - link.slots({ - content: - language.$(capsule, { - title: filename, - }), - })))), - ]))))))), + language.encapsulate(pageCapsule, 'file', capsule => { + const titleLine = + language.encapsulate(capsule, workingCapsule => { + const workingOptions = {}; + + const titlePart = + (title + ? language.sanitize(title) + : language.$(capsule, 'placeholderTitle')); + + workingOptions.title = + (links.length <= 1 + ? links[0].slot('content', titlePart) + : html.tag('b', titlePart)); + + artistCredit.setSlots({ + normalStringKey: capsule + '.credit', + }); + + if (!html.isBlank(artistCredit)) { + workingCapsule += '.withCredit'; + workingOptions.credit = artistCredit; + } + + if (links.length === 0) { + workingCapsule += '.withNoFiles'; + } else if (links.length >= 2) { + workingCapsule += '.withMultipleFiles'; + workingOptions.files = + language.countFiles(links.length, {unit: true}); + } + + return language.$(workingCapsule, workingOptions); + }); + + if (links.length <= 1) { + return html.tag('li', titleLine); + } + + const summary = + html.tag('summary', + html.tag('span', titleLine)); + + const list = + html.tag('ul', + stitchArrays({ + link: links, + filename: filenames, + }).map(({link, filename}) => + html.tag('li', + link.slots({ + content: + language.$(capsule, { + title: filename, + }), + })))); + + return ( + html.tag('li', {class: 'has-details'}, + html.tag('details', [summary, list])) + ); + })))), ])), }; diff --git a/src/content/dependencies/generateTrackInfoPage.js b/src/content/dependencies/generateTrackInfoPage.js index 1a21cc72..33242b4e 100644 --- a/src/content/dependencies/generateTrackInfoPage.js +++ b/src/content/dependencies/generateTrackInfoPage.js @@ -122,7 +122,7 @@ export default { midiProjectFilesList: relation('generateAdditionalFilesList', track.midiProjectFiles), - additionalFilesList: + miscellaneousAdditionalFilesList: relation('generateAdditionalFilesList', track.additionalFiles), artistCommentarySection: @@ -219,12 +219,12 @@ export default { language.$(capsule, 'link')), })), - !html.isBlank(relations.additionalFilesList) && - language.encapsulate(capsule, 'additionalFiles.shortcut', capsule => + !html.isBlank(relations.miscellaneousAdditionalFilesList) && + language.encapsulate(capsule, 'miscellaneousAdditionalFiles.shortcut', capsule => language.$(capsule, { link: html.tag('a', - {href: '#midi-project-files'}, + {href: '#additional-files'}, language.$(capsule, 'link')), })), @@ -346,7 +346,9 @@ export default { title: language.$('releaseInfo.sheetMusicFiles.heading'), }), - relations.sheetMusicFilesList, + relations.sheetMusicFilesList.slots({ + string: 'sheetMusicFiles', + }), ]), html.tags([ @@ -355,16 +357,20 @@ export default { title: language.$('releaseInfo.midiProjectFiles.heading'), }), - relations.midiProjectFilesList, + relations.midiProjectFilesList.slots({ + string: 'midiProjectFiles', + }), ]), html.tags([ relations.contentHeading.clone().slots({ attributes: {id: 'additional-files'}, - title: language.$('releaseInfo.additionalFiles.heading'), + title: language.$('releaseInfo.miscellaneousAdditionalFiles.heading'), }), - relations.additionalFilesList, + relations.miscellaneousAdditionalFilesList.slots({ + string: 'miscellaneousAdditionalFiles', + }), ]), relations.artistCommentarySection, diff --git a/src/data/checks.js b/src/data/checks.js index 01b5cf9e..6909f011 100644 --- a/src/data/checks.js +++ b/src/data/checks.js @@ -248,6 +248,10 @@ export function filterReferenceErrors(wikiData, { find, bindFind, }) { + const additionalFileShape = { + artistContribs: '_contrib', + }; + const referenceSpec = [ ['albumData', { artistContribs: '_contrib', @@ -295,6 +299,9 @@ export function filterReferenceErrors(wikiData, { featuredTracks: 'track', }], + ['midiProjectFileData', additionalFileShape], + ['miscellaneousAdditionalFileData', additionalFileShape], + ['musicVideoData', { artistContribs: '_contrib', contributorContribs: '_contrib', @@ -304,6 +311,8 @@ export function filterReferenceErrors(wikiData, { albums: 'album', }], + ['sheetMusicFileData', additionalFileShape], + ['trackData', { artistContribs: '_contrib', contributorContribs: '_contrib', diff --git a/src/data/things/AdditionalFile.js b/src/data/things/AdditionalFile.js deleted file mode 100644 index e3f309a6..00000000 --- a/src/data/things/AdditionalFile.js +++ /dev/null @@ -1,56 +0,0 @@ -import {input} from '#composite'; -import Thing from '#thing'; -import {isString, validateArrayItems} from '#validators'; - -import {exposeConstant, exposeUpdateValueOrContinue} - from '#composite/control-flow'; -import {contentString, simpleString, thing} from '#composite/wiki-properties'; - -export class AdditionalFile extends Thing { - static [Thing.friendlyName] = `Additional File`; - - static [Thing.getPropertyDescriptors] = () => ({ - // Update & expose - - thing: thing(), - - title: simpleString(), - - description: contentString(), - - filenames: [ - exposeUpdateValueOrContinue({ - validate: input.value(validateArrayItems(isString)), - }), - - exposeConstant({ - value: input.value([]), - }), - ], - - // Expose only - - isAdditionalFile: [ - exposeConstant({ - value: input.value(true), - }), - ], - }); - - static [Thing.yamlDocumentSpec] = { - fields: { - 'Title': {property: 'title'}, - 'Description': {property: 'description'}, - 'Files': {property: 'filenames'}, - }, - }; - - get paths() { - if (!this.thing) return null; - if (!this.thing.getOwnAdditionalFilePath) return null; - - return ( - this.filenames.map(filename => - this.thing.getOwnAdditionalFilePath(this, filename))); - } -} diff --git a/src/data/things/Artist.js b/src/data/things/Artist.js index 64798527..89da3c88 100644 --- a/src/data/things/Artist.js +++ b/src/data/things/Artist.js @@ -273,6 +273,18 @@ export class Artist extends Thing { exposeDependency('#otherArtistContributions'), ], + miscellaneousAdditionalFileArtistContributions: reverseReferenceList({ + reverse: soupyReverse.input('miscellaneousAdditionalFileArtistContributionsBy'), + }), + + sheetMusicFileArtistContributions: reverseReferenceList({ + reverse: soupyReverse.input('sheetMusicFileArtistContributionsBy'), + }), + + midiProjectFileArtistContributions: reverseReferenceList({ + reverse: soupyReverse.input('midiProjectFileArtistContributionsBy'), + }), + totalDuration: [ withPropertyFromList('musicContributions', V('thing')), withPropertyFromList('#musicContributions.thing', V('isMainRelease')), diff --git a/src/data/things/Language.js b/src/data/things/Language.js index 2df58d19..5265d851 100644 --- a/src/data/things/Language.js +++ b/src/data/things/Language.js @@ -955,6 +955,7 @@ export class Language extends Thing { const countHelper = (stringKey, optionName = stringKey) => function(value, { unit = false, + unitOnly = false, blankIfZero = false, } = {}) { // Null or undefined value is blank content. @@ -967,22 +968,30 @@ const countHelper = (stringKey, optionName = stringKey) => return html.blank(); } - return this.formatString( - unit + const string = + (unitOnly + ? `count.${stringKey}.unitOnly.` + this.getUnitForm(value) + : unit ? `count.${stringKey}.withUnit.` + this.getUnitForm(value) - : `count.${stringKey}`, - {[optionName]: this.formatNumber(value)}); + : `count.${stringKey}`); + + const options = + (unitOnly + ? {} + : {[optionName]: this.formatNumber(value)}); + + return this.formatString(string, options); }; // TODO: These are hard-coded. Is there a better way? Object.assign(Language.prototype, { - countAdditionalFiles: countHelper('additionalFiles', 'files'), countAlbums: countHelper('albums'), countArtTags: countHelper('artTags', 'tags'), countArtworks: countHelper('artworks'), countCommentaryEntries: countHelper('commentaryEntries', 'entries'), countContributions: countHelper('contributions'), countDays: countHelper('days'), + countFiles: countHelper('files'), countFlashes: countHelper('flashes'), countMonths: countHelper('months'), countTimesFeatured: countHelper('timesFeatured'), diff --git a/src/data/things/Track.js b/src/data/things/Track.js index 785d0080..c47729e9 100644 --- a/src/data/things/Track.js +++ b/src/data/things/Track.js @@ -10,6 +10,7 @@ import Thing from '#thing'; import {compareKebabCase} from '#wiki-data'; import { + anyOf, is, isBoolean, isColor, @@ -36,8 +37,10 @@ import { parseDimensions, parseDuration, parseLyrics, + parseMidiProjectFiles, parseMusicVideos, parseReferencingSources, + parseSheetMusicFiles, parseURLs, } from '#yaml'; @@ -385,7 +388,10 @@ export class Track extends Thing { excludingURLs: [ exposeUpdateValueOrContinue({ - validate: input.value(isExcludingURLsReason), + validate: input.value( + anyOf( + is(false), + isExcludingURLsReason)), }), withPropertyFromObject('trackSection', V('excludingTrackURLs')), @@ -1119,12 +1125,12 @@ export class Track extends Thing { 'Sheet Music Files': { property: 'sheetMusicFiles', - transform: parseAdditionalFiles, + transform: parseSheetMusicFiles, }, 'MIDI Project Files': { property: 'midiProjectFiles', - transform: parseAdditionalFiles, + transform: parseMidiProjectFiles, }, // Content entries @@ -1184,9 +1190,9 @@ export class Track extends Thing { ], }, - {message: `Don't include URLs alongside Excluding URLs`, fields: [ + {message: `Don't include URLs alongside Excluding URLs, unless Excluding URLs is false`, fields: [ 'URLs', - 'Excluding URLs', + ['Excluding URLs', v => v !== false], ]}, ], }; @@ -1365,14 +1371,10 @@ export class Track extends Thing { }, }; - getOwnAdditionalFilePath(_file, filename) { + getOwnAdditionalFilePath(file, filename) { if (!this.album) return null; - return [ - 'media.albumAdditionalFile', - this.album.directory, - filename, - ]; + return this.album.getOwnAdditionalFilePath(file, filename); } getOwnArtworkPath(artwork) { diff --git a/src/data/things/additional-file/AdditionalFile.js b/src/data/things/additional-file/AdditionalFile.js new file mode 100644 index 00000000..d137c741 --- /dev/null +++ b/src/data/things/additional-file/AdditionalFile.js @@ -0,0 +1,109 @@ +import {inspect} from 'node:util'; + +import {colors} from '#cli'; +import {input, V} from '#composite'; +import Thing from '#thing'; +import {isString, validateArrayItems} from '#validators'; +import {parseContributors} from '#yaml'; + +import {exposeConstant, exposeUpdateValueOrContinue} + from '#composite/control-flow'; +import {contributionList, contentString, simpleString, soupyFind, thing} + from '#composite/wiki-properties'; + +export class AdditionalFile extends Thing { + static [Thing.friendlyName] = `Additional File`; + + static [Thing.getPropertyDescriptors] = () => ({ + // Update & expose + + thing: thing(), + + title: simpleString(), + + description: contentString(), + + folder: simpleString(), + + filenames: [ + exposeUpdateValueOrContinue({ + validate: input.value(validateArrayItems(isString)), + }), + + exposeConstant(V([])), + ], + + artistContribs: contributionList({ + // Subclasses override with the relevant artistProperty. + artistProperty: input.value(null), + }), + + // Update only + + find: soupyFind(), + + // Expose only + + isAdditionalFile: exposeConstant(V(true)), + + // The date property is generally expected by contributions. + // Additional files don't actually support dates, but provide a null + // value for convenience. + date: { + flags: {expose: true}, + expose: {compute: () => null}, + }, + }); + + static [Thing.yamlDocumentSpec] = { + fields: { + 'Title': {property: 'title'}, + 'Description': {property: 'description'}, + + 'Artists': { + property: 'artistContribs', + transform: parseContributors, + }, + + 'Folder': {property: 'folder'}, + 'Files': {property: 'filenames'}, + }, + }; + + get paths() { + if (!this.thing) return null; + if (!this.thing.getOwnAdditionalFilePath) return null; + + return ( + this.filenames.map(filename => + this.thing.getOwnAdditionalFilePath(this, filename))); + } + + [inspect.custom](depth, options, inspect) { + const parts = []; + + parts.push(this.constructor.name); + + if (this.title) { + parts.push(` ${colors.green(`"${this.title}"`)}`); + } + + if (this.thing) { + if (depth >= 0) { + const newOptions = { + ...options, + depth: + (options.depth === null + ? null + : options.depth - 1), + }; + + parts.push(` for ${inspect(this.thing, newOptions)}`); + } else { + parts.push(` for ${colors.blue(Thing.inspectReference(this.thing))}`); + } + } + + return parts.join(''); + } +} diff --git a/src/data/things/additional-file/MidiProjectFile.js b/src/data/things/additional-file/MidiProjectFile.js new file mode 100644 index 00000000..8e7c19ca --- /dev/null +++ b/src/data/things/additional-file/MidiProjectFile.js @@ -0,0 +1,28 @@ +import {input, V} from '#composite'; +import Thing from '#thing'; + +import {exposeConstant} from '#composite/control-flow'; +import {contributionList, soupyReverse} from '#composite/wiki-properties'; + +import {AdditionalFile} from './AdditionalFile.js'; + +export class MidiProjectFile extends AdditionalFile { + static [Thing.wikiData] = 'midiProjectFileData'; + + static [Thing.getPropertyDescriptors] = () => ({ + // Update & expose + + artistContribs: contributionList({ + artistProperty: input.value('midiProjectFileArtistContributions'), + }), + + // Expose only + + isMidiProjectFile: exposeConstant(V(true)), + }); + + static [Thing.reverseSpecs] = { + midiProjectFileArtistContributionsBy: + soupyReverse.contributionsBy('midiProjectFileData', 'artistContribs'), + }; +} diff --git a/src/data/things/additional-file/MiscellaneousAdditionalFile.js b/src/data/things/additional-file/MiscellaneousAdditionalFile.js new file mode 100644 index 00000000..0110f830 --- /dev/null +++ b/src/data/things/additional-file/MiscellaneousAdditionalFile.js @@ -0,0 +1,28 @@ +import {input, V} from '#composite'; +import Thing from '#thing'; + +import {exposeConstant} from '#composite/control-flow'; +import {contributionList, soupyReverse} from '#composite/wiki-properties'; + +import {AdditionalFile} from './AdditionalFile.js'; + +export class MiscellaneousAdditionalFile extends AdditionalFile { + static [Thing.wikiData] = 'miscellaneousAdditionalFileData'; + + static [Thing.getPropertyDescriptors] = () => ({ + // Update & expose + + artistContribs: contributionList({ + artistProperty: input.value('miscellaneousAdditionalFileArtistContributions'), + }), + + // Expose only + + isMiscellaneousAdditionalFile: exposeConstant(V(true)), + }); + + static [Thing.reverseSpecs] = { + miscellaneousAdditionalFileArtistContributionsBy: + soupyReverse.contributionsBy('miscellaneousAdditionalFileData', 'artistContribs'), + }; +} diff --git a/src/data/things/additional-file/SheetMusicFile.js b/src/data/things/additional-file/SheetMusicFile.js new file mode 100644 index 00000000..c06cde7f --- /dev/null +++ b/src/data/things/additional-file/SheetMusicFile.js @@ -0,0 +1,28 @@ +import {input, V} from '#composite'; +import Thing from '#thing'; + +import {exposeConstant} from '#composite/control-flow'; +import {contributionList, soupyReverse} from '#composite/wiki-properties'; + +import {AdditionalFile} from './AdditionalFile.js'; + +export class SheetMusicFile extends AdditionalFile { + static [Thing.wikiData] = 'sheetMusicFileData'; + + static [Thing.getPropertyDescriptors] = () => ({ + // Update & expose + + artistContribs: contributionList({ + artistProperty: input.value('sheetMusicFileArtistContributions'), + }), + + // Expose only + + isSheetMusicFile: exposeConstant(V(true)), + }); + + static [Thing.reverseSpecs] = { + sheetMusicFileArtistContributionsBy: + soupyReverse.contributionsBy('sheetMusicFileData', 'artistContribs'), + }; +} diff --git a/src/data/things/additional-file/index.js b/src/data/things/additional-file/index.js new file mode 100644 index 00000000..d8de7455 --- /dev/null +++ b/src/data/things/additional-file/index.js @@ -0,0 +1,5 @@ +export * from './AdditionalFile.js'; + +export * from './MidiProjectFile.js'; +export * from './MiscellaneousAdditionalFile.js'; +export * from './SheetMusicFile.js' diff --git a/src/data/things/album/Album.js b/src/data/things/album/Album.js index f07d552c..201aaf4e 100644 --- a/src/data/things/album/Album.js +++ b/src/data/things/album/Album.js @@ -852,12 +852,21 @@ export class Album extends Thing { ], }; - getOwnAdditionalFilePath(_file, filename) { - return [ - 'media.albumAdditionalFile', - this.directory, - filename, - ]; + getOwnAdditionalFilePath(file, filename) { + if (file.folder) { + return [ + 'media.albumAdditionalFileInFolder', + this.directory, + file.folder, + filename, + ]; + } else { + return [ + 'media.albumAdditionalFile', + this.directory, + filename, + ]; + } } getOwnArtworkPath(artwork) { diff --git a/src/data/things/album/TrackSection.js b/src/data/things/album/TrackSection.js index 1e901a09..451f8f7b 100644 --- a/src/data/things/album/TrackSection.js +++ b/src/data/things/album/TrackSection.js @@ -6,6 +6,8 @@ import Thing from '#thing'; import {parseDate, parseExcludingURLs} from '#yaml'; import { + anyOf, + is, isBoolean, isColor, isDirectory, @@ -121,7 +123,10 @@ export class TrackSection extends Thing { excludingTrackURLs: [ exposeUpdateValueOrContinue({ - validate: input.value(isExcludingURLsReason), + validate: input.value( + anyOf( + is(false), + isExcludingURLsReason)), }), withPropertyFromObject('album', V('excludingTrackURLs')), diff --git a/src/data/things/index.js b/src/data/things/index.js index 3773864b..8cd21e9d 100644 --- a/src/data/things/index.js +++ b/src/data/things/index.js @@ -1,5 +1,6 @@ // Not actually the entry point for #things - that's init.js in this folder. +export * from './additional-file/index.js'; export * from './album/index.js'; export * from './content/index.js'; export * from './contrib/index.js'; @@ -8,7 +9,6 @@ export * from './group/index.js'; export * from './homepage-layout/index.js'; export * from './sorting-rule/index.js'; -export * from './AdditionalFile.js'; export * from './AdditionalName.js'; export * from './ArtTag.js'; export * from './Artist.js'; diff --git a/src/data/yaml.js b/src/data/yaml.js index ba627b29..15d7b0ba 100644 --- a/src/data/yaml.js +++ b/src/data/yaml.js @@ -762,25 +762,44 @@ export function parseURLs(entries) { } export function parseExcludingURLs(value) { - if (typeof value !== 'string') { - return value; + if (typeof value === 'boolean') { + switch (value) { + case true: return 'generic'; + case false: return false; + // False is for nullifying an inherited reason for exclusion. + } } - switch (value) { - case 'paid bonus tracks': return 'paid bonus track'; + if (typeof value === 'string') { + switch (value) { + case 'paid bonus tracks': return 'paid bonus track'; + default: return value; + } } return value; } -export function parseAdditionalFiles(entries, {subdoc, AdditionalFile}) { +export function parseAdditionalFilesEntries(thingClass, entries, {subdoc}) { return parseArrayEntries(entries, item => { if (typeof item !== 'object') return item; - return subdoc(AdditionalFile, item, {bindInto: 'thing'}); + return subdoc(thingClass, item, {bindInto: 'thing'}); }); } +export function parseAdditionalFiles(entries, {subdoc, MiscellaneousAdditionalFile}) { + return parseAdditionalFilesEntries(MiscellaneousAdditionalFile, entries, {subdoc}); +} + +export function parseMidiProjectFiles(entries, {subdoc, MidiProjectFile}) { + return parseAdditionalFilesEntries(MidiProjectFile, entries, {subdoc}); +} + +export function parseSheetMusicFiles(entries, {subdoc, SheetMusicFile}) { + return parseAdditionalFilesEntries(SheetMusicFile, entries, {subdoc}); +} + export function parseAdditionalNames(entries, {subdoc, AdditionalName}) { return parseArrayEntries(entries, item => { if (typeof item === 'object') { @@ -1866,12 +1885,18 @@ export function linkWikiDataArrays(wikiData, {bindFind, bindReverse}) { ['lyricsData', [/* find */]], + ['midiProjectFileData', [/* find */]], + + ['miscellaneousAdditionalFileData', [/* find */]], + ['musicVideoData', [/* find */]], ['referencingSourceData', [/* find */]], ['seriesData', [/* find */]], + ['sheetMusicFileData', [/* find */]], + ['trackData', [ 'artworkData', 'wikiInfo', diff --git a/src/static/css/miscellany.css b/src/static/css/miscellany.css index eb085693..37fefb40 100644 --- a/src/static/css/miscellany.css +++ b/src/static/css/miscellany.css @@ -503,9 +503,9 @@ text-decoration: none !important; } - summary > span:hover:has(a:hover) a, - summary > span:hover:has(a.nested-hover) a, - summary.has-nested-hover > span a { + summary > span:hover:has(a:hover) a:hover, + summary > span:hover:has(a.nested-hover) a:hover, + summary.has-nested-hover > span a:hover { text-decoration: underline !important; } diff --git a/src/strings-default.yaml b/src/strings-default.yaml index 4e88ea90..18f93187 100644 --- a/src/strings-default.yaml +++ b/src/strings-default.yaml @@ -37,28 +37,28 @@ count: # Count things and objects - additionalFiles: - _: "{FILES}" + albums: + _: "{ALBUMS}" + withUnit: zero: "" - one: "{FILES} file" + one: "{ALBUMS} album" two: "" few: "" many: "" - other: "{FILES} files" + other: "{ALBUMS} albums" - albums: - _: "{ALBUMS}" - withUnit: + unitOnly: zero: "" - one: "{ALBUMS} album" + one: "album" two: "" few: "" many: "" - other: "{ALBUMS} albums" + other: "albums" artTags: _: "{TAGS}" + withUnit: zero: "" one: "{TAGS} tag" @@ -67,8 +67,17 @@ count: many: "" other: "{TAGS} tags" + unitOnly: + zero: "" + one: "tag" + two: "" + few: "" + many: "" + other: "tags" + artworks: _: "{ARTWORKS}" + withUnit: zero: "" one: "{ARTWORKS} artwork" @@ -77,8 +86,17 @@ count: many: "" other: "{ARTWORKS} artworks" + unitOnly: + zero: "" + one: "artwork" + two: "" + few: "" + many: "" + other: "artworks" + commentaryEntries: _: "{ENTRIES}" + withUnit: zero: "" one: "{ENTRIES} entry" @@ -87,8 +105,17 @@ count: many: "" other: "{ENTRIES} entries" + unitOnly: + zero: "" + one: "entry" + two: "" + few: "" + many: "" + other: "entries" + contributions: _: "{CONTRIBUTIONS}" + withUnit: zero: "" one: "{CONTRIBUTIONS} contribution" @@ -97,18 +124,55 @@ count: many: "" other: "{CONTRIBUTIONS} contributions" + unitOnly: + zero: "" + one: "contribution" + two: "" + few: "" + many: "" + other: "contributions" + + files: + _: "{FILES}" + + withUnit: + zero: "" + one: "{FILES} file" + two: "" + few: "" + many: "" + other: "{FILES} files" + + unitOnly: + zero: "" + one: "file" + two: "" + few: "" + many: "" + other: "files" + flashes: _: "{FLASHES}" + withUnit: zero: "" - one: "{FLASHES} flashes" + one: "{FLASHES} flash" two: "" few: "" many: "" other: "{FLASHES} flashes" + unitOnly: + zero: "" + one: "flash" + two: "" + few: "" + many: "" + other: "flashes" + tracks: _: "{TRACKS}" + withUnit: zero: "" one: "{TRACKS} track" @@ -117,10 +181,19 @@ count: many: "" other: "{TRACKS} tracks" + unitOnly: + zero: "" + one: "track" + two: "" + few: "" + many: "" + other: "tracks" + # Count more abstract stuff days: _: "{DAYS}" + withUnit: zero: "" one: "{DAYS} day" @@ -129,8 +202,17 @@ count: many: "" other: "{DAYS} days" + unitOnly: + zero: "" + one: "day" + two: "" + few: "" + many: "" + other: "days" + months: _: "{MONTHS}" + withUnit: zero: "" one: "{MONTHS} month" @@ -139,8 +221,17 @@ count: many: "" other: "{MONTHS} months" + unitOnly: + zero: "" + one: "month" + two: "" + few: "" + many: "" + other: "months" + timesFeatured: _: "{TIMES_FEATURED}" + withUnit: zero: "" one: "featured {TIMES_FEATURED} time" @@ -149,8 +240,17 @@ count: many: "" other: "featured {TIMES_FEATURED} times" + unitOnly: + zero: "" + one: "time featured" + two: "" + few: "" + many: "" + other: "times featured" + timesReferenced: _: "{TIMES_REFERENCED}" + withUnit: zero: "" one: "{TIMES_REFERENCED} time referenced" @@ -159,8 +259,17 @@ count: many: "" other: "{TIMES_REFERENCED} times referenced" + unitOnly: + zero: "" + one: "time referenced" + two: "" + few: "" + many: "" + other: "times referenced" + timesUsed: _: "{TIMES_USED}" + withUnit: zero: "" one: "used {TIMES_USED} time" @@ -169,8 +278,17 @@ count: many: "" other: "used {TIMES_USED} times" + unitOnly: + zero: "" + one: "time used" + two: "" + few: "" + many: "" + other: "times used" + weeks: _: "{WEEKS}" + withUnit: zero: "" one: "{WEEKS} week" @@ -179,9 +297,18 @@ count: many: "" other: "{WEEKS} weeks" + unitOnly: + zero: "" + one: "week" + two: "" + few: "" + many: "" + other: "weeks" + words: _: "{WORDS}" thousand: "{WORDS}k" + withUnit: zero: "" one: "{WORDS} word" @@ -190,8 +317,17 @@ count: many: "" other: "{WORDS} words" + unitOnly: + zero: "" + one: "word" + two: "" + few: "" + many: "" + other: "words" + years: _: "{YEARS}" + withUnit: zero: "" one: "{YEARS} year" @@ -200,6 +336,14 @@ count: many: "" other: "{YEARS} years" + unitOnly: + zero: "" + one: "year" + two: "" + few: "" + many: "" + other: "years" + # Numerical things that aren't exactly counting, per se duration: @@ -423,10 +567,14 @@ releaseInfo: link: "referencing sources" additionalFiles: - heading: "View or download additional files:" - entry: - _: "{TITLE}" + _: >- + {TITLE} + + withCredit: >- + {TITLE} {CREDIT} + + credit: "by {ARTISTS}" noFilesAvailable: >- There are no files available or listed for this entry. @@ -435,23 +583,29 @@ releaseInfo: _: "{FILE}" withSize: "{FILE} ({SIZE})" + miscellaneousAdditionalFiles: + heading: "View or download additional files:" + entry.placeholderTitle: "Additional file" + shortcut: _: "View {LINK}." link: "additional files" sheetMusicFiles: heading: "Print or download sheet music files:" + entry.placeholderTitle: "Sheet music" shortcut: _: "Download {LINK}." link: "sheet music files" midiProjectFiles: - heading: "Download MIDI/project files:" + heading: "Download MIDI & project files:" + entry.placeholderTitle: "MIDI or project file" shortcut: _: "Download {LINK}." - link: "MIDI/project files" + link: "MIDI & project files" # # trackList: @@ -1638,6 +1792,69 @@ artistPage: credit.alongsideTitle: >- by {ARTISTS} + track.miscellaneousAdditionalFile: + _: >- + {TRACK} + + withTitle: >- + {TRACK} — {TITLE} + + withCredit: >- + {TRACK}: {CREDIT} + + withCredit.withTitle: >- + {TRACK}: {TITLE} {CREDIT} + + credit: >- + files by {ARTISTS} + + credit.standaloneWithFiles: >- + {FILES} by {ARTISTS} + + credit.alongsideTitle: >- + by {ARTISTS} + + track.sheetMusicFile: + _: >- + {TRACK} + + withTitle: >- + {TRACK} — {TITLE} + + withCredit: >- + {TRACK}: {CREDIT} + + withCredit.withTitle: >- + {TRACK}: {TITLE} {CREDIT} + + credit: >- + sheet music by {ARTISTS} + + credit.alongsideTitle: >- + by {ARTISTS} + + track.midiProjectFile: + _: >- + {TRACK} + + withTitle: >- + {TRACK} — {TITLE} + + withCredit: >- + {TRACK}: {CREDIT} + + withCredit.withTitle: >- + {TRACK}: {TITLE} {CREDIT} + + credit: >- + files by {ARTISTS} + + credit.standaloneWithFiles: >- + {FILES} by {ARTISTS} + + credit.alongsideTitle: >- + by {ARTISTS} + # album: # The artist info page doesn't display if the artist is # musically credited outright for the album as a whole, @@ -1652,87 +1869,109 @@ artistPage: bannerArt: "(banner art)" commentary: "(album commentary)" - musicVideo: - _: >- - (album music video) + album.musicVideo: + _: >- + (album music video) + + withLinks: >- + (album music video: {LINKS}) + + withTitle: >- + (for album: {TITLE}) + + withTitle.withLinks: >- + (for album: {TITLE} - {LINKS}) + + withLabel: >- + (for album: {LABEL}) - withLinks: >- - (album music video: {LINKS}) + withLabel.withLinks: >- + (for album: {LABEL} - {LINKS}) - withTitle: >- - (for album: {TITLE}) + withCredit: >- + (album music video {CREDIT}) - withTitle.withLinks: >- - (for album: {TITLE} - {LINKS}) + withCredit.withLinks: >- + (album music video {CREDIT} - {LINKS}) - withLabel: >- - (for album: {LABEL}) + withCredit.withTitle: >- + (for album: {TITLE} {CREDIT}) - withLabel.withLinks: >- - (for album: {LABEL} - {LINKS}) + withCredit.withTitle.withLinks: >- + (for album: {TITLE} {CREDIT} - {LINKS}) - withCredit: >- - (album music video {CREDIT}) + withCredit.withLabel: >- + (for album: {LABEL} {CREDIT}) - withCredit.withLinks: >- - (album music video {CREDIT} - {LINKS}) + withCredit.withLabel.withLinks: >- + (for album: {LABEL} {CREDIT} - {LINKS}) - withCredit.withTitle: >- - (for album: {TITLE} {CREDIT}) + withDate: >- + ({DATE}: album music video) - withCredit.withTitle.withLinks: >- - (for album: {TITLE} {CREDIT} - {LINKS}) + withDate.withLinks: >- + ({DATE}, album music video: {LINKS}) - withCredit.withLabel: >- - (for album: {LABEL} {CREDIT}) + withDate.withTitle: >- + ({DATE}, for album: {TITLE}) - withCredit.withLabel.withLinks: >- - (for album: {LABEL} {CREDIT} - {LINKS}) + withDate.withTitle.withLinks: >- + ({DATE}, for album: {TITLE} - {LINKS}) - withDate: >- - ({DATE}: album music video) + withDate.withLabel: >- + ({DATE}, for album: {LABEL}) - withDate.withLinks: >- - ({DATE}, album music video: {LINKS}) + withDate.withLabel.withLinks: >- + ({DATE}, for album: {LABEL} - {LINKS}) - withDate.withTitle: >- - ({DATE}, for album: {TITLE}) + withDate.withCredit: >- + ({DATE}: album music video {CREDIT}) + + withDate.withCredit.withLinks: >- + ({DATE}, album music video {CREDIT} - {LINKS}) + + withDate.withCredit.withTitle: >- + ({DATE}, for album: {TITLE} {CREDIT}) + + withDate.withCredit.withTitle.withLinks: >- + ({DATE}, for album: {TITLE} {CREDIT} - {LINKS}) - withDate.withTitle.withLinks: >- - ({DATE}, for album: {TITLE} - {LINKS}) + withDate.withCredit.withLabel: >- + ({DATE}, for album: {LABEL} {CREDIT}) - withDate.withLabel: >- - ({DATE}, for album: {LABEL}) + withDate.withCredit.withLabel.withLinks: >- + ({DATE}, for album: {LABEL} {CREDIT} - {LINKS}) - withDate.withLabel.withLinks: >- - ({DATE}, for album: {LABEL} - {LINKS}) + credit: >- + by {ARTISTS} - withDate.withCredit: >- - ({DATE}: album music video {CREDIT}) + credit.alongsideLabel: >- + by {ARTISTS} - withDate.withCredit.withLinks: >- - ({DATE}, album music video {CREDIT} - {LINKS}) + credit.alongsideTitle: >- + by {ARTISTS} - withDate.withCredit.withTitle: >- - ({DATE}, for album: {TITLE} {CREDIT}) + album.miscellaneousAdditionalFile: + _: >- + (album additional files) - withDate.withCredit.withTitle.withLinks: >- - ({DATE}, for album: {TITLE} {CREDIT} - {LINKS}) + withTitle: >- + (for album: {TITLE}) - withDate.withCredit.withLabel: >- - ({DATE}, for album: {LABEL} {CREDIT}) + withCredit: >- + (for album: {CREDIT}) - withDate.withCredit.withLabel.withLinks: >- - ({DATE}, for album: {LABEL} {CREDIT} - {LINKS}) + withCredit.withTitle: >- + (for album: {TITLE} {CREDIT}) - credit: >- - by {ARTISTS} + credit: >- + files by {ARTISTS} - credit.alongsideLabel: >- - by {ARTISTS} + credit.standaloneWithFiles: >- + {FILES} by {ARTISTS} - credit.alongsideTitle: >- - by {ARTISTS} + credit.alongsideTitle: >- + by {ARTISTS} flash: "{FLASH}" @@ -1779,6 +2018,9 @@ artistPage: trackList.title: "Tracks" artList.title: "Artworks" musicVideoList.title: "Music Videos" + sheetMusicFileList.title: "Sheet Music Files" + midiProjectFileList.title: "MIDI & Project Files" + miscellaneousAdditionalFileList.title: "Additional Files" flashList.title: "Flashes" commentaryList.title: "Commentary" @@ -2741,21 +2983,53 @@ listingPage: file: _: "{TITLE}" - withMultipleFiles: "{TITLE} ({FILES})" - withNoFiles: "{TITLE} (no files)" + placeholderTitle: "Sheet music" + + withCredit: >- + {TITLE} {CREDIT} + + withMultipleFiles: >- + {TITLE} ({FILES}) + + withNoFiles: >- + {TITLE} (no files) + + withCredit.withMultipleFiles: >- + {TITLE} {CREDIT} ({FILES}) + + withCredit.withNoFiles: >- + {TITLE} {CREDIT} (no files) + + credit: "by {ARTISTS}" # other.midiProjectFiles: # Same as other.allSheetMusic, but for MIDI & project files. allMidiProjectFiles: - title: "All MIDI/Project Files" - title.short: "All MIDI/Project Files" - albumFiles: "Album MIDI/project files:" + title: "All MIDI / Project Files" + title.short: "All MIDI & Project Files" + albumFiles: "Album MIDI & project files:" file: _: "{TITLE}" - withMultipleFiles: "{TITLE} ({FILES})" - withNoFiles: "{TITLE} (no files)" + placeholderTitle: "MIDI or project file" + + withCredit: >- + {TITLE} {CREDIT} + + withMultipleFiles: >- + {TITLE} ({FILES}) + + withNoFiles: >- + {TITLE} (no files) + + withCredit.withMultipleFiles: >- + {TITLE} {CREDIT} ({FILES}) + + withCredit.withNoFiles: >- + {TITLE} {CREDIT} (no files) + + credit: "by {ARTISTS}" # other.additionalFiles: # Same as other.allSheetMusic, but for additional files. @@ -2767,8 +3041,24 @@ listingPage: file: _: "{TITLE}" - withMultipleFiles: "{TITLE} ({FILES})" - withNoFiles: "{TITLE} (no files available)" + placeholderTitle: "Additional file" + + withCredit: >- + {TITLE} {CREDIT} + + withMultipleFiles: >- + {TITLE} ({FILES}) + + withNoFiles: >- + {TITLE} (no files) + + withCredit.withMultipleFiles: >- + {TITLE} {CREDIT} ({FILES}) + + withCredit.withNoFiles: >- + {TITLE} {CREDIT} (no files) + + credit: "by {ARTISTS}" # other.randomPages: # Special listing which shows a bunch of buttons that each diff --git a/src/urls-default.yaml b/src/urls-default.yaml index 2f05b207..dfa5b0e9 100644 --- a/src/urls-default.yaml +++ b/src/urls-default.yaml @@ -125,6 +125,7 @@ media: - *genericPaths - albumAdditionalFile: 'album-additional/<>/<>' + albumAdditionalFileInFolder: 'album-additional/<>/<>/<>' albumBanner: 'album-art/<>/banner.<>' albumCover: 'album-art/<>/cover.<>' albumWallpaper: 'album-art/<>/bg.<>' |