« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/page
diff options
context:
space:
mode:
Diffstat (limited to 'src/page')
-rw-r--r--src/page/album.js381
-rw-r--r--src/page/index.js5
2 files changed, 386 insertions, 0 deletions
diff --git a/src/page/album.js b/src/page/album.js
new file mode 100644
index 0000000..f859779
--- /dev/null
+++ b/src/page/album.js
@@ -0,0 +1,381 @@
+// Album page specification.
+//
+// Also exports utility functions:
+//   - generateAlbumSidebar
+//   - generateAlbumNavLinks
+//   - generateAlbumChronologyLinks
+
+import fixWS from 'fix-whitespace';
+
+import {
+    getAlbumCover,
+    getAlbumListTag,
+    getTotalDuration
+} from '../util/wiki-data.js';
+
+import {
+    getLinkThemeString,
+    getThemeString
+} from '../util/colors.js';
+
+import * as html from '../util/html.js';
+
+export function targets({wikiData}) {
+    return wikiData.albumData;
+}
+
+export function write(album, {wikiData}) {
+    const { wikiInfo } = wikiData;
+
+    const trackToListItem = (track, {getArtistString, link, strings}) => {
+        const itemOpts = {
+            duration: strings.count.duration(track.duration),
+            track: link.track(track)
+        };
+        return `<li style="${getLinkThemeString(track.color)}">${
+            (track.artists === album.artists
+                ? strings('trackList.item.withDuration', itemOpts)
+                : strings('trackList.item.withDuration.withArtists', {
+                    ...itemOpts,
+                    by: `<span class="by">${
+                        strings('trackList.item.withArtists.by', {
+                            artists: getArtistString(track.artists)
+                        })
+                    }</span>`
+                }))
+        }</li>`;
+    };
+
+    const commentaryEntries = [album, ...album.tracks].filter(x => x.commentary).length;
+    const albumDuration = getTotalDuration(album.tracks);
+
+    const listTag = getAlbumListTag(album);
+
+    const data = {
+        type: 'data',
+        path: ['album', album.directory],
+        data: ({
+            serializeContribs,
+            serializeCover,
+            serializeGroupsForAlbum,
+            serializeLink
+        }) => ({
+            name: album.name,
+            directory: album.directory,
+            dates: {
+                released: album.date,
+                trackArtAdded: album.trackArtDate,
+                coverArtAdded: album.coverArtDate,
+                addedToWiki: album.dateAdded
+            },
+            duration: albumDuration,
+            color: album.color,
+            cover: serializeCover(album, getAlbumCover),
+            artists: serializeContribs(album.artists || []),
+            coverArtists: serializeContribs(album.coverArtists || []),
+            wallpaperArtists: serializeContribs(album.wallpaperArtists || []),
+            bannerArtists: serializeContribs(album.bannerArtists || []),
+            groups: serializeGroupsForAlbum(album),
+            trackGroups: album.trackGroups?.map(trackGroup => ({
+                name: trackGroup.name,
+                color: trackGroup.color,
+                tracks: trackGroup.tracks.map(track => track.directory)
+            })),
+            tracks: album.tracks.map(track => ({
+                link: serializeLink(track),
+                duration: track.duration
+            }))
+        })
+    };
+
+    const page = {
+        type: 'page',
+        path: ['album', album.directory],
+        page: ({
+            chronologyLinks,
+            fancifyURL,
+            generateCoverLink,
+            getAlbumStylesheet,
+            getArtistString,
+            link,
+            strings,
+            transformMultiline
+        }) => ({
+            title: strings('albumPage.title', {album: album.name}),
+            stylesheet: getAlbumStylesheet(album),
+            theme: getThemeString(album.color, [
+                `--album-directory: ${album.directory}`
+            ]),
+
+            banner: album.bannerArtists && {
+                dimensions: album.bannerDimensions,
+                path: ['media.albumBanner', album.directory],
+                alt: strings('misc.alt.albumBanner'),
+                position: 'top'
+            },
+
+            main: {
+                content: fixWS`
+                    ${generateCoverLink({
+                        path: ['media.albumCover', album.directory],
+                        alt: strings('misc.alt.albumCover'),
+                        tags: album.artTags
+                    })}
+                    <h1>${strings('albumPage.title', {album: album.name})}</h1>
+                    <p>
+                        ${[
+                            album.artists && strings('releaseInfo.by', {
+                                artists: getArtistString(album.artists, {
+                                    showContrib: true,
+                                    showIcons: true
+                                })
+                            }),
+                            album.coverArtists && strings('releaseInfo.coverArtBy', {
+                                artists: getArtistString(album.coverArtists, {
+                                    showContrib: true,
+                                    showIcons: true
+                                })
+                            }),
+                            album.wallpaperArtists && strings('releaseInfo.wallpaperArtBy', {
+                                artists: getArtistString(album.wallpaperArtists, {
+                                    showContrib: true,
+                                    showIcons: true
+                                })
+                            }),
+                            album.bannerArtists && strings('releaseInfo.bannerArtBy', {
+                                artists: getArtistString(album.bannerArtists, {
+                                    showContrib: true,
+                                    showIcons: true
+                                })
+                            }),
+                            strings('releaseInfo.released', {
+                                date: strings.count.date(album.date)
+                            }),
+                            +album.coverArtDate !== +album.date && strings('releaseInfo.artReleased', {
+                                date: strings.count.date(album.coverArtDate)
+                            }),
+                            strings('releaseInfo.duration', {
+                                duration: strings.count.duration(albumDuration, {approximate: album.tracks.length > 1})
+                            })
+                        ].filter(Boolean).join('<br>\n')}
+                    </p>
+                    ${commentaryEntries && `<p>${
+                        strings('releaseInfo.viewCommentary', {
+                            link: link.albumCommentary(album, {
+                                text: strings('releaseInfo.viewCommentary.link')
+                            })
+                        })
+                    }</p>`}
+                    ${album.urls.length && `<p>${
+                        strings('releaseInfo.listenOn', {
+                            links: strings.list.or(album.urls.map(url => fancifyURL(url, {album: true})))
+                        })
+                    }</p>`}
+                    ${album.trackGroups ? fixWS`
+                        <dl class="album-group-list">
+                            ${album.trackGroups.map(({ name, color, startIndex, tracks }) => fixWS`
+                                <dt>${
+                                    strings('trackList.group', {
+                                        duration: strings.count.duration(getTotalDuration(tracks), {approximate: tracks.length > 1}),
+                                        group: name
+                                    })
+                                }</dt>
+                                <dd><${listTag === 'ol' ? `ol start="${startIndex + 1}"` : listTag}>
+                                    ${tracks.map(t => trackToListItem(t, {getArtistString, link, strings})).join('\n')}
+                                </${listTag}></dd>
+                            `).join('\n')}
+                        </dl>
+                    ` : fixWS`
+                        <${listTag}>
+                            ${album.tracks.map(t => trackToListItem(t, {getArtistString, link, strings})).join('\n')}
+                        </${listTag}>
+                    `}
+                    <p>
+                        ${[
+                            strings('releaseInfo.addedToWiki', {
+                                date: strings.count.date(album.dateAdded)
+                            })
+                        ].filter(Boolean).join('<br>\n')}
+                    </p>
+                    ${album.commentary && fixWS`
+                        <p>${strings('releaseInfo.artistCommentary')}</p>
+                        <blockquote>
+                            ${transformMultiline(album.commentary)}
+                        </blockquote>
+                    `}
+                `
+            },
+
+            sidebarLeft: generateAlbumSidebar(album, null, {
+                fancifyURL,
+                link,
+                strings,
+                transformMultiline,
+                wikiData
+            }),
+
+            nav: {
+                links: [
+                    {toHome: true},
+                    {
+                        html: strings('albumPage.nav.album', {
+                            album: link.album(album, {class: 'current'})
+                        })
+                    },
+                    album.tracks.length > 1 &&
+                    {
+                        divider: false,
+                        html: generateAlbumNavLinks(album, null, {link, strings})
+                    }
+                ],
+                content: html.tag('div', generateAlbumChronologyLinks(album, null, {chronologyLinks}))
+            }
+        })
+    };
+
+    return [page, data];
+}
+
+export function generateAlbumSidebar(album, currentTrack, {
+    fancifyURL,
+    link,
+    strings,
+    transformMultiline,
+    wikiData
+}) {
+    const listTag = getAlbumListTag(album);
+
+    const trackGroups = album.trackGroups || [{
+        name: strings('albumSidebar.trackList.fallbackGroupName'),
+        color: album.color,
+        startIndex: 0,
+        tracks: album.tracks
+    }];
+
+    const trackToListItem = track => html.tag('li',
+        {class: track === currentTrack && 'current'},
+        strings('albumSidebar.trackList.item', {
+            track: link.track(track)
+        }));
+
+    const trackListPart = fixWS`
+        <h1>${link.album(album)}</h1>
+        ${trackGroups.map(({ name, color, startIndex, tracks }) =>
+            html.tag('details', {
+                // Leave side8ar track groups collapsed on al8um homepage,
+                // since there's already a view of all the groups expanded
+                // in the main content area.
+                open: currentTrack && tracks.includes(currentTrack),
+                class: tracks.includes(currentTrack) && 'current'
+            }, [
+                html.tag('summary',
+                    {style: getLinkThemeString(color)},
+                    (listTag === 'ol'
+                        ? strings('albumSidebar.trackList.group.withRange', {
+                            group: `<span class="group-name">${name}</span>`,
+                            range: `${startIndex + 1}&ndash;${startIndex + tracks.length}`
+                        })
+                        : strings('albumSidebar.trackList.group', {
+                            group: `<span class="group-name">${name}</span>`
+                        }))
+                ),
+                fixWS`
+                    <${listTag === 'ol' ? `ol start="${startIndex + 1}"` : listTag}>
+                        ${tracks.map(trackToListItem).join('\n')}
+                    </${listTag}>
+                `
+            ])).join('\n')}
+    `;
+
+    const { groups } = album;
+
+    const groupParts = groups.map(group => {
+        const index = group.albums.indexOf(album);
+        const next = group.albums[index + 1];
+        const previous = group.albums[index - 1];
+        return {group, next, previous};
+    }).map(({group, next, previous}) => fixWS`
+        <h1>${
+            strings('albumSidebar.groupBox.title', {
+                group: link.groupInfo(group)
+            })
+        }</h1>
+        ${!currentTrack && transformMultiline(group.descriptionShort)}
+        ${group.urls.length && `<p>${
+            strings('releaseInfo.visitOn', {
+                links: strings.list.or(group.urls.map(url => fancifyURL(url)))
+            })
+        }</p>`}
+        ${!currentTrack && fixWS`
+            ${next && `<p class="group-chronology-link">${
+                strings('albumSidebar.groupBox.next', {
+                    album: link.album(next)
+                })
+            }</p>`}
+            ${previous && `<p class="group-chronology-link">${
+                strings('albumSidebar.groupBox.previous', {
+                    album: link.album(previous)
+                })
+            }</p>`}
+        `}
+    `);
+
+    if (groupParts.length) {
+        if (currentTrack) {
+            const combinedGroupPart = groupParts.join('\n<hr>\n');
+            return {
+                multiple: [
+                    trackListPart,
+                    combinedGroupPart
+                ]
+            };
+        } else {
+            return {
+                multiple: [
+                    ...groupParts,
+                    trackListPart
+                ]
+            };
+        }
+    } else {
+        return {
+            content: trackListPart
+        };
+    }
+}
+
+export function generateAlbumNavLinks(album, currentTrack, {link, strings}) {
+    if (album.tracks.length <= 1) {
+        return '';
+    }
+
+    const previousNextLinks = currentTrack && generatePreviousNextLinks(currentTrack, {
+        link, strings,
+        data: album.tracks,
+        linkKey: 'track'
+    });
+    const randomLink = `<a href="#" data-random="track-in-album" id="random-button">${
+        (currentTrack
+            ? strings('trackPage.nav.random')
+            : strings('albumPage.nav.randomTrack'))
+    }</a>`;
+
+    return (previousNextLinks
+        ? `(${previousNextLinks}<span class="js-hide-until-data">, ${randomLink}</span>)`
+        : `<span class="js-hide-until-data">(${randomLink})</span>`);
+}
+
+export function generateAlbumChronologyLinks(album, currentTrack, {chronologyLinks}) {
+    return [
+        currentTrack && chronologyLinks(currentTrack, {
+            contribKey: 'artists',
+            getThings: artist => [...artist.tracks.asArtist, ...artist.tracks.asContributor],
+            headingString: 'misc.chronology.heading.track'
+        }),
+        chronologyLinks(currentTrack || album, {
+            contribKey: 'coverArtists',
+            getThings: artist => [...artist.albums.asCoverArtist, ...artist.tracks.asCoverArtist],
+            headingString: 'misc.chronology.heading.coverArt'
+        })
+    ].filter(Boolean).join('\n');
+}
diff --git a/src/page/index.js b/src/page/index.js
new file mode 100644
index 0000000..ca78238
--- /dev/null
+++ b/src/page/index.js
@@ -0,0 +1,5 @@
+// NB: This is the index for the page/ directory and contains exports for all
+// other modules here! It's not the page spec for the homepage - see
+// homepage.js for that.
+
+export * as album from './album.js';