diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/page/index.js | 33 | ||||
-rw-r--r-- | src/page/news.js | 129 | ||||
-rwxr-xr-x | src/upd8.js | 232 |
3 files changed, 227 insertions, 167 deletions
diff --git a/src/page/index.js b/src/page/index.js index d74dad57..384b4193 100644 --- a/src/page/index.js +++ b/src/page/index.js @@ -3,7 +3,14 @@ // homepage.js for that. // // Each module published in this list should follow a particular format, -// including the following exports: +// including any of the following exports: +// +// condition({wikiData}) +// Returns a boolean indicating whether to process targets/writes (true) or +// skip this page spec altogether (false). This is usually used for +// selectively toggling pages according to site feature flags, though it may +// also be used to e.g. skip out if no targets would be found (preventing +// writeTargetless from generating an empty index page). // // targets({wikiData}) // Gets the objects which this page's write() function should be called on. @@ -11,14 +18,21 @@ // but it may also apply filter/map/etc if useful. // // write(thing, {wikiData}) -// Gets descriptors for any page and data writes associated with the given -// thing (which will be a value from the targets() array). This includes -// page (HTML) writes, data (JSON) writes, etc. Notably, this function does -// not perform any file operations itself; it only describes the operations -// which will be processed elsewhere, once for each translation language. -// The write function also immediately transforms any data which will be -// reused across writes of the same page, so that this data is effectively -// cached (rather than recalculated for each language/write). +// Provides descriptors for any page and data writes associated with the +// given thing (which will be a value from the targets() array). This +// includes page (HTML) writes, data (JSON) writes, etc. Notably, this +// function does not perform any file operations itself; it only describes +// the operations which will be processed elsewhere, once for each +// translation language. The write function also immediately transforms +// any data which will be reused across writes of the same page, so that +// this data is effectively cached (rather than recalculated for each +// language/write). +// +// writeTargetless({wikiData}) +// Provides descriptors for page/data/etc writes which will be used +// without concern for targets. This is usually used for writing index pages +// which should be generated just once (rather than corresponding to +// targets). // // As these modules are effectively the HTML templates for all site layout, // common patterns may also be exported alongside the special exports above. @@ -29,4 +43,5 @@ export * as album from './album.js'; export * as artist from './artist.js'; export * as artistAlias from './artist-alias.js'; export * as group from './group.js'; +export * as news from './news.js'; export * as track from './track.js'; diff --git a/src/page/news.js b/src/page/news.js new file mode 100644 index 00000000..99cbe8d5 --- /dev/null +++ b/src/page/news.js @@ -0,0 +1,129 @@ +// News entry & index page specifications. + +// Imports + +import fixWS from 'fix-whitespace'; + +// Page exports + +export function condition({wikiData}) { + return wikiData.wikiInfo.features.news; +} + +export function targets({wikiData}) { + return wikiData.newsData; +} + +export function write(entry, {wikiData}) { + const page = { + type: 'page', + path: ['newsEntry', entry.directory], + page: ({ + generatePreviousNextLinks, + link, + strings, + transformMultiline, + }) => ({ + title: strings('newsEntryPage.title', {entry: entry.name}), + + main: { + content: fixWS` + <div class="long-content"> + <h1>${strings('newsEntryPage.title', {entry: entry.name})}</h1> + <p>${strings('newsEntryPage.published', {date: strings.count.date(entry.date)})}</p> + ${transformMultiline(entry.body)} + </div> + ` + }, + + nav: generateNewsEntryNav(entry, { + generatePreviousNextLinks, + link, + strings, + wikiData + }) + }) + }; + + return [page]; +} + +export function writeTargetless({wikiData}) { + const { newsData } = wikiData; + + const page = { + type: 'page', + path: ['newsIndex'], + page: ({ + link, + strings, + transformMultiline + }) => ({ + title: strings('newsIndex.title'), + + main: { + content: fixWS` + <div class="long-content news-index"> + <h1>${strings('newsIndex.title')}</h1> + ${newsData.map(entry => fixWS` + <article id="${entry.directory}"> + <h2><time>${strings.count.date(entry.date)}</time> ${link.newsEntry(entry)}</h2> + ${transformMultiline(entry.bodyShort)} + ${entry.bodyShort !== entry.body && `<p>${link.newsEntry(entry, { + text: strings('newsIndex.entry.viewRest') + })}</p>`} + </article> + `).join('\n')} + </div> + ` + }, + + nav: {simple: true} + }) + }; + + return [page]; +} + +// Utility functions + +function generateNewsEntryNav(entry, { + generatePreviousNextLinks, + link, + strings, + wikiData +}) { + const { wikiInfo, newsData } = wikiData; + + // The newsData list is sorted reverse chronologically (newest ones first), + // so the way we find next/previous entries is flipped from normal. + const previousNextLinks = generatePreviousNextLinks(entry, { + link, strings, + data: newsData.slice().reverse(), + linkKey: 'newsEntry' + }); + + return { + links: [ + { + path: ['localized.home'], + title: wikiInfo.shortName + }, + { + path: ['localized.newsIndex'], + title: strings('newsEntryPage.nav.news') + }, + { + html: strings('newsEntryPage.nav.entry', { + date: strings.count.date(entry.date), + entry: link.newsEntry(entry, {class: 'current'}) + }) + }, + previousNextLinks && + { + divider: false, + html: `(${previousNextLinks})` + } + ] + }; +} diff --git a/src/upd8.js b/src/upd8.js index 2e164fac..b9fa0274 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -2429,120 +2429,6 @@ function writeMiscellaneousPages({wikiData}) { ]; } -function writeNewsPages({wikiData}) { - const { newsData, wikiInfo } = wikiData; - - if (!wikiInfo.features.news) { - return; - } - - return [ - writeNewsIndex({wikiData}), - ...newsData.map(entry => writeNewsEntryPage(entry, {wikiData})) - ]; -} - -function writeNewsIndex({wikiData}) { - const { newsData } = wikiData; - - const page = { - type: 'page', - path: ['newsIndex'], - page: ({ - link, - strings, - transformMultiline - }) => ({ - title: strings('newsIndex.title'), - - main: { - content: fixWS` - <div class="long-content news-index"> - <h1>${strings('newsIndex.title')}</h1> - ${newsData.map(entry => fixWS` - <article id="${entry.directory}"> - <h2><time>${strings.count.date(entry.date)}</time> ${link.newsEntry(entry)}</h2> - ${transformMultiline(entry.bodyShort)} - ${entry.bodyShort !== entry.body && `<p>${link.newsEntry(entry, { - text: strings('newsIndex.entry.viewRest') - })}</p>`} - </article> - `).join('\n')} - </div> - ` - }, - - nav: {simple: true} - }) - }; - - return [page]; -} - -function writeNewsEntryPage(entry, {wikiData}) { - const page = { - type: 'page', - path: ['newsEntry', entry.directory], - page: ({ - link, - strings, - transformMultiline, - }) => ({ - title: strings('newsEntryPage.title', {entry: entry.name}), - - main: { - content: fixWS` - <div class="long-content"> - <h1>${strings('newsEntryPage.title', {entry: entry.name})}</h1> - <p>${strings('newsEntryPage.published', {date: strings.count.date(entry.date)})}</p> - ${transformMultiline(entry.body)} - </div> - ` - }, - - nav: generateNewsEntryNav(entry, {link, strings, wikiData}) - }) - }; - - return [page]; -} - -function generateNewsEntryNav(entry, {link, strings, wikiData}) { - const { wikiInfo, newsData } = wikiData; - - // The newsData list is sorted reverse chronologically (newest ones first), - // so the way we find next/previous entries is flipped from normal. - const previousNextLinks = generatePreviousNextLinks(entry, { - link, strings, - data: newsData.slice().reverse(), - linkKey: 'newsEntry' - }); - - return { - links: [ - { - path: ['localized.home'], - title: wikiInfo.shortName - }, - { - path: ['localized.newsIndex'], - title: strings('newsEntryPage.nav.news') - }, - { - html: strings('newsEntryPage.nav.entry', { - date: strings.count.date(entry.date), - entry: link.newsEntry(entry, {class: 'current'}) - }) - }, - previousNextLinks && - { - divider: false, - html: `(${previousNextLinks})` - } - ] - }; -} - function writeStaticPages({wikiData}) { return wikiData.staticPageData.map(staticPage => writeStaticPage(staticPage, {wikiData})); } @@ -4913,72 +4799,102 @@ async function main() { : (Object.entries(buildDictionary) .filter(([ flag ]) => writeFlags[flag]))); - // *NB: While what's 8elow is 8asically still true in principle, the - // format is QUITE DIFFERENT than what's descri8ed here! There - // will 8e actual document8tion on like, what the return format - // looks like soon, once we implement a 8unch of other pages and - // are certain what they actually, uh, will look like, in the end.* - // - // The writeThingPages functions don't actually immediately do any file - // writing themselves; an initial call will only gather the relevant data - // which is *then* used for writing. So the return value is a function - // (or an array of functions) which expects {strings}, and *that's* what - // we call after -- multiple times, once for each language. let writes; { let error = false; - const targets = buildSteps.flatMap(([ flag, pageSpec ]) => { + const buildStepsWithTargets = buildSteps.map(([ flag, pageSpec ]) => { + // Condition not met: skip this build step altogether. + if (pageSpec.condition && !pageSpec.condition({wikiData})) { + return null; + } + + // May still call writeTargetless if present. + if (!pageSpec.targets) { + return {flag, pageSpec, targets: []}; + } + + if (!pageSpec.write) { + logError`${flag + '.targets'} is specified, but ${flag + '.write'} is missing!`; + error = true; + return null; + } + const targets = pageSpec.targets({wikiData}); - return targets.map(target => ({flag, pageSpec, target})); - }); + return {flag, pageSpec, targets}; + }).filter(Boolean); - const writeArrays = await progressPromiseAll(`Processing build data to be shared across langauges.`, queue( - targets.map(({ flag, pageSpec, target }) => () => { - const writes = pageSpec.write(target, {wikiData}) || []; + if (error) { + return; + } - // Do a quick valid8tion! If one of the writeThingPages functions go - // wrong, this will stall out early and tell us which did. + const validateWrites = (writes, fnName) => { + // Do a quick valid8tion! If one of the writeThingPages functions go + // wrong, this will stall out early and tell us which did. - if (!Array.isArray(writes)) { - logError`${flag + '.write'} didn't return an array!`; - error = true; - return []; - } + if (!Array.isArray(writes)) { + logError`${fnName} didn't return an array!`; + error = true; + return false; + } - if (!( - writes.every(obj => typeof obj === 'object') && - writes.every(obj => { - const result = validateWriteObject(obj); - if (result.error) { - logError`Validating write object failed: ${result.error}`; - return false; - } else { - return true; - } - }) - )) { - logError`${flag + '.write'} uses updated format, but entries are invalid!`; - error = true; + if (!( + writes.every(obj => typeof obj === 'object') && + writes.every(obj => { + const result = validateWriteObject(obj); + if (result.error) { + logError`Validating write object failed: ${result.error}`; + return false; + } else { + return true; + } + }) + )) { + logError`${fnName} returned invalid entries!`; + error = true; + return false; + } + + return true; + }; + + writes = buildStepsWithTargets.flatMap(({ flag, pageSpec, targets }) => { + const writes = targets.flatMap(target => + pageSpec.write(target, {wikiData}).slice() || []); + + if (!validateWrites(writes, flag + '.write')) { + return []; + } + + if (pageSpec.writeTargetless) { + const writes2 = pageSpec.writeTargetless({wikiData}); + + if (!validateWrites(writes2, flag + '.writeTargetless')) { return []; } - return writes; - }), - queueSize - )); + writes.push(...writes2); + } + + return writes; + }); if (error) { return; } - - writes = writeArrays.flatMap(writes => writes); } const pageWrites = writes.filter(({ type }) => type === 'page'); const dataWrites = writes.filter(({ type }) => type === 'data'); const redirectWrites = writes.filter(({ type }) => type === 'redirect'); + if (writes.length) { + logInfo`Total of ${writes.length} writes returned. (${pageWrites.length} page, ${dataWrites.length} data, ${redirectWrites.length} redirect)`; + } else { + logWarn`No writes returned at all, so exiting early. This is probably a bug!`; + return; + } + await progressPromiseAll(`Writing data files shared across languages.`, queue( dataWrites.map(({path, data}) => () => { const bound = {}; |