diff options
30 files changed, 492 insertions, 836 deletions
diff --git a/package.json b/package.json index ed18fdba..d19da806 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "#composite/things/art-tag": "./src/data/composite/things/art-tag/index.js", "#composite/things/artist": "./src/data/composite/things/artist/index.js", "#composite/things/artwork": "./src/data/composite/things/artwork/index.js", + "#composite/things/commentary-entry": "./src/data/composite/things/commentary-entry/index.js", "#composite/things/contribution": "./src/data/composite/things/contribution/index.js", "#composite/things/flash": "./src/data/composite/things/flash/index.js", "#composite/things/flash-act": "./src/data/composite/things/flash-act/index.js", diff --git a/src/common-util/wiki-data.js b/src/common-util/wiki-data.js index 0aa18ddb..a4c6b3bd 100644 --- a/src/common-util/wiki-data.js +++ b/src/common-util/wiki-data.js @@ -103,10 +103,35 @@ export const commentaryRegexCaseSensitiveOneShot = new RegExp(commentaryRegexRaw); // The #validators function isOldStyleLyrics() describes -// what this regular expression detects. -export const oldStyleLyricsDetectionRegex = +// what this regular expression detects against. +export const multipleLyricsDetectionRegex = /^<i>.*:<\/i>/m; +export function matchContentEntries(sourceText) { + const matchEntries = []; + + let previousMatchEntry = null; + let previousEndIndex = null; + + for (const {0: matchText, index: startIndex, groups: matchEntry} + of sourceText.matchAll(commentaryRegexCaseSensitive)) { + if (previousMatchEntry) { + previousMatchEntry.body = sourceText.slice(previousEndIndex, startIndex); + } + + matchEntries.push(matchEntry); + + previousMatchEntry = matchEntry; + previousEndIndex = startIndex + matchText.length; + } + + if (previousMatchEntry) { + previousMatchEntry.body = sourceText.slice(previousEndIndex); + } + + return matchEntries; +} + export function filterAlbumsByCommentary(albums) { return albums .filter((album) => [album, ...album.tracks].some((x) => x.commentary)); diff --git a/src/content/dependencies/generateCommentaryEntry.js b/src/content/dependencies/generateCommentaryEntry.js index c93020f3..4cb618e3 100644 --- a/src/content/dependencies/generateCommentaryEntry.js +++ b/src/content/dependencies/generateCommentaryEntry.js @@ -98,8 +98,6 @@ export default { return language.$(workingCapsule, workingOptions); })), - - relations.date, ])), html.tag('blockquote', {class: 'commentary-entry-body'}, @@ -107,6 +105,10 @@ export default { relations.colorStyle.clone() .slot('color', slots.color), - relations.bodyContent.slot('mode', 'multiline')), + [ + relations.date, + + relations.bodyContent.slot('mode', 'multiline'), + ]), ])), }; diff --git a/src/content/dependencies/generatePageLayout.js b/src/content/dependencies/generatePageLayout.js index 070c7c82..0acf401c 100644 --- a/src/content/dependencies/generatePageLayout.js +++ b/src/content/dependencies/generatePageLayout.js @@ -583,6 +583,11 @@ export default { ` background-image: url("${to('media.path', 'bg.jpg')}");\n` + `}`); + const goshFrigginDarnitStyleRule = + `.image-media-link::after {\n` + + ` mask-image: url("${to('staticMisc.path', 'image.svg')}");\n` + + `}`; + const numWallpaperParts = html.resolve(slots.styleRules, {normalize: 'string'}) .match(/\.wallpaper-part:nth-child/g) @@ -733,6 +738,7 @@ export default { .slot('color', slots.color ?? data.wikiColor), fallbackBackgroundStyleRule, + goshFrigginDarnitStyleRule, slots.styleRules, ]), diff --git a/src/content/dependencies/generateTrackInfoPage.js b/src/content/dependencies/generateTrackInfoPage.js index ca6f82b9..11d179ad 100644 --- a/src/content/dependencies/generateTrackInfoPage.js +++ b/src/content/dependencies/generateTrackInfoPage.js @@ -311,18 +311,6 @@ export default { relations.lyricsSection, - // html.tags([ - // relations.contentHeading.clone() - // .slots({ - // attributes: {id: 'lyrics'}, - // title: language.$('releaseInfo.lyrics'), - // }), - - // html.tag('blockquote', - // {[html.onlyIfContent]: true}, - // relations.lyrics.slot('mode', 'lyrics')), - // ]), - html.tags([ relations.contentHeading.clone() .slots({ diff --git a/src/content/dependencies/listTracksWithLyrics.js b/src/content/dependencies/listTracksWithLyrics.js index a13a76f0..e6ab9d7d 100644 --- a/src/content/dependencies/listTracksWithLyrics.js +++ b/src/content/dependencies/listTracksWithLyrics.js @@ -2,7 +2,7 @@ export default { contentDependencies: ['listTracksWithExtra'], relations: (relation, spec) => - ({page: relation('listTracksWithExtra', spec, 'lyrics', 'truthy')}), + ({page: relation('listTracksWithExtra', spec, 'lyrics', 'array')}), generate: (relations) => relations.page, diff --git a/src/data/checks.js b/src/data/checks.js index 25863d2d..52024144 100644 --- a/src/data/checks.js +++ b/src/data/checks.js @@ -19,12 +19,6 @@ import { withAggregate, } from '#aggregate'; -import { - combineWikiDataArrays, - commentaryRegexCaseSensitive, - oldStyleLyricsDetectionRegex, -} from '#wiki-data'; - function inspect(value, opts = {}) { return nodeInspect(value, {colors: ENABLE_COLOR, ...opts}); } @@ -190,7 +184,8 @@ export function filterReferenceErrors(wikiData, { groups: 'group', artTags: '_artTag', referencedArtworks: '_artwork', - commentary: '_commentary', + commentary: '_content', + creditSources: '_content', }], ['artTagData', { @@ -198,7 +193,8 @@ export function filterReferenceErrors(wikiData, { }], ['flashData', { - commentary: '_commentary', + commentary: '_content', + creditSources: '_content', }], ['groupCategoryData', { @@ -238,7 +234,9 @@ export function filterReferenceErrors(wikiData, { artTags: '_artTag', referencedArtworks: '_artwork', mainReleaseTrack: '_trackMainReleasesOnly', - commentary: '_commentary', + commentary: '_content', + creditSources: '_content', + lyrics: '_content', }], ['wikiInfo', { @@ -273,12 +271,12 @@ export function filterReferenceErrors(wikiData, { let writeProperty = true; switch (findFnKey) { - case '_commentary': + case '_content': if (value) { value = - Array.from(value.matchAll(commentaryRegexCaseSensitive)) - .map(({groups}) => groups.artistReferences) - .map(text => text.split(',').map(text => text.trim())); + value.map(entry => + CacheableObject.getUpdateValue(entry, 'artists') ?? + []); } writeProperty = false; @@ -334,7 +332,7 @@ export function filterReferenceErrors(wikiData, { findFn = boundFind.artTag; break; - case '_commentary': + case '_content': findFn = findArtistOrAlias; break; @@ -466,7 +464,7 @@ export function filterReferenceErrors(wikiData, { } } - if (findFnKey === '_commentary') { + if (findFnKey === '_content') { filter( value, {message: errorMessage}, decorateErrorWithIndex(refs => @@ -573,7 +571,7 @@ export function reportContentTextErrors(wikiData, { annotation: 'commentary annotation', }; - const newStyleLyricsShape = { + const lyricsShape = { body: 'lyrics body', artistDisplayText: 'lyrics artist display text', annotation: 'lyrics annotation', @@ -625,7 +623,7 @@ export function reportContentTextErrors(wikiData, { additionalFiles: additionalFileShape, commentary: commentaryShape, creditSources: commentaryShape, - lyrics: '_lyrics', + lyrics: lyricsShape, midiProjectFiles: additionalFileShape, sheetMusicFiles: additionalFileShape, }], @@ -749,7 +747,6 @@ export function reportContentTextErrors(wikiData, { nest({message: `Content text errors in ${inspect(thing)}`}, ({nest, push}) => { for (let [property, shape] of Object.entries(propSpec)) { - const rawValue = CacheableObject.getUpdateValue(thing, property); let value = thing[property]; if (value === undefined) { @@ -761,15 +758,6 @@ export function reportContentTextErrors(wikiData, { continue; } - if (shape === '_lyrics') { - if (oldStyleLyricsDetectionRegex.test(rawValue)) { - value = rawValue; - shape = '_content'; - } else { - shape = newStyleLyricsShape; - } - } - const fieldPropertyMessage = getFieldPropertyMessage( thing.constructor[Thing.yamlDocumentSpec], diff --git a/src/data/composite/things/commentary-entry/index.js b/src/data/composite/things/commentary-entry/index.js new file mode 100644 index 00000000..091bae1a --- /dev/null +++ b/src/data/composite/things/commentary-entry/index.js @@ -0,0 +1 @@ +export {default as withWebArchiveDate} from './withWebArchiveDate.js'; diff --git a/src/data/composite/things/commentary-entry/withWebArchiveDate.js b/src/data/composite/things/commentary-entry/withWebArchiveDate.js new file mode 100644 index 00000000..3aaa4f64 --- /dev/null +++ b/src/data/composite/things/commentary-entry/withWebArchiveDate.js @@ -0,0 +1,41 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; + +export default templateCompositeFrom({ + annotation: `withWebArchiveDate`, + + outputs: ['#webArchiveDate'], + + steps: () => [ + { + dependencies: ['annotation'], + + compute: (continuation, {annotation}) => + continuation({ + ['#dateText']: + annotation + ?.match(/https?:\/\/web.archive.org\/web\/([0-9]{8,8})[0-9]*\//) + ?.[1] ?? + null, + }), + }, + + raiseOutputWithoutDependency({ + dependency: '#dateText', + output: input.value({['#webArchiveDate']: null}), + }), + + { + dependencies: ['#dateText'], + compute: (continuation, {['#dateText']: dateText}) => + continuation({ + ['#webArchiveDate']: + new Date( + dateText.slice(0, 4) + '/' + + dateText.slice(4, 6) + '/' + + dateText.slice(6, 8)), + }), + }, + ], +}); diff --git a/src/data/composite/wiki-data/index.js b/src/data/composite/wiki-data/index.js index 1d94f74b..005c68c0 100644 --- a/src/data/composite/wiki-data/index.js +++ b/src/data/composite/wiki-data/index.js @@ -11,15 +11,11 @@ export {default as inputNotFoundMode} from './inputNotFoundMode.js'; export {default as inputSoupyFind} from './inputSoupyFind.js'; export {default as inputSoupyReverse} from './inputSoupyReverse.js'; export {default as inputWikiData} from './inputWikiData.js'; -export {default as processContentEntryDates} from './processContentEntryDates.js'; export {default as withClonedThings} from './withClonedThings.js'; export {default as withConstitutedArtwork} from './withConstitutedArtwork.js'; export {default as withContributionListSums} from './withContributionListSums.js'; export {default as withCoverArtDate} from './withCoverArtDate.js'; export {default as withDirectory} from './withDirectory.js'; -export {default as withParsedCommentaryEntries} from './withParsedCommentaryEntries.js'; -export {default as withParsedContentEntries} from './withParsedContentEntries.js'; -export {default as withParsedLyricsEntries} from './withParsedLyricsEntries.js'; export {default as withRecontextualizedContributionList} from './withRecontextualizedContributionList.js'; export {default as withRedatedContributionList} from './withRedatedContributionList.js'; export {default as withResolvedAnnotatedReferenceList} from './withResolvedAnnotatedReferenceList.js'; diff --git a/src/data/composite/wiki-data/processContentEntryDates.js b/src/data/composite/wiki-data/processContentEntryDates.js deleted file mode 100644 index e418a121..00000000 --- a/src/data/composite/wiki-data/processContentEntryDates.js +++ /dev/null @@ -1,181 +0,0 @@ -import {input, templateCompositeFrom} from '#composite'; -import {stitchArrays} from '#sugar'; -import {isContentString, isString, looseArrayOf} from '#validators'; - -import {fillMissingListItems} from '#composite/data'; - -// Important note: These two kinds of inputs have the exact same shape!! -// This isn't on purpose (besides that they *are* both supposed to be strings). -// They just don't have any more particular validation, yet. - -const inputDateList = defaultDependency => - input({ - validate: looseArrayOf(isString), - defaultDependency, - }); - -const inputKindList = defaultDependency => - input.staticDependency({ - validate: looseArrayOf(isString), - defaultDependency: defaultDependency, - }); - -export default templateCompositeFrom({ - annotation: `processContentEntryDates`, - - inputs: { - annotations: input({ - validate: looseArrayOf(isContentString), - defaultDependency: '#entries.annotation', - }), - - dates: inputDateList('#entries.date'), - secondDates: inputDateList('#entries.secondDate'), - accessDates: inputDateList('#entries.accessDate'), - - dateKinds: inputKindList('#entries.dateKind'), - accessKinds: inputKindList('#entries.accessKind'), - }, - - outputs: ({ - [input.staticDependency('dates')]: dates, - [input.staticDependency('secondDates')]: secondDates, - [input.staticDependency('accessDates')]: accessDates, - [input.staticDependency('dateKinds')]: dateKinds, - [input.staticDependency('accessKinds')]: accessKinds, - }) => [ - dates ?? '#processedContentEntryDates', - secondDates ?? '#processedContentEntrySecondDates', - accessDates ?? '#processedContentEntryAccessDates', - dateKinds ?? '#processedContentEntryDateKinds', - accessKinds ?? '#processedContentEntryAccessKinds', - ], - - steps: () => [ - { - dependencies: [input('annotations')], - compute: (continuation, { - [input('annotations')]: annotations, - }) => continuation({ - ['#webArchiveDates']: - annotations - .map(text => text?.match(/https?:\/\/web.archive.org\/web\/([0-9]{8,8})[0-9]*\//)) - .map(match => match?.[1]) - .map(dateText => - (dateText - ? dateText.slice(0, 4) + '/' + - dateText.slice(4, 6) + '/' + - dateText.slice(6, 8) - : null)), - }), - }, - - { - dependencies: [input('dates')], - compute: (continuation, { - [input('dates')]: dates, - }) => continuation({ - ['#processedContentEntryDates']: - dates - .map(date => date ? new Date(date) : null), - }), - }, - - { - dependencies: [input('secondDates')], - compute: (continuation, { - [input('secondDates')]: secondDates, - }) => continuation({ - ['#processedContentEntrySecondDates']: - secondDates - .map(date => date ? new Date(date) : null), - }), - }, - - fillMissingListItems({ - list: input('dateKinds'), - fill: input.value(null), - }).outputs({ - '#list': '#processedContentEntryDateKinds', - }), - - { - dependencies: [input('accessDates'), '#webArchiveDates'], - compute: (continuation, { - [input('accessDates')]: accessDates, - ['#webArchiveDates']: webArchiveDates, - }) => continuation({ - ['#processedContentEntryAccessDates']: - stitchArrays({ - accessDate: accessDates, - webArchiveDate: webArchiveDates - }).map(({accessDate, webArchiveDate}) => - accessDate ?? - webArchiveDate ?? - null) - .map(date => date ? new Date(date) : date), - }), - }, - - { - dependencies: [input('accessKinds'), '#webArchiveDates'], - compute: (continuation, { - [input('accessKinds')]: accessKinds, - ['#webArchiveDates']: webArchiveDates, - }) => continuation({ - ['#processedContentEntryAccessKinds']: - stitchArrays({ - accessKind: accessKinds, - webArchiveDate: webArchiveDates, - }).map(({accessKind, webArchiveDate}) => - accessKind ?? - (webArchiveDate && 'captured') ?? - null), - }), - }, - - // TODO: Annoying conversion step for outputs, would be nice to avoid. - { - dependencies: [ - '#processedContentEntryDates', - '#processedContentEntrySecondDates', - '#processedContentEntryAccessDates', - '#processedContentEntryDateKinds', - '#processedContentEntryAccessKinds', - input.staticDependency('dates'), - input.staticDependency('secondDates'), - input.staticDependency('accessDates'), - input.staticDependency('dateKinds'), - input.staticDependency('accessKinds'), - ], - - compute: (continuation, { - ['#processedContentEntryDates']: processedContentEntryDates, - ['#processedContentEntrySecondDates']: processedContentEntrySecondDates, - ['#processedContentEntryAccessDates']: processedContentEntryAccessDates, - ['#processedContentEntryDateKinds']: processedContentEntryDateKinds, - ['#processedContentEntryAccessKinds']: processedContentEntryAccessKinds, - [input.staticDependency('dates')]: dates, - [input.staticDependency('secondDates')]: secondDates, - [input.staticDependency('accessDates')]: accessDates, - [input.staticDependency('dateKinds')]: dateKinds, - [input.staticDependency('accessKinds')]: accessKinds, - }) => continuation({ - [dates ?? '#processedContentEntryDates']: - processedContentEntryDates, - - [secondDates ?? '#processedContentEntrySecondDates']: - processedContentEntrySecondDates, - - [accessDates ?? '#processedContentEntryAccessDates']: - processedContentEntryAccessDates, - - [dateKinds ?? '#processedContentEntryDateKinds']: - processedContentEntryDateKinds, - - [accessKinds ?? '#processedContentEntryAccessKinds']: - processedContentEntryAccessKinds, - }), - }, - ], -}); diff --git a/src/data/composite/wiki-data/withParsedCommentaryEntries.js b/src/data/composite/wiki-data/withParsedCommentaryEntries.js deleted file mode 100644 index 6794c479..00000000 --- a/src/data/composite/wiki-data/withParsedCommentaryEntries.js +++ /dev/null @@ -1,129 +0,0 @@ -import {input, templateCompositeFrom} from '#composite'; -import {stitchArrays} from '#sugar'; -import {isCommentary} from '#validators'; -import {commentaryRegexCaseSensitive} from '#wiki-data'; - -import { - fillMissingListItems, - withFlattenedList, - withPropertiesFromList, - withUnflattenedList, -} from '#composite/data'; - -import inputSoupyFind from './inputSoupyFind.js'; -import processContentEntryDates from './processContentEntryDates.js'; -import withParsedContentEntries from './withParsedContentEntries.js'; -import withResolvedReferenceList from './withResolvedReferenceList.js'; - -export default templateCompositeFrom({ - annotation: `withParsedCommentaryEntries`, - - inputs: { - from: input({validate: isCommentary}), - }, - - outputs: ['#parsedCommentaryEntries'], - - steps: () => [ - withParsedContentEntries({ - from: input('from'), - caseSensitiveRegex: input.value(commentaryRegexCaseSensitive), - }), - - withPropertiesFromList({ - list: '#parsedContentEntryHeadings', - prefix: input.value('#entries'), - properties: input.value([ - 'artistReferences', - 'artistDisplayText', - 'annotation', - 'date', - 'secondDate', - 'dateKind', - 'accessDate', - 'accessKind', - ]), - }), - - // The artistReferences group will always have a value, since it's required - // for the line to match in the first place. - - { - dependencies: ['#entries.artistReferences'], - compute: (continuation, { - ['#entries.artistReferences']: artistReferenceTexts, - }) => continuation({ - ['#entries.artistReferences']: - artistReferenceTexts - .map(text => text.split(',').map(ref => ref.trim())), - }), - }, - - withFlattenedList({ - list: '#entries.artistReferences', - }), - - withResolvedReferenceList({ - list: '#flattenedList', - find: inputSoupyFind.input('artist'), - notFoundMode: input.value('null'), - }), - - withUnflattenedList({ - list: '#resolvedReferenceList', - }).outputs({ - '#unflattenedList': '#entries.artists', - }), - - fillMissingListItems({ - list: '#entries.artistDisplayText', - fill: input.value(null), - }), - - fillMissingListItems({ - list: '#entries.annotation', - fill: input.value(null), - }), - - processContentEntryDates(), - - { - dependencies: [ - '#entries.artists', - '#entries.artistDisplayText', - '#entries.annotation', - '#entries.date', - '#entries.secondDate', - '#entries.dateKind', - '#entries.accessDate', - '#entries.accessKind', - '#parsedContentEntryBodies', - ], - - compute: (continuation, { - ['#entries.artists']: artists, - ['#entries.artistDisplayText']: artistDisplayText, - ['#entries.annotation']: annotation, - ['#entries.date']: date, - ['#entries.secondDate']: secondDate, - ['#entries.dateKind']: dateKind, - ['#entries.accessDate']: accessDate, - ['#entries.accessKind']: accessKind, - ['#parsedContentEntryBodies']: body, - }) => continuation({ - ['#parsedCommentaryEntries']: - stitchArrays({ - artists, - artistDisplayText, - annotation, - date, - secondDate, - dateKind, - accessDate, - accessKind, - body, - }), - }), - }, - ], -}); diff --git a/src/data/composite/wiki-data/withParsedContentEntries.js b/src/data/composite/wiki-data/withParsedContentEntries.js deleted file mode 100644 index 2a9b3f6a..00000000 --- a/src/data/composite/wiki-data/withParsedContentEntries.js +++ /dev/null @@ -1,111 +0,0 @@ -import {input, templateCompositeFrom} from '#composite'; -import {stitchArrays} from '#sugar'; -import {isContentString, validateInstanceOf} from '#validators'; - -import {withPropertiesFromList} from '#composite/data'; - -export default templateCompositeFrom({ - annotation: `withParsedContentEntries`, - - inputs: { - // TODO: Is there any way to validate this input based on the *other* - // inputs proivded, i.e. regexes? This kind of just assumes the string - // has already been validated according to the form the regex expects, - // which *is* always the case (as used), but it seems a bit awkward. - from: input({validate: isContentString}), - - caseSensitiveRegex: input({ - validate: validateInstanceOf(RegExp), - }), - }, - - outputs: [ - '#parsedContentEntryHeadings', - '#parsedContentEntryBodies', - ], - - steps: () => [ - { - dependencies: [ - input('from'), - input('caseSensitiveRegex'), - ], - - compute: (continuation, { - [input('from')]: commentaryText, - [input('caseSensitiveRegex')]: caseSensitiveRegex, - }) => continuation({ - ['#rawMatches']: - Array.from(commentaryText.matchAll(caseSensitiveRegex)), - }), - }, - - withPropertiesFromList({ - list: '#rawMatches', - properties: input.value([ - '0', // The entire match as a string. - 'groups', - 'index', - ]), - }).outputs({ - '#rawMatches.0': '#rawMatches.text', - '#rawMatches.groups': '#parsedContentEntryHeadings', - '#rawMatches.index': '#rawMatches.startIndex', - }), - - { - dependencies: [ - '#rawMatches.text', - '#rawMatches.startIndex', - ], - - compute: (continuation, { - ['#rawMatches.text']: text, - ['#rawMatches.startIndex']: startIndex, - }) => continuation({ - ['#rawMatches.endIndex']: - stitchArrays({text, startIndex}) - .map(({text, startIndex}) => startIndex + text.length), - }), - }, - - { - dependencies: [ - input('from'), - '#rawMatches.startIndex', - '#rawMatches.endIndex', - ], - - compute: (continuation, { - [input('from')]: commentaryText, - ['#rawMatches.startIndex']: startIndex, - ['#rawMatches.endIndex']: endIndex, - }) => continuation({ - ['#parsedContentEntryBodies']: - stitchArrays({startIndex, endIndex}) - .map(({endIndex}, index, stitched) => - (index === stitched.length - 1 - ? commentaryText.slice(endIndex) - : commentaryText.slice( - endIndex, - stitched[index + 1].startIndex))) - .map(body => body.trim()), - }), - }, - - { - dependencies: [ - '#parsedContentEntryHeadings', - '#parsedContentEntryBodies', - ], - - compute: (continuation, { - ['#parsedContentEntryHeadings']: parsedContentEntryHeadings, - ['#parsedContentEntryBodies']: parsedContentEntryBodies, - }) => continuation({ - ['#parsedContentEntryHeadings']: parsedContentEntryHeadings, - ['#parsedContentEntryBodies']: parsedContentEntryBodies, - }) - } - ], -}); diff --git a/src/data/composite/wiki-data/withParsedLyricsEntries.js b/src/data/composite/wiki-data/withParsedLyricsEntries.js deleted file mode 100644 index d13bfbaa..00000000 --- a/src/data/composite/wiki-data/withParsedLyricsEntries.js +++ /dev/null @@ -1,157 +0,0 @@ -import {input, templateCompositeFrom} from '#composite'; -import {stitchArrays} from '#sugar'; -import {isLyrics} from '#validators'; -import {commentaryRegexCaseSensitive, oldStyleLyricsDetectionRegex} - from '#wiki-data'; - -import { - fillMissingListItems, - withFlattenedList, - withPropertiesFromList, - withUnflattenedList, -} from '#composite/data'; - -import inputSoupyFind from './inputSoupyFind.js'; -import processContentEntryDates from './processContentEntryDates.js'; -import withParsedContentEntries from './withParsedContentEntries.js'; -import withResolvedReferenceList from './withResolvedReferenceList.js'; - -function constituteLyricsEntry(text) { - return { - artists: [], - artistDisplayText: null, - annotation: null, - date: null, - secondDate: null, - dateKind: null, - accessDate: null, - accessKind: null, - body: text, - }; -} - -export default templateCompositeFrom({ - annotation: `withParsedLyricsEntries`, - - inputs: { - from: input({validate: isLyrics}), - }, - - outputs: ['#parsedLyricsEntries'], - - steps: () => [ - { - dependencies: [input('from')], - compute: (continuation, { - [input('from')]: lyrics, - }) => - (oldStyleLyricsDetectionRegex.test(lyrics) - ? continuation() - : continuation.raiseOutput({ - ['#parsedLyricsEntries']: - [constituteLyricsEntry(lyrics)], - })), - }, - - withParsedContentEntries({ - from: input('from'), - caseSensitiveRegex: input.value(commentaryRegexCaseSensitive), - }), - - withPropertiesFromList({ - list: '#parsedContentEntryHeadings', - prefix: input.value('#entries'), - properties: input.value([ - 'artistReferences', - 'artistDisplayText', - 'annotation', - 'date', - 'secondDate', - 'dateKind', - 'accessDate', - 'accessKind', - ]), - }), - - // The artistReferences group will always have a value, since it's required - // for the line to match in the first place. - - { - dependencies: ['#entries.artistReferences'], - compute: (continuation, { - ['#entries.artistReferences']: artistReferenceTexts, - }) => continuation({ - ['#entries.artistReferences']: - artistReferenceTexts - .map(text => text.split(',').map(ref => ref.trim())), - }), - }, - - withFlattenedList({ - list: '#entries.artistReferences', - }), - - withResolvedReferenceList({ - list: '#flattenedList', - find: inputSoupyFind.input('artist'), - notFoundMode: input.value('null'), - }), - - withUnflattenedList({ - list: '#resolvedReferenceList', - }).outputs({ - '#unflattenedList': '#entries.artists', - }), - - fillMissingListItems({ - list: '#entries.artistDisplayText', - fill: input.value(null), - }), - - fillMissingListItems({ - list: '#entries.annotation', - fill: input.value(null), - }), - - processContentEntryDates(), - - { - dependencies: [ - '#entries.artists', - '#entries.artistDisplayText', - '#entries.annotation', - '#entries.date', - '#entries.secondDate', - '#entries.dateKind', - '#entries.accessDate', - '#entries.accessKind', - '#parsedContentEntryBodies', - ], - - compute: (continuation, { - ['#entries.artists']: artists, - ['#entries.artistDisplayText']: artistDisplayText, - ['#entries.annotation']: annotation, - ['#entries.date']: date, - ['#entries.secondDate']: secondDate, - ['#entries.dateKind']: dateKind, - ['#entries.accessDate']: accessDate, - ['#entries.accessKind']: accessKind, - ['#parsedContentEntryBodies']: body, - }) => continuation({ - ['#parsedLyricsEntries']: - stitchArrays({ - artists, - artistDisplayText, - annotation, - date, - secondDate, - dateKind, - accessDate, - accessKind, - body, - }), - }), - }, - ], -}); diff --git a/src/data/composite/wiki-properties/commentary.js b/src/data/composite/wiki-properties/commentary.js deleted file mode 100644 index 928bbd1b..00000000 --- a/src/data/composite/wiki-properties/commentary.js +++ /dev/null @@ -1,34 +0,0 @@ -// Artist commentary! Generally present on tracks and albums. - -import {input, templateCompositeFrom} from '#composite'; -import {isCommentary} from '#validators'; - -import {exitWithoutDependency, exposeDependency} - from '#composite/control-flow'; -import {withParsedCommentaryEntries} from '#composite/wiki-data'; - -export default templateCompositeFrom({ - annotation: `commentary`, - - compose: false, - - update: { - validate: isCommentary, - }, - - steps: () => [ - exitWithoutDependency({ - dependency: input.updateValue(), - mode: input.value('falsy'), - value: input.value([]), - }), - - withParsedCommentaryEntries({ - from: input.updateValue(), - }), - - exposeDependency({ - dependency: '#parsedCommentaryEntries', - }), - ], -}); diff --git a/src/data/composite/wiki-properties/commentatorArtists.js b/src/data/composite/wiki-properties/commentatorArtists.js index c5c14769..54d3e1a5 100644 --- a/src/data/composite/wiki-properties/commentatorArtists.js +++ b/src/data/composite/wiki-properties/commentatorArtists.js @@ -7,7 +7,6 @@ import {exitWithoutDependency, exposeDependency} from '#composite/control-flow'; import {withFlattenedList, withPropertyFromList, withUniqueItemsOnly} from '#composite/data'; -import {withParsedCommentaryEntries} from '#composite/wiki-data'; export default templateCompositeFrom({ annotation: `commentatorArtists`, @@ -21,19 +20,13 @@ export default templateCompositeFrom({ value: input.value([]), }), - withParsedCommentaryEntries({ - from: 'commentary', - }), - withPropertyFromList({ - list: '#parsedCommentaryEntries', + list: 'commentary', property: input.value('artists'), - }).outputs({ - '#parsedCommentaryEntries.artists': '#artistLists', }), withFlattenedList({ - list: '#artistLists', + list: '#commentary.artists', }).outputs({ '#flattenedList': '#artists', }), diff --git a/src/data/composite/wiki-properties/index.js b/src/data/composite/wiki-properties/index.js index 892fc44a..d5e7657e 100644 --- a/src/data/composite/wiki-properties/index.js +++ b/src/data/composite/wiki-properties/index.js @@ -7,7 +7,6 @@ export {default as additionalFiles} from './additionalFiles.js'; export {default as additionalNameList} from './additionalNameList.js'; export {default as annotatedReferenceList} from './annotatedReferenceList.js'; export {default as color} from './color.js'; -export {default as commentary} from './commentary.js'; export {default as commentatorArtists} from './commentatorArtists.js'; export {default as constitutibleArtwork} from './constitutibleArtwork.js'; export {default as constitutibleArtworkList} from './constitutibleArtworkList.js'; @@ -20,7 +19,6 @@ export {default as duration} from './duration.js'; export {default as externalFunction} from './externalFunction.js'; export {default as fileExtension} from './fileExtension.js'; export {default as flag} from './flag.js'; -export {default as lyrics} from './lyrics.js'; export {default as name} from './name.js'; export {default as referenceList} from './referenceList.js'; export {default as referencedArtworkList} from './referencedArtworkList.js'; diff --git a/src/data/composite/wiki-properties/lyrics.js b/src/data/composite/wiki-properties/lyrics.js deleted file mode 100644 index eb5e524a..00000000 --- a/src/data/composite/wiki-properties/lyrics.js +++ /dev/null @@ -1,36 +0,0 @@ -// Lyrics! This comes in two styles - "old", where there's just one set of -// lyrics, or the newer/standard one, with multiple sets that are each -// annotated, credited, etc. - -import {input, templateCompositeFrom} from '#composite'; -import {isLyrics} from '#validators'; - -import {exitWithoutDependency, exposeDependency} - from '#composite/control-flow'; -import {withParsedLyricsEntries} from '#composite/wiki-data'; - -export default templateCompositeFrom({ - annotation: `lyrics`, - - compose: false, - - update: { - validate: isLyrics, - }, - - steps: () => [ - exitWithoutDependency({ - dependency: input.updateValue(), - mode: input.value('falsy'), - value: input.value([]), - }), - - withParsedLyricsEntries({ - from: input.updateValue(), - }), - - exposeDependency({ - dependency: '#parsedLyricsEntries', - }), - ], -}); diff --git a/src/data/things/album.js b/src/data/things/album.js index 4c85ddfa..8a25a8ac 100644 --- a/src/data/things/album.js +++ b/src/data/things/album.js @@ -3,6 +3,7 @@ export const DATA_ALBUM_DIRECTORY = 'album'; import * as path from 'node:path'; import {inspect} from 'node:util'; +import CacheableObject from '#cacheable-object'; import {colors} from '#cli'; import {input} from '#composite'; import {traverse} from '#node-utils'; @@ -16,7 +17,9 @@ import { parseAdditionalNames, parseAnnotatedReferences, parseArtwork, + parseCommentary, parseContributors, + parseCreditingSources, parseDate, parseDimensions, parseWallpaperParts, @@ -32,7 +35,6 @@ import {exitWithoutContribs, withDirectory, withCoverArtDate} import { additionalFiles, additionalNameList, - commentary, color, commentatorArtists, constitutibleArtwork, @@ -69,6 +71,8 @@ export class Album extends Thing { static [Thing.getPropertyDescriptors] = ({ ArtTag, Artwork, + CommentaryEntry, + CreditingSourcesEntry, Group, Track, TrackSection, @@ -204,8 +208,14 @@ export class Album extends Thing { isListedOnHomepage: flag(true), isListedInGalleries: flag(true), - commentary: commentary(), - creditSources: commentary(), + commentary: thingList({ + class: input.value(CommentaryEntry), + }), + + creditSources: thingList({ + class: input.value(CreditingSourcesEntry), + }), + additionalFiles: additionalFiles(), trackSections: thingList({ @@ -596,8 +606,15 @@ export class Album extends Thing { transform: parseDimensions, }, - 'Commentary': {property: 'commentary'}, - 'Credit Sources': {property: 'creditSources'}, + 'Commentary': { + property: 'commentary', + transform: parseCommentary, + }, + + 'Credit Sources': { + property: 'creditSources', + transform: parseCreditingSources, + }, 'Additional Files': { property: 'additionalFiles', @@ -668,7 +685,11 @@ export class Album extends Thing { const albumData = []; const trackSectionData = []; const trackData = []; + const artworkData = []; + const commentaryData = []; + const creditingSourceData = []; + const lyricsData = []; for (const {header: album, entries} of results) { const trackSections = []; @@ -715,6 +736,13 @@ export class Album extends Thing { entry.album = album; artworkData.push(...entry.trackArtworks); + commentaryData.push(...entry.commentary); + creditingSourceData.push(...entry.creditSources); + + // TODO: As exposed, Track.lyrics tries to inherit from the main + // release, which is impossible before the data's been linked. + // We just use the update value here. But it's icky! + lyricsData.push(...CacheableObject.getUpdateValue(entry, 'lyrics') ?? []); } closeCurrentTrackSection(); @@ -731,6 +759,9 @@ export class Album extends Thing { artworkData.push(album.wallpaperArtwork); } + commentaryData.push(...album.commentary); + creditingSourceData.push(...album.creditSources); + album.trackSections = trackSections; } @@ -738,7 +769,11 @@ export class Album extends Thing { albumData, trackSectionData, trackData, + artworkData, + commentaryData, + creditingSourceData, + lyricsData, }; }, diff --git a/src/data/things/content.js b/src/data/things/content.js new file mode 100644 index 00000000..7f352795 --- /dev/null +++ b/src/data/things/content.js @@ -0,0 +1,122 @@ +import {input} from '#composite'; +import find from '#find'; +import Thing from '#thing'; +import {is, isDate} from '#validators'; +import {parseDate} from '#yaml'; + +import {contentString, referenceList, simpleDate, soupyFind, thing} + from '#composite/wiki-properties'; + +import { + exposeConstant, + exposeDependencyOrContinue, + exposeUpdateValueOrContinue, + withResultOfAvailabilityCheck, +} from '#composite/control-flow'; + +import {withWebArchiveDate} from '#composite/things/commentary-entry'; + +export class ContentEntry extends Thing { + static [Thing.getPropertyDescriptors] = ({Artist}) => ({ + // Update & expose + + thing: thing(), + + artists: referenceList({ + class: input.value(Artist), + find: soupyFind.input('artist'), + }), + + artistText: contentString(), + + annotation: contentString(), + + dateKind: { + flags: {update: true, expose: true}, + + update: { + validate: is(...[ + 'sometime', + 'throughout', + 'around', + ]), + }, + }, + + accessKind: [ + exposeUpdateValueOrContinue({ + validate: input.value( + is(...[ + 'captured', + 'accessed', + ])), + }), + + withWebArchiveDate(), + + withResultOfAvailabilityCheck({ + from: '#webArchiveDate', + }), + + { + dependencies: ['#availability'], + compute: (continuation, {['#availability']: availability}) => + (availability + ? continuation.exit('captured') + : continuation()), + }, + + exposeConstant({ + value: input.value(null), + }), + ], + + date: simpleDate(), + + secondDate: simpleDate(), + + accessDate: [ + exposeUpdateValueOrContinue({ + validate: input.value(isDate), + }), + + withWebArchiveDate(), + + exposeDependencyOrContinue({ + dependency: '#webArchiveDate', + }), + + exposeConstant({ + value: input.value(null), + }), + ], + + body: contentString(), + + // Update only + + find: soupyFind(), + }); + + static [Thing.yamlDocumentSpec] = { + fields: { + 'Artists': {property: 'artists'}, + 'Artist Text': {property: 'artistText'}, + + 'Annotation': {property: 'annotation'}, + + 'Date Kind': {property: 'dateKind'}, + 'Access Kind': {property: 'accessKind'}, + + 'Date': {property: 'date', transform: parseDate}, + 'Second Date': {property: 'secondDate', transform: parseDate}, + 'Access Date': {property: 'accessDate', transform: parseDate}, + + 'Body': {property: 'body'}, + }, + }; +} + +export class CommentaryEntry extends ContentEntry {} +export class LyricsEntry extends ContentEntry {} +export class CreditingSourcesEntry extends ContentEntry {} diff --git a/src/data/things/flash.js b/src/data/things/flash.js index ace18af9..dac674dd 100644 --- a/src/data/things/flash.js +++ b/src/data/things/flash.js @@ -10,7 +10,9 @@ import {anyOf, isColor, isContentString, isDirectory, isNumber, isString} import { parseArtwork, parseAdditionalNames, + parseCommentary, parseContributors, + parseCreditingSources, parseDate, parseDimensions, } from '#yaml'; @@ -27,7 +29,6 @@ import { import { additionalNameList, color, - commentary, commentatorArtists, constitutibleArtwork, contentString, @@ -41,6 +42,7 @@ import { soupyFind, soupyReverse, thing, + thingList, urls, wikiData, } from '#composite/wiki-properties'; @@ -52,6 +54,8 @@ export class Flash extends Thing { static [Thing.referenceType] = 'flash'; static [Thing.getPropertyDescriptors] = ({ + CommentaryEntry, + CreditingSourcesEntry, Track, FlashAct, WikiInfo, @@ -125,8 +129,13 @@ export class Flash extends Thing { additionalNames: additionalNameList(), - commentary: commentary(), - creditSources: commentary(), + commentary: thingList({ + class: input.value(CommentaryEntry), + }), + + creditSources: thingList({ + class: input.value(CreditingSourcesEntry), + }), // Update only @@ -240,8 +249,15 @@ export class Flash extends Thing { transform: parseContributors, }, - 'Commentary': {property: 'commentary'}, - 'Credit Sources': {property: 'creditSources'}, + 'Commentary': { + property: 'commentary', + transform: parseCommentary, + }, + + 'Credit Sources': { + property: 'creditSources', + transform: parseCreditingSources, + }, 'Review Points': {ignore: true}, }, @@ -441,8 +457,18 @@ export class FlashSide extends Thing { const flashSideData = results.filter(x => x instanceof FlashSide); const artworkData = flashData.map(flash => flash.coverArtwork); - - return {flashData, flashActData, flashSideData, artworkData}; + const commentaryData = flashData.flatMap(flash => flash.commentary); + const creditingSourceData = flashData.flatMap(flash => flash.creditSources); + + return { + flashData, + flashActData, + flashSideData, + + artworkData, + commentaryData, + creditingSourceData, + }; }, sort({flashData}) { diff --git a/src/data/things/index.js b/src/data/things/index.js index 96cec88e..b832ab75 100644 --- a/src/data/things/index.js +++ b/src/data/things/index.js @@ -13,6 +13,7 @@ import * as albumClasses from './album.js'; import * as artTagClasses from './art-tag.js'; import * as artistClasses from './artist.js'; import * as artworkClasses from './artwork.js'; +import * as contentClasses from './content.js'; import * as contributionClasses from './contribution.js'; import * as flashClasses from './flash.js'; import * as groupClasses from './group.js'; @@ -29,6 +30,7 @@ const allClassLists = { 'art-tag.js': artTagClasses, 'artist.js': artistClasses, 'artwork.js': artworkClasses, + 'content.js': contentClasses, 'contribution.js': contributionClasses, 'flash.js': flashClasses, 'group.js': groupClasses, diff --git a/src/data/things/track.js b/src/data/things/track.js index bcf84aa8..ae7be170 100644 --- a/src/data/things/track.js +++ b/src/data/things/track.js @@ -12,10 +12,13 @@ import { parseAdditionalNames, parseAnnotatedReferences, parseArtwork, + parseCommentary, parseContributors, + parseCreditingSources, parseDate, parseDimensions, parseDuration, + parseLyrics, } from '#yaml'; import {withPropertyFromObject} from '#composite/data'; @@ -37,7 +40,6 @@ import { import { additionalFiles, additionalNameList, - commentary, commentatorArtists, constitutibleArtworkList, contentString, @@ -46,7 +48,6 @@ import { directory, duration, flag, - lyrics, name, referenceList, referencedArtworkList, @@ -57,6 +58,7 @@ import { soupyFind, soupyReverse, thing, + thingList, urls, wikiData, } from '#composite/wiki-properties'; @@ -87,7 +89,10 @@ export class Track extends Thing { Album, ArtTag, Artwork, + CommentaryEntry, + CreditingSourcesEntry, Flash, + LyricsEntry, TrackSection, WikiInfo, }) => ({ @@ -216,12 +221,23 @@ export class Track extends Thing { dimensions(), ], - commentary: commentary(), - creditSources: commentary(), + commentary: thingList({ + class: input.value(CommentaryEntry), + }), + + creditSources: thingList({ + class: input.value(CreditingSourcesEntry), + }), lyrics: [ + // TODO: Inherited lyrics are literally the same objects, so of course + // their .thing properties aren't going to point back to this one, and + // certainly couldn't be recontextualized... inheritFromMainRelease(), - lyrics(), + + thingList({ + class: input.value(LyricsEntry), + }), ], additionalFiles: additionalFiles(), @@ -481,9 +497,20 @@ export class Track extends Thing { 'Always Reference By Directory': {property: 'alwaysReferenceByDirectory'}, - 'Lyrics': {property: 'lyrics'}, - 'Commentary': {property: 'commentary'}, - 'Credit Sources': {property: 'creditSources'}, + 'Lyrics': { + property: 'lyrics', + transform: parseLyrics, + }, + + 'Commentary': { + property: 'commentary', + transform: parseCommentary, + }, + + 'Credit Sources': { + property: 'creditSources', + transform: parseCreditingSources, + }, 'Additional Files': { property: 'additionalFiles', diff --git a/src/data/yaml.js b/src/data/yaml.js index 50317238..79602faa 100644 --- a/src/data/yaml.js +++ b/src/data/yaml.js @@ -11,6 +11,7 @@ import {colors, ENABLE_COLOR, logInfo, logWarn} from '#cli'; import {sortByName} from '#sort'; import Thing from '#thing'; import thingConstructors from '#things'; +import {matchContentEntries, multipleLyricsDetectionRegex} from '#wiki-data'; import { aggregateThrows, @@ -824,6 +825,73 @@ export function parseArtwork({ return transform; } +export function parseContentEntries(thingClass, sourceText, {subdoc}) { + const map = matchEntry => ({ + 'Artists': + matchEntry.artistReferences + .split(',') + .map(ref => ref.trim()), + + 'Artist Text': + matchEntry.artistDisplayText, + + 'Annotation': + matchEntry.annotation, + + 'Date': + matchEntry.date, + + 'Second Date': + matchEntry.secondDate, + + 'Date Kind': + matchEntry.dateKind, + + 'Access Date': + matchEntry.accessDate, + + 'Access Kind': + matchEntry.accessKind, + + 'Body': + matchEntry.body, + }); + + const documents = + matchContentEntries(sourceText) + .map(matchEntry => + withEntries( + map(matchEntry), + entries => entries + .filter(([key, value]) => + value !== undefined && + value !== null))); + + const subdocs = + documents.map(document => + subdoc(thingClass, document, {bindInto: 'thing'})); + + return subdocs; +} + +export function parseCommentary(sourceText, {subdoc, CommentaryEntry}) { + return parseContentEntries(CommentaryEntry, sourceText, {subdoc}); +} + +export function parseCreditingSources(sourceText, {subdoc, CreditingSourcesEntry}) { + return parseContentEntries(CreditingSourcesEntry, sourceText, {subdoc}); +} + +export function parseLyrics(sourceText, {subdoc, LyricsEntry}) { + if (!multipleLyricsDetectionRegex.test(sourceText)) { + const document = {'Body': sourceText}; + + return [subdoc(LyricsEntry, document, {bindInto: 'thing'})]; + } + + return parseContentEntries(LyricsEntry, sourceText, {subdoc}); +} + // documentModes: Symbols indicating sets of behavior for loading and processing // data files. export const documentModes = { @@ -1499,6 +1567,10 @@ export function linkWikiDataArrays(wikiData, {bindFind, bindReverse}) { ['artworkData', ['artworkData']], + ['commentaryData', [/* find */]], + + ['creditingSourceData', [/* find */]], + ['flashData', [ 'wikiInfo', ]], @@ -1513,6 +1585,8 @@ export function linkWikiDataArrays(wikiData, {bindFind, bindReverse}) { ['homepageLayout.sections.rows', [/* find */]], + ['lyricsData', [/* find */]], + ['trackData', [ 'artworkData', 'trackData', @@ -1781,14 +1855,16 @@ export function flattenThingLayoutToDocumentOrder(layout) { } export function* splitDocumentsInYAMLSourceText(sourceText) { - const dividerRegex = /^-{3,}\n?/gm; + // Not multiline! + const dividerRegex = /(?:\r\n|\n|^)-{3,}(?:\r\n|\n|$)/g; + let previousDivider = ''; while (true) { const {lastIndex} = dividerRegex; const match = dividerRegex.exec(sourceText); if (match) { - const nextDivider = match[0].trim(); + const nextDivider = match[0]; yield { previousDivider, @@ -1799,11 +1875,12 @@ export function* splitDocumentsInYAMLSourceText(sourceText) { previousDivider = nextDivider; } else { const nextDivider = ''; + const lineBreak = previousDivider.match(/\r?\n/)?.[0] ?? ''; yield { previousDivider, nextDivider, - text: sourceText.slice(lastIndex).replace(/(?<!\n)$/, '\n'), + text: sourceText.slice(lastIndex).replace(/(?<!\n)$/, lineBreak), }; return; @@ -1829,7 +1906,7 @@ export function recombineDocumentsIntoYAMLSourceText(documents) { for (const document of documents) { if (sourceText) { - sourceText += divider + '\n'; + sourceText += divider; } sourceText += document.text; diff --git a/src/static/css/site.css b/src/static/css/site.css index a4139624..0a7e36ae 100644 --- a/src/static/css/site.css +++ b/src/static/css/site.css @@ -931,7 +931,11 @@ a .normal-content { background-color: var(--primary-color); - mask-image: url(/static-4p1/misc/image.svg); + /* mask-image is set in content JavaScript, + * because we can't identify the correct nor + * absolute path to the file from CSS. + */ + mask-repeat: no-repeat; mask-position: calc(100% - 2px); vertical-align: text-bottom; @@ -1119,7 +1123,7 @@ a .normal-content { font-size: 0.9rem; } -li:not(:first-child:last-child) .tooltip, +li:not(:first-child:last-child) .tooltip:where(:not(.cover-artwork .tooltip)), .offset-tooltips > :not(:first-child:last-child) .tooltip { left: 14px; } @@ -1161,7 +1165,7 @@ li:not(:first-child:last-child) .tooltip, .thing-name-tooltip, .wiki-edits-tooltip { padding: 3px 4px 2px 2px; - left: -6px !important; + left: -6px; } .thing-name-tooltip .tooltip-content, @@ -1169,11 +1173,15 @@ li:not(:first-child:last-child) .tooltip, font-size: 0.85em; } -/* Terrifying? - * https://stackoverflow.com/a/64424759/4633828 - */ -.thing-name-tooltip { margin-right: -120px; } -.wiki-edits-tooltip { margin-right: -200px; } +.thing-name-tooltip .tooltip-content { + width: max-content; + max-width: 120px; +} + +.wiki-edits-tooltip .tooltip-content { + width: max-content; + max-width: 200px; +} .contribution-tooltip .tooltip-content { padding: 6px 2px 2px 2px; @@ -1548,14 +1556,13 @@ p.image-details.origin-details { margin: 0; } +/* p.content-heading:has(+ .commentary-entry-heading.dated) { clear: right; } +*/ .commentary-entry-heading { - display: flex; - flex-direction: row; - margin-left: 15px; padding-left: 5px; max-width: 625px; @@ -1565,7 +1572,7 @@ p.content-heading:has(+ .commentary-entry-heading.dated) { } .commentary-entry-heading-text { - flex-grow: 1; + display: block; padding-left: 1.25ch; text-indent: -1.25ch; } @@ -1574,20 +1581,6 @@ p.content-heading:has(+ .commentary-entry-heading.dated) { font-style: oblique; } -.commentary-entry-heading .commentary-date { - flex-shrink: 0; - - margin-left: 0.75ch; - align-self: flex-end; - - padding-left: 0.5ch; - padding-right: 0.25ch; -} - -.commentary-entry-heading .hoverable { - box-shadow: 1px 2px 6px 5px #04040460; -} - .commentary-entry-body summary { list-style-position: outside; } @@ -1596,6 +1589,19 @@ p.content-heading:has(+ .commentary-entry-heading.dated) { color: var(--primary-color); } +.commentary-date { + float: right; + margin-top: -0.8em; + margin-left: 0.75ch; + padding-left: 0.5ch; + padding-right: 0.4em; + font-size: 0.9em; +} + +.commentary-date .hoverable { + box-shadow: 1px 2px 6px 5px #04040460; +} + .commentary-art { float: right; width: 30%; @@ -1848,7 +1854,7 @@ li .by a { display: inline-block; } -p code { +p code, li code { font-size: 0.95em; font-family: "courier new", monospace; font-weight: 800; @@ -3495,12 +3501,12 @@ main.long-content .content-sticky-heading-container .content-sticky-subheading-r max-width: 375px; } - html[data-url-key="localized.home"] #page-container.showing-sidebar-left .grid-listing > .grid-item:not(:nth-child(n+10)) { + html[data-url-key="localized.home"] #page-container.showing-sidebar-left .grid-listing > .grid-item:not(:nth-child(n+7)) { flex-basis: 23%; margin: 15px; } - html[data-url-key="localized.home"] #page-container.showing-sidebar-left .grid-listing > .grid-item:nth-child(n+10) { + html[data-url-key="localized.home"] #page-container.showing-sidebar-left .grid-listing > .grid-item:nth-child(n+7) { flex-basis: 18%; margin: 10px; } diff --git a/src/static/js/client/index.js b/src/static/js/client/index.js index b2343f07..81ea3415 100644 --- a/src/static/js/client/index.js +++ b/src/static/js/client/index.js @@ -15,7 +15,6 @@ import * as hoverableTooltipModule from './hoverable-tooltip.js'; import * as imageOverlayModule from './image-overlay.js'; import * as intrapageDotSwitcherModule from './intrapage-dot-switcher.js'; import * as liveMousePositionModule from './live-mouse-position.js'; -import * as lyricsSwitcherModule from './lyrics-switcher.js'; import * as quickDescriptionModule from './quick-description.js'; import * as scriptedLinkModule from './scripted-link.js'; import * as sidebarSearchModule from './sidebar-search.js'; @@ -38,7 +37,6 @@ export const modules = [ imageOverlayModule, intrapageDotSwitcherModule, liveMousePositionModule, - lyricsSwitcherModule, quickDescriptionModule, scriptedLinkModule, sidebarSearchModule, diff --git a/src/static/js/client/lyrics-switcher.js b/src/static/js/client/lyrics-switcher.js deleted file mode 100644 index b350ea50..00000000 --- a/src/static/js/client/lyrics-switcher.js +++ /dev/null @@ -1,70 +0,0 @@ -/* eslint-env browser */ - -import {stitchArrays} from '../../shared-util/sugar.js'; - -import {cssProp} from '../client-util.js'; - -export const info = { - id: 'lyricsSwitcherInfo', - - entries: null, - switchLinks: null, - currentLinks: null, -}; - -export function getPageReferences() { - const content = document.getElementById('content'); - - if (!content) return; - - const switcher = content.querySelector('.lyrics-switcher'); - - if (!switcher) return; - - info.entries = - Array.from(content.querySelectorAll('.lyrics-entry')); - - info.currentLinks = - Array.from(switcher.querySelectorAll('a.current')); - - info.switchLinks = - Array.from(switcher.querySelectorAll('a:not(.current)')); -} - -export function addPageListeners() { - if (!info.switchLinks) return; - - for (const {switchLink, entry} of stitchArrays({ - switchLink: info.switchLinks, - entry: info.entries, - })) { - switchLink.addEventListener('click', domEvent => { - domEvent.preventDefault(); - showLyricsEntry(entry); - }); - } -} - -function showLyricsEntry(entry) { - const entryToShow = entry; - - stitchArrays({ - entry: info.entries, - currentLink: info.currentLinks, - switchLink: info.switchLinks, - }).forEach(({ - entry, - currentLink, - switchLink, - }) => { - if (entry === entryToShow) { - cssProp(entry, 'display', null); - cssProp(currentLink, 'display', null); - cssProp(switchLink, 'display', 'none'); - } else { - cssProp(entry, 'display', 'none'); - cssProp(currentLink, 'display', 'none'); - cssProp(switchLink, 'display', null); - } - }); -} diff --git a/src/static/js/rectangles.js b/src/static/js/rectangles.js index cdab2cb8..b00ed98e 100644 --- a/src/static/js/rectangles.js +++ b/src/static/js/rectangles.js @@ -510,4 +510,46 @@ export class WikiRect extends DOMRect { height: this.height, }); } + + // Other utilities + + #display = null; + + display() { + if (!this.#display) { + this.#display = document.createElement('div'); + document.body.appendChild(this.#display); + } + + Object.assign(this.#display.style, { + position: 'fixed', + background: '#000c', + border: '3px solid var(--primary-color)', + borderRadius: '4px', + top: this.top + 'px', + left: this.left + 'px', + width: this.width + 'px', + height: this.height + 'px', + pointerEvents: 'none', + }); + + let i = 0; + const int = setInterval(() => { + i++; + if (i >= 3) clearInterval(int); + if (!this.#display) return; + + this.#display.style.display = 'none'; + setTimeout(() => { + this.#display.style.display = ''; + }, 200); + }, 600); + } + + hide() { + if (this.#display) { + this.#display.remove(); + this.#display = null; + } + } } diff --git a/src/urls-default.yaml b/src/urls-default.yaml index c3bf89eb..74225efd 100644 --- a/src/urls-default.yaml +++ b/src/urls-default.yaml @@ -11,7 +11,7 @@ yamlAliases: # part of a build. This is so that multiple builds of a wiki can coexist # served from the same server / file system root: older builds' HTML files # refer to earlier values of STATIC_VERSION, avoiding name collisions. - - &staticVersion 4p1 + - &staticVersion 5p1 data: prefix: 'data/' diff --git a/src/validators.js b/src/validators.js index 6badc93a..5b8227fb 100644 --- a/src/validators.js +++ b/src/validators.js @@ -7,7 +7,7 @@ import {cut, empty, matchMultiline, typeAppearance} from '#sugar'; import { commentaryRegexCaseInsensitive, commentaryRegexCaseSensitiveOneShot, - oldStyleLyricsDetectionRegex, + multipleLyricsDetectionRegex, } from '#wiki-data'; function inspect(value) { @@ -375,7 +375,7 @@ export const isCommentary = export function isOldStyleLyrics(content) { isContentString(content); - if (oldStyleLyricsDetectionRegex.test(content)) { + if (multipleLyricsDetectionRegex.test(content)) { throw new TypeError( `Expected old-style lyrics block not to include "<i> ... :</i>" at start of any line`); } |