From 212c5fe9972dad3c53826c0364376a98b6c5a3c3 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 26 Feb 2023 17:43:15 -0400 Subject: new 'Sheet Music Files' & 'MIDI Project Files' fields --- src/data/things/thing.js | 4 +++ src/data/things/track.js | 2 ++ src/data/yaml.js | 4 +++ src/misc-templates.js | 65 ++++++++++++++++-------------------------- src/page/album.js | 53 ++++++++++++++++++++++++++-------- src/page/track.js | 74 ++++++++++++++++++++++++++++++++++++++++++++++++ src/strings-default.json | 8 +++++- src/upd8.js | 6 +++- 8 files changed, 162 insertions(+), 54 deletions(-) diff --git a/src/data/things/thing.js b/src/data/things/thing.js index b9fa60c..5ab15c0 100644 --- a/src/data/things/thing.js +++ b/src/data/things/thing.js @@ -148,6 +148,10 @@ export default class Thing extends CacheableObject { additionalFiles: () => ({ flags: {update: true, expose: true}, update: {validate: isAdditionalFileList}, + expose: { + transform: (additionalFiles) => + additionalFiles ?? [], + }, }), // A reference list! Keep in mind this is for general references to wiki diff --git a/src/data/things/track.js b/src/data/things/track.js index 6b1e958..aecc876 100644 --- a/src/data/things/track.js +++ b/src/data/things/track.js @@ -98,6 +98,8 @@ export class Track extends Thing { commentary: Thing.common.commentary(), lyrics: Thing.common.simpleString(), additionalFiles: Thing.common.additionalFiles(), + sheetMusicFiles: Thing.common.additionalFiles(), + midiProjectFiles: Thing.common.additionalFiles(), // Update only diff --git a/src/data/yaml.js b/src/data/yaml.js index 9c3a4b8..01cb550 100644 --- a/src/data/yaml.js +++ b/src/data/yaml.js @@ -251,6 +251,8 @@ export const processTrackDocument = makeProcessDocument(T.Track, { 'Cover Artists': parseContributors, 'Additional Files': parseAdditionalFiles, + 'Sheet Music Files': parseAdditionalFiles, + 'MIDI Project Files': parseAdditionalFiles, }, propertyFieldMapping: { @@ -278,6 +280,8 @@ export const processTrackDocument = makeProcessDocument(T.Track, { lyrics: 'Lyrics', additionalFiles: 'Additional Files', + sheetMusicFiles: 'Sheet Music Files', + midiProjectFiles: 'MIDI Project Files', }, }); diff --git a/src/misc-templates.js b/src/misc-templates.js index 8a61bf7..1c6dda5 100644 --- a/src/misc-templates.js +++ b/src/misc-templates.js @@ -49,46 +49,31 @@ function unbound_generateAdditionalFilesList(additionalFiles, { }) { if (empty(additionalFiles)) return []; - const fileCount = additionalFiles.flatMap((g) => g.files).length; - - return html.fragment([ - html.tag('p', - { - id: 'additional-files', - class: ['content-heading'], - }, - language.$('releaseInfo.additionalFiles.heading', { - additionalFiles: language.countAdditionalFiles(fileCount, { - unit: true, - }), - })), - - html.tag('dl', - additionalFiles.flatMap(({title, description, files}) => [ - html.tag('dt', - (description - ? language.$('releaseInfo.additionalFiles.entry.withDescription', { - title, - description, - }) - : language.$('releaseInfo.additionalFiles.entry', {title}))), - - html.tag('dd', - html.tag('ul', - files.map((file) => { - const size = getFileSize(file); - return html.tag('li', - (size - ? language.$('releaseInfo.additionalFiles.file.withSize', { - file: linkFile(file), - size: language.formatFileSize(size), - }) - : language.$('releaseInfo.additionalFiles.file', { - file: linkFile(file), - }))); - }))), - ])), - ]); + return html.tag('dl', + additionalFiles.flatMap(({title, description, files}) => [ + html.tag('dt', + (description + ? language.$('releaseInfo.additionalFiles.entry.withDescription', { + title, + description, + }) + : language.$('releaseInfo.additionalFiles.entry', {title}))), + + html.tag('dd', + html.tag('ul', + files.map((file) => { + const size = (getFileSize && getFileSize(file)); + return html.tag('li', + (size + ? language.$('releaseInfo.additionalFiles.file.withSize', { + file: linkFile(file), + size: language.formatFileSize(size), + }) + : language.$('releaseInfo.additionalFiles.file', { + file: linkFile(file), + }))) + }))), + ])); } // Artist strings diff --git a/src/page/album.js b/src/page/album.js index 897e511..f4578ac 100644 --- a/src/page/album.js +++ b/src/page/album.js @@ -50,6 +50,8 @@ export function write(album, {wikiData}) { }; const hasAdditionalFiles = !empty(album.additionalFiles); + const numAdditionalFiles = album.additionalFiles.flatMap((g) => g.files).length; + const albumDuration = getTotalDuration(album.tracks); const displayTrackSections = @@ -336,18 +338,22 @@ export function write(album, {wikiData}) { ]), ...html.fragment( - hasAdditionalFiles && - generateAdditionalFilesList(album.additionalFiles, { - // TODO: Kinda near the metal here... - getFileSize: (file) => - getSizeOfAdditionalFile( - urls.from('media.root').to( - 'media.albumAdditionalFile', - album.directory, - file)), - linkFile: (file) => - link.albumAdditionalFile({album, file}), - })), + hasAdditionalFiles && [ + html.tag('p', + {id: 'additional-files', class: ['content-heading']}, + language.$('releaseInfo.additionalFiles.heading', { + additionalFiles: language.countAdditionalFiles(numAdditionalFiles, { + unit: true, + }), + })), + + generateAlbumAdditionalFilesList(album, album.additionalFiles, { + generateAdditionalFilesList, + getSizeOfAdditionalFile, + link, + urls, + }), + ]), ...html.fragment( album.commentary && [ @@ -834,3 +840,26 @@ export function generateAlbumChronologyLinks(album, currentTrack, { })), ]); } + +export function generateAlbumAdditionalFilesList(album, additionalFiles, { + fileSize = true, + + generateAdditionalFilesList, + getSizeOfAdditionalFile, + link, + urls, +}) { + return generateAdditionalFilesList(additionalFiles, { + getFileSize: + (fileSize + ? (file) => + // TODO: Kinda near the metal here... + getSizeOfAdditionalFile( + urls + .from('media.root') + .to('media.albumAdditionalFile', album.directory, file)) + : () => null), + linkFile: (file) => + link.albumAdditionalFile({album, file}), + }); +} diff --git a/src/page/track.js b/src/page/track.js index caba366..b9038ba 100644 --- a/src/page/track.js +++ b/src/page/track.js @@ -5,6 +5,7 @@ import { generateAlbumNavLinks, generateAlbumSecondaryNav, generateAlbumSidebar, + generateAlbumAdditionalFilesList as unbound_generateAlbumAdditionalFilesList, } from './album.js'; import { @@ -73,6 +74,11 @@ export function write(track, {wikiData}) { const hasCommentary = track.commentary || otherReleases.some((t) => t.commentary); + const hasAdditionalFiles = !empty(track.additionalFiles); + const hasSheetMusicFiles = !empty(track.sheetMusicFiles); + const hasMidiProjectFiles = !empty(track.midiProjectFiles); + const numAdditionalFiles = album.additionalFiles.flatMap((g) => g.files).length; + const generateCommentary = ({language, link, transformMultiline}) => transformMultiline([ track.commentary, @@ -161,12 +167,15 @@ export function write(track, {wikiData}) { page: ({ absoluteTo, fancifyURL, + generateAdditionalFilesList, + generateAdditionalFilesShortcut, generateChronologyLinks, generateNavigationLinks, generateTrackListDividedByGroups, getAlbumStylesheet, getArtistString, getLinkThemeString, + getSizeOfAdditionalFile, getThemeString, getTrackCover, html, @@ -184,6 +193,14 @@ export function write(track, {wikiData}) { link, }); + const generateAlbumAdditionalFilesList = bindOpts(unbound_generateAlbumAdditionalFilesList, { + [bindOpts.bindIndex]: 2, + generateAdditionalFilesList, + getSizeOfAdditionalFile, + link, + urls, + }); + return { title: language.$('trackPage.title', {track: track.name}), stylesheet: getAlbumStylesheet(album, {to}), @@ -273,6 +290,30 @@ export function write(track, {wikiData}) { }), ]), + html.tag('p', + { + [html.onlyIfContent]: true, + [html.joinChildren]: '
', + }, + [ + hasSheetMusicFiles && + language.$('releaseInfo.sheetMusicFiles.shortcut', { + link: html.tag('a', + {href: '#sheet-music-files'}, + language.$('releaseInfo.sheetMusicFiles.shortcut.link')), + }), + + hasMidiProjectFiles && + language.$('releaseInfo.midiProjectFiles.shortcut', { + link: html.tag('a', + {href: '#midi-project-files'}, + language.$('releaseInfo.midiProjectFiles.shortcut.link')), + }), + + hasAdditionalFiles && + generateAdditionalFilesShortcut(track.additionalFiles), + ]), + html.tag('p', (empty(track.urls) ? language.$('releaseInfo.listenOn.noLinks') @@ -377,6 +418,39 @@ export function write(track, {wikiData}) { html.tag('blockquote', transformLyrics(track.lyrics)), ]), + ...html.fragment( + hasSheetMusicFiles && [ + html.tag('p', + {id: 'sheet-music-files', class: ['content-heading']}, + language.$('releaseInfo.sheetMusicFiles.heading')), + + generateAlbumAdditionalFilesList(album, track.sheetMusicFiles, { + fileSize: false, + }), + ]), + + ...html.fragment( + hasMidiProjectFiles && [ + html.tag('p', + {id: 'midi-project-files', class: ['content-heading']}, + language.$('releaseInfo.midiProjectFiles.heading')), + + generateAlbumAdditionalFilesList(album, track.midiProjectFiles), + ]), + + ...html.fragment( + hasAdditionalFiles && [ + html.tag('p', + {id: 'additional-files', class: ['content-heading']}, + language.$('releaseInfo.additionalFiles.heading', { + additionalFiles: language.countAdditionalFiles(numAdditionalFiles, { + unit: true, + }), + })), + + generateAlbumAdditionalFilesList(album, track.additionalFiles), + ]), + ...html.fragment( hasCommentary && [ html.tag('p', {class: ['content-heading']}, diff --git a/src/strings-default.json b/src/strings-default.json index 0faa4f7..5d6935e 100644 --- a/src/strings-default.json +++ b/src/strings-default.json @@ -118,11 +118,17 @@ "releaseInfo.artTags.inline": "Tags: {TAGS}", "releaseInfo.additionalFiles.shortcut": "{ANCHOR_LINK} {TITLES}", "releaseInfo.additionalFiles.shortcut.anchorLink": "Additional files:", - "releaseInfo.additionalFiles.heading": "Has {ADDITIONAL_FILES}:", + "releaseInfo.additionalFiles.heading": "View or download {ADDITIONAL_FILES}:", "releaseInfo.additionalFiles.entry": "{TITLE}", "releaseInfo.additionalFiles.entry.withDescription": "{TITLE}: {DESCRIPTION}", "releaseInfo.additionalFiles.file": "{FILE}", "releaseInfo.additionalFiles.file.withSize": "{FILE} ({SIZE})", + "releaseInfo.sheetMusicFiles.shortcut": "Download {LINK}.", + "releaseInfo.sheetMusicFiles.shortcut.link": "sheet music files", + "releaseInfo.sheetMusicFiles.heading": "Print or download sheet music files:", + "releaseInfo.midiProjectFiles.shortcut": "Download {LINK}.", + "releaseInfo.midiProjectFiles.shortcut.link": "MIDI/project files", + "releaseInfo.midiProjectFiles.heading": "Download MIDI/project files:", "releaseInfo.note": "Note:", "trackList.section.withDuration": "{SECTION} ({DURATION}):", "trackList.group": "{GROUP}:", diff --git a/src/upd8.js b/src/upd8.js index 3937283..fd56522 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -627,7 +627,11 @@ async function main() { ...wikiData.albumData.flatMap((album) => [ ...(album.additionalFiles ?? []), - ...album.tracks.flatMap((track) => track.additionalFiles ?? []), + ...album.tracks.flatMap((track) => [ + ...(track.additionalFiles ?? []), + ...(track.sheetMusicFiles ?? []), + ...(track.midiProjectFiles ?? []), + ]), ] .flatMap((fileGroup) => fileGroup.files) .map((file) => ({ -- cgit 1.3.0-6-gf8a5 From 8a24f02782b7f2454ef67d6ece0b62b808175a21 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 27 Feb 2023 10:30:12 -0400 Subject: smooth hash link scrolling & anchor scroll offset --- src/static/client.js | 43 ++++++++++++++++++++++++++++++++++++++++++- src/static/site3.css | 10 ++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/static/client.js b/src/static/client.js index 15f21fd..87b7400 100644 --- a/src/static/client.js +++ b/src/static/client.js @@ -444,6 +444,47 @@ if (localStorage.tryInfoCards) { addInfoCardLinkHandlers('track'); } +// Custom hash links -------------------------------------- + +function addHashLinkHandlers() { + // Instead of defining a scroll offset (to account for the sticky heading) + // in JavaScript, we interface with the CSS property 'scroll-margin-top'. + // This lets the scroll offset be consolidated where it makes sense, and + // sets an appropriate offset when (re)loading a page with hash for free! + + for (const a of document.links) { + const href = a.getAttribute('href'); + if (!href || !href.startsWith('#')) { + continue; + } + + a.addEventListener('click', handleHashLinkClicked); + } + + function handleHashLinkClicked(evt) { + if (evt.metaKey || evt.shiftKey || evt.ctrlKey || evt.altKey) { + return; + } + + const href = evt.target.getAttribute('href'); + const id = href.slice(1); + const linked = document.getElementById(id); + const box = linked.getBoundingClientRect(); + const style = window.getComputedStyle(linked); + + const scrollY = + window.scrollY + + box.top + - style['scroll-margin-top'].replace('px', ''); + + evt.preventDefault(); + history.pushState({}, '', href); + window.scrollTo({top: scrollY, behavior: 'smooth'}); + } +} + +addHashLinkHandlers(); + // Sticky content heading --------------------------------- const stickyHeadingInfo = Array.from(document.querySelectorAll('.content-sticky-heading-container')) @@ -510,7 +551,7 @@ function updateStickyHeading() { for (let i = contentHeadings.length - 1; i >= 0; i--) { const heading = contentHeadings[i]; const headingRect = heading.getBoundingClientRect(); - if (headingRect.y + headingRect.height / 1.5 < stickyBottom) { + if (headingRect.y + headingRect.height / 1.5 < stickyBottom + 20) { closestHeading = heading; break; } diff --git a/src/static/site3.css b/src/static/site3.css index cc853b6..22a92f8 100644 --- a/src/static/site3.css +++ b/src/static/site3.css @@ -1140,6 +1140,16 @@ html[data-url-key="localized.home"] .carousel-container { /* Sticky heading */ +#content [id] { + /* Adjust scroll margin. */ + scroll-margin-top: calc( + 74px /* Sticky heading */ + + 33px /* Sticky subheading */ + - 1em /* One line of text (align bottom) */ + - 4px /* Padding for hanging letters */ + ); +} + .content-sticky-heading-container { position: sticky; top: 0; -- cgit 1.3.0-6-gf8a5 From 7cb7f029625302dcdc185efa7e0539bb09bfcf2b Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 28 Feb 2023 19:42:07 -0400 Subject: fix summary els not looking right on Safari --- src/page/album.js | 4 ++-- src/page/group.js | 7 ++++--- src/static/site3.css | 3 +-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/page/album.js b/src/page/album.js index f4578ac..24033b1 100644 --- a/src/page/album.js +++ b/src/page/album.js @@ -549,7 +549,7 @@ export function generateAlbumSidebar(album, currentTrack, { html.tag( 'summary', {style: getLinkThemeString(color)}, - [ + html.tag('span', [ listTag === 'ol' && language.$('albumSidebar.trackList.group.withRange', { group: groupName, @@ -561,7 +561,7 @@ export function generateAlbumSidebar(album, currentTrack, { language.$('albumSidebar.trackList.group', { group: groupName, }), - ]), + ])), html.tag(listTag, listTag === 'ol' ? {start: startIndex + 1} : {}, tracks.map(trackToListItem)), diff --git a/src/page/group.js b/src/page/group.js index 9a48c1d..81e1728 100644 --- a/src/page/group.js +++ b/src/page/group.js @@ -247,9 +247,10 @@ function generateGroupSidebar(currentGroup, isGallery, { [ html.tag('summary', {style: getLinkThemeString(category.color)}, - language.$('groupSidebar.groupList.category', { - category: `${category.name}`, - })), + html.tag('span', + language.$('groupSidebar.groupList.category', { + category: `${category.name}`, + }))), html.tag('ul', category.groups.map((group) => { const linkKey = ( diff --git a/src/static/site3.css b/src/static/site3.css index 22a92f8..9128bd8 100644 --- a/src/static/site3.css +++ b/src/static/site3.css @@ -342,14 +342,13 @@ body::before { .sidebar > details summary { margin-top: 0.5em; padding-left: 5px; - user-select: none; } .sidebar > details summary .group-name { color: var(--primary-color); } -.sidebar > details summary:hover { +.sidebar > details summary > span:hover { cursor: pointer; text-decoration: underline; text-decoration-color: var(--primary-color); -- cgit 1.3.0-6-gf8a5 From 6d8f75dd5873f1427a343971edd0e0ea40b015a5 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 28 Feb 2023 19:50:01 -0400 Subject: hash link highlighting & additional skippers --- src/misc-templates.js | 16 +++++++ src/page/track.js | 103 ++++++++++++++++++++++++++---------------- src/static/client.js | 34 ++++++++++++++ src/static/site3.css | 47 +++++++++++++++++++- src/strings-default.json | 23 +++++++--- src/write/bind-utilities.js | 6 +++ src/write/page-template.js | 106 ++++++++++++++++++++++++++++++++++---------- 7 files changed, 266 insertions(+), 69 deletions(-) diff --git a/src/misc-templates.js b/src/misc-templates.js index 1c6dda5..db97e53 100644 --- a/src/misc-templates.js +++ b/src/misc-templates.js @@ -916,6 +916,21 @@ function unbound_generateNavigationLinks(current, { // Sticky heading, ooooo +function unbound_generateContentHeading({ + html, + + id, + title, +}) { + return html.tag('p', + { + class: 'content-heading', + id, + tabindex: '0', + }, + title); +} + function unbound_generateStickyHeadingContainer({ getRevealStringFromArtTags, html, @@ -1025,6 +1040,7 @@ export { unbound_generateInfoGalleryLinks as generateInfoGalleryLinks, unbound_generateNavigationLinks as generateNavigationLinks, + unbound_generateContentHeading as generateContentHeading, unbound_generateStickyHeadingContainer as generateStickyHeadingContainer, unbound_getFooterLocalizationLinks as getFooterLocalizationLinks, diff --git a/src/page/track.js b/src/page/track.js index b9038ba..7f0d1cf 100644 --- a/src/page/track.js +++ b/src/page/track.js @@ -170,6 +170,7 @@ export function write(track, {wikiData}) { generateAdditionalFilesList, generateAdditionalFilesShortcut, generateChronologyLinks, + generateContentHeading, generateNavigationLinks, generateTrackListDividedByGroups, getAlbumStylesheet, @@ -324,8 +325,10 @@ export function write(track, {wikiData}) { ...html.fragment( !empty(otherReleases) && [ - html.tag('p', {class: ['content-heading']}, - language.$('releaseInfo.alsoReleasedAs')), + generateContentHeading({ + id: 'also-released-as', + title: language.$('releaseInfo.alsoReleasedAs'), + }), html.tag('ul', otherReleases.map(track => html.tag('li', language.$('releaseInfo.alsoReleasedAs.item', { @@ -336,8 +339,10 @@ export function write(track, {wikiData}) { ...html.fragment( !empty(contributorContribs) && [ - html.tag('p', {class: ['content-heading']}, - language.$('releaseInfo.contributors')), + generateContentHeading({ + id: 'contributors', + title: language.$('releaseInfo.contributors'), + }), html.tag('ul', contributorContribs.map(contrib => html.tag('li', getArtistString([contrib], { @@ -348,20 +353,26 @@ export function write(track, {wikiData}) { ...html.fragment( !empty(referencedTracks) && [ - html.tag('p', {class: ['content-heading']}, - language.$('releaseInfo.tracksReferenced', { - track: html.tag('i', track.name), - })), + generateContentHeading({ + id: 'references', + title: + language.$('releaseInfo.tracksReferenced', { + track: html.tag('i', track.name), + }), + }), html.tag('ul', referencedTracks.map(getTrackItem)), ]), ...html.fragment( !empty(referencedByTracks) && [ - html.tag('p', {class: ['content-heading']}, - language.$('releaseInfo.tracksThatReference', { - track: html.tag('i', track.name), - })), + generateContentHeading({ + id: 'referenced-by', + title: + language.$('releaseInfo.tracksThatReference', { + track: html.tag('i', track.name), + }), + }), generateTrackListDividedByGroups(referencedByTracks, { getTrackItem, @@ -371,20 +382,26 @@ export function write(track, {wikiData}) { ...html.fragment( !empty(sampledTracks) && [ - html.tag('p', {class: ['content-heading']}, - language.$('releaseInfo.tracksSampled', { - track: html.tag('i', track.name), - })), + generateContentHeading({ + id: 'samples', + title: + language.$('releaseInfo.tracksSampled', { + track: html.tag('i', track.name), + }), + }), html.tag('ul', sampledTracks.map(getTrackItem)), ]), ...html.fragment( !empty(sampledByTracks) && [ - html.tag('p', {class: ['content-heading']}, - language.$('releaseInfo.tracksThatSample', { - track: html.tag('i', track.name), - })), + generateContentHeading({ + id: 'sampled-by', + title: + language.$('releaseInfo.tracksThatSample', { + track: html.tag('i', track.name), + }) + }), html.tag('ul', sampledByTracks.map(getTrackItem)), ]), @@ -392,10 +409,13 @@ export function write(track, {wikiData}) { ...html.fragment( wikiInfo.enableFlashesAndGames && !empty(flashesThatFeature) && [ - html.tag('p', {class: ['content-heading']}, - language.$('releaseInfo.flashesThatFeature', { - track: html.tag('i', track.name), - })), + generateContentHeading({ + id: 'featured-in', + title: + language.$('releaseInfo.flashesThatFeature', { + track: html.tag('i', track.name), + }), + }), html.tag('ul', flashesThatFeature.map(({flash, as}) => html.tag('li', @@ -412,17 +432,20 @@ export function write(track, {wikiData}) { ...html.fragment( track.lyrics && [ - html.tag('p', {class: ['content-heading']}, - language.$('releaseInfo.lyrics')), + generateContentHeading({ + id: 'lyrics', + title: language.$('releaseInfo.lyrics'), + }), html.tag('blockquote', transformLyrics(track.lyrics)), ]), ...html.fragment( hasSheetMusicFiles && [ - html.tag('p', - {id: 'sheet-music-files', class: ['content-heading']}, - language.$('releaseInfo.sheetMusicFiles.heading')), + generateContentHeading({ + id: 'sheet-music-files', + title: language.$('releaseInfo.sheetMusicFiles.heading'), + }), generateAlbumAdditionalFilesList(album, track.sheetMusicFiles, { fileSize: false, @@ -431,30 +454,34 @@ export function write(track, {wikiData}) { ...html.fragment( hasMidiProjectFiles && [ - html.tag('p', - {id: 'midi-project-files', class: ['content-heading']}, - language.$('releaseInfo.midiProjectFiles.heading')), + generateContentHeading({ + id: 'midi-project-files', + title: language.$('releaseInfo.midiProjectFiles.heading'), + }), generateAlbumAdditionalFilesList(album, track.midiProjectFiles), ]), ...html.fragment( hasAdditionalFiles && [ - html.tag('p', - {id: 'additional-files', class: ['content-heading']}, - language.$('releaseInfo.additionalFiles.heading', { + generateContentHeading({ + id: 'additional-files', + title: language.$('releaseInfo.additionalFiles.heading', { additionalFiles: language.countAdditionalFiles(numAdditionalFiles, { unit: true, }), - })), + }) + }), generateAlbumAdditionalFilesList(album, track.additionalFiles), ]), ...html.fragment( hasCommentary && [ - html.tag('p', {class: ['content-heading']}, - language.$('releaseInfo.artistCommentary')), + generateContentHeading({ + id: 'artist-commentary', + title: language.$('releaseInfo.artistCommentary'), + }), html.tag('blockquote', generateCommentary({ link, diff --git a/src/static/client.js b/src/static/client.js index 87b7400..4eb1d2b 100644 --- a/src/static/client.js +++ b/src/static/client.js @@ -452,6 +452,8 @@ function addHashLinkHandlers() { // This lets the scroll offset be consolidated where it makes sense, and // sets an appropriate offset when (re)loading a page with hash for free! + let wasHighlighted; + for (const a of document.links) { const href = a.getAttribute('href'); if (!href || !href.startsWith('#')) { @@ -469,6 +471,17 @@ function addHashLinkHandlers() { const href = evt.target.getAttribute('href'); const id = href.slice(1); const linked = document.getElementById(id); + + if (!linked) { + return; + } + + // Hide skipper box right away, so the layout is updated on time for the + // math operations coming up next. + const skipper = document.getElementById('skippers'); + skipper.style.display = 'none'; + setTimeout(() => skipper.style.display = ''); + const box = linked.getBoundingClientRect(); const style = window.getComputedStyle(linked); @@ -480,6 +493,27 @@ function addHashLinkHandlers() { evt.preventDefault(); history.pushState({}, '', href); window.scrollTo({top: scrollY, behavior: 'smooth'}); + linked.focus({preventScroll: true}); + + const maxScroll = + document.body.scrollHeight + - window.innerHeight; + + if (scrollY > maxScroll && linked.classList.contains('content-heading')) { + if (wasHighlighted) { + wasHighlighted.classList.remove('highlight-hash-link'); + } + + wasHighlighted = linked; + linked.classList.add('highlight-hash-link'); + linked.addEventListener('animationend', function handle(evt) { + if (evt.animationName === 'highlight-hash-link') { + linked.removeEventListener('animationend', handle); + linked.classList.remove('highlight-hash-link'); + wasHighlighted = null; + } + }); + } } } diff --git a/src/static/site3.css b/src/static/site3.css index 9128bd8..c522bc9 100644 --- a/src/static/site3.css +++ b/src/static/site3.css @@ -208,7 +208,19 @@ body::before { box-shadow: 0 0 40px rgba(0, 0, 0, 0.5); } -#skippers > .skipper:not(:last-child)::after { +#skippers > * { + display: inline-block; +} + +#skippers > .skipper-list:not(:last-child)::after { + display: inline-block; + content: "\00a0"; + margin-left: 2px; + margin-right: -2px; + border-left: 1px dotted; +} + +#skippers .skipper-list > .skipper:not(:last-child)::after { content: " \00b7 "; font-weight: 800; } @@ -1137,6 +1149,37 @@ html[data-url-key="localized.home"] .carousel-container { margin-bottom: 0; } +/* Custom hash links */ + +.content-heading { + border-bottom: 3px double transparent; + margin-bottom: -3px; +} + +.content-heading.highlight-hash-link { + animation: highlight-hash-link 4s; + animation-delay: 125ms; +} + +/* This animation's name is referenced in JavaScript */ +@keyframes highlight-hash-link { + 0% { + border-bottom-color: transparent; + } + + 10% { + border-bottom-color: white; + } + + 25% { + border-bottom-color: white; + } + + 100% { + border-bottom-color: transparent; + } +} + /* Sticky heading */ #content [id] { @@ -1145,7 +1188,7 @@ html[data-url-key="localized.home"] .carousel-container { 74px /* Sticky heading */ + 33px /* Sticky subheading */ - 1em /* One line of text (align bottom) */ - - 4px /* Padding for hanging letters */ + - 12px /* Padding for hanging letters & focus ring */ ); } diff --git a/src/strings-default.json b/src/strings-default.json index 5d6935e..97be1f0 100644 --- a/src/strings-default.json +++ b/src/strings-default.json @@ -181,11 +181,24 @@ "misc.nav.gallery": "Gallery", "misc.pageTitle": "{TITLE}", "misc.pageTitle.withWikiName": "{TITLE} | {WIKI_NAME}", - "misc.skippers.skipToContent": "Skip to content", - "misc.skippers.skipToSidebar": "Skip to sidebar", - "misc.skippers.skipToSidebar.left": "Skip to sidebar (left)", - "misc.skippers.skipToSidebar.right": "Skip to sidebar (right)", - "misc.skippers.skipToFooter": "Skip to footer", + "misc.skippers.skipTo": "Skip to:", + "misc.skippers.content": "Content", + "misc.skippers.sidebar": "Sidebar", + "misc.skippers.sidebar.left": "Sidebar (left)", + "misc.skippers.sidebar.right": "Sidebar (right)", + "misc.skippers.header": "Header", + "misc.skippers.footer": "Footer", + "misc.skippers.contributors": "Contributors", + "misc.skippers.references": "References...", + "misc.skippers.referencedBy": "Referenced by...", + "misc.skippers.samples": "Samples...", + "misc.skippers.sampledBy": "Sampled by...", + "misc.skippers.featuredIn": "Featured in...", + "misc.skippers.lyrics": "Lyrics", + "misc.skippers.sheetMusicFiles": "Sheet music files", + "misc.skippers.midiProjectFiles": "MIDI/project files", + "misc.skippers.additionalFiles": "Additional files", + "misc.skippers.artistCommentary": "Commentary", "misc.socialEmbed.heading": "{WIKI_NAME} | {HEADING}", "misc.jumpTo": "Jump to:", "misc.jumpTo.withLinks": "Jump to: {LINKS}.", diff --git a/src/write/bind-utilities.js b/src/write/bind-utilities.js index 127afe2..427111b 100644 --- a/src/write/bind-utilities.js +++ b/src/write/bind-utilities.js @@ -19,6 +19,7 @@ import { generateAdditionalFilesList, generateAdditionalFilesShortcut, generateChronologyLinks, + generateContentHeading, generateCoverLink, generateInfoGalleryLinks, generateTrackListDividedByGroups, @@ -192,6 +193,11 @@ export function bindUtilities({ language, }); + bound.generateContentHeading = bindOpts(generateContentHeading, { + [bindOpts.bindIndex]: 0, + html, + }); + bound.generateStickyHeadingContainer = bindOpts(generateStickyHeadingContainer, { [bindOpts.bindIndex]: 0, getRevealStringFromArtTags: bound.getRevealStringFromArtTags, diff --git a/src/write/page-template.js b/src/write/page-template.js index de36901..96036df 100644 --- a/src/write/page-template.js +++ b/src/write/page-template.js @@ -401,6 +401,87 @@ export function generateDocumentHTML(pageInfo, { footerHTML, ].filter(Boolean).join('\n'); + const processSkippers = skipperList => + skipperList + .filter(Boolean) + .map(([href, stringSubkey]) => + html.tag('span', {class: 'skipper'}, + html.tag('a', + {href}, + language.$(`misc.skippers.${stringSubkey}`)))); + + // Hilariously jank. Sorry! + const hasID = id => mainHTML.includes(`id="${id}"`); + const hasContributors = hasID('contributors'); + const hasReferences = hasID('references'); + const hasReferencedBy = hasID('referenced-by'); + const hasSamples = hasID('samples'); + const hasSampledBy = hasID('sampled-by'); + const hasFeaturedIn = hasID('featured-in'); + const hasLyrics = hasID('lyrics'); + const hasSheetMusicFiles = hasID('sheet-music-files'); + const hasMidiProjectFiles = hasID('midi-project-files'); + const hasAdditionalFiles = hasID('additional-files'); + const hasArtistCommentary = hasID('artist-commentary'); + + const skippersHTML = + mainHTML && + html.tag('div', {id: 'skippers'}, [ + html.tag('span', language.$('misc.skippers.skipTo')), + html.tag('div', {class: 'skipper-list'}, + processSkippers([ + ['#content', 'content'], + sidebarLeftHTML && + [ + '#sidebar-left', + sidebarRightHTML + ? 'sidebar.left' + : 'sidebar', + ], + sidebarRightHTML && + [ + '#sidebar-right', + sidebarLeftHTML + ? 'sidebar.right' + : 'sidebar', + ], + navHTML && + ['#header', 'header'], + footerHTML && + ['#footer', 'footer'], + ])), + + html.tag('div', + { + [html.onlyIfContent]: true, + class: 'skipper-list' + }, + processSkippers([ + hasContributors && + ['#contributors', 'contributors'], + hasReferences && + ['#references', 'references'], + hasReferencedBy && + ['#referenced-by', 'referencedBy'], + hasSamples && + ['#samples', 'samples'], + hasSampledBy && + ['#sampled-by', 'sampledBy'], + hasFeaturedIn && + ['#featured-in', 'featuredIn'], + hasLyrics && + ['#lyrics', 'lyrics'], + hasSheetMusicFiles && + ['#sheet-music-files', 'sheetMusicFiles'], + hasMidiProjectFiles && + ['#midi-project-files', 'midiProjectFiles'], + hasAdditionalFiles && + ['#additional-files', 'additionalFiles'], + hasArtistCommentary && + ['#artist-commentary', 'artistCommentary'], + ])), + ]); + const infoCardHTML = html.tag('div', {id: 'info-card-container'}, html.tag('div', {id: 'info-card-decor'}, html.tag('div', {id: 'info-card'}, [ @@ -552,30 +633,7 @@ export function generateDocumentHTML(pageInfo, { [ html.tag('div', {id: 'page-container'}, [ mainHTML && - html.tag('div', {id: 'skippers'}, - [ - ['#content', language.$('misc.skippers.skipToContent')], - sidebarLeftHTML && - [ - '#sidebar-left', - sidebarRightHTML - ? language.$('misc.skippers.skipToSidebar.left') - : language.$('misc.skippers.skipToSidebar'), - ], - sidebarRightHTML && - [ - '#sidebar-right', - sidebarLeftHTML - ? language.$('misc.skippers.skipToSidebar.right') - : language.$('misc.skippers.skipToSidebar'), - ], - footerHTML && - ['#footer', language.$('misc.skippers.skipToFooter')], - ] - .filter(Boolean) - .map(([href, title]) => - html.tag('span', {class: 'skipper'}, - html.tag('a', {href}, title)))), + skippersHTML, layoutHTML, ]), -- cgit 1.3.0-6-gf8a5 From 1f0924cc94ea10320afe951a2e8fb4906b5e6106 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Wed, 1 Mar 2023 16:04:50 -0400 Subject: Tracks - with Sheet Music / MIDI & Project Files listings --- src/listing-spec.js | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ src/strings-default.json | 8 ++++++ 2 files changed, 76 insertions(+) diff --git a/src/listing-spec.js b/src/listing-spec.js index ef51fe9..29b7645 100644 --- a/src/listing-spec.js +++ b/src/listing-spec.js @@ -825,6 +825,74 @@ const listingSpec = [ ])), }, + { + directory: 'tracks/with-sheet-music-files', + stringsKey: 'listTracks.withSheetMusicFiles', + + data: ({wikiData: {albumData}}) => + albumData + .map(album => ({ + album, + tracks: album.tracks.filter(t => !empty(t.sheetMusicFiles)), + })) + .filter(({tracks}) => !empty(tracks)), + + html: (data, {html, language, link}) => + html.tag('dl', + data.flatMap(({album, tracks}) => [ + html.tag('dt', + {class: 'content-heading'}, + language.$('listingPage.listTracks.withSheetMusicFiles.album', { + album: link.album(album), + date: language.formatDate(album.date), + })), + + html.tag('dd', + html.tag('ul', + tracks.map(track => + html.tag('li', + language.$('listingPage.listTracks.withSheetMusicFiles.track', { + track: link.track(track, { + hash: 'sheet-music-files', + }), + }))))), + ])), + }, + + { + directory: 'tracks/with-midi-project-files', + stringsKey: 'listTracks.withMidiProjectFiles', + + data: ({wikiData: {albumData}}) => + albumData + .map(album => ({ + album, + tracks: album.tracks.filter(t => !empty(t.midiProjectFiles)), + })) + .filter(({tracks}) => !empty(tracks)), + + html: (data, {html, language, link}) => + html.tag('dl', + data.flatMap(({album, tracks}) => [ + html.tag('dt', + {class: 'content-heading'}, + language.$('listingPage.listTracks.withMidiProjectFiles.album', { + album: link.album(album), + date: language.formatDate(album.date), + })), + + html.tag('dd', + html.tag('ul', + tracks.map(track => + html.tag('li', + language.$('listingPage.listTracks.withMidiProjectFiles.track', { + track: link.track(track, { + hash: 'midi-project-files', + }), + }))))), + ])), + }, + { directory: 'tags/by-name', stringsKey: 'listTags.byName', diff --git a/src/strings-default.json b/src/strings-default.json index 97be1f0..0c775e3 100644 --- a/src/strings-default.json +++ b/src/strings-default.json @@ -390,6 +390,14 @@ "listingPage.listTracks.withLyrics.title.short": "...with Lyrics", "listingPage.listTracks.withLyrics.album": "{ALBUM} ({DATE})", "listingPage.listTracks.withLyrics.track": "{TRACK}", + "listingPage.listTracks.withSheetMusicFiles.title": "Tracks - with Sheet Music Files", + "listingPage.listTracks.withSheetMusicFiles.title.short": "...with Sheet Music Files", + "listingPage.listTracks.withSheetMusicFiles.album": "{ALBUM} ({DATE})", + "listingPage.listTracks.withSheetMusicFiles.track": "{TRACK}", + "listingPage.listTracks.withMidiProjectFiles.title": "Tracks - with MIDI & Project Files", + "listingPage.listTracks.withMidiProjectFiles.title.short": "...with MIDI & Project Files", + "listingPage.listTracks.withMidiProjectFiles.album": "{ALBUM} ({DATE})", + "listingPage.listTracks.withMidiProjectFiles.track": "{TRACK}", "listingPage.listTags.byName.title": "Tags - by Name", "listingPage.listTags.byName.title.short": "...by Name", "listingPage.listTags.byName.item": "{TAG} ({TIMES_USED})", -- cgit 1.3.0-6-gf8a5