зеркало из https://github.com/github/docs.git
83 строки
3.4 KiB
JavaScript
83 строки
3.4 KiB
JavaScript
import renderContent from './render-content/index.js'
|
|
import Page from './page.js'
|
|
import { TitleFromAutotitleError } from './render-content/plugins/rewrite-local-links.js'
|
|
|
|
class EmptyTitleError extends Error {}
|
|
|
|
const LIQUID_ERROR_NAMES = new Set(['RenderError', 'ParseError', 'TokenizationError'])
|
|
const isLiquidError = (error) =>
|
|
error instanceof Error && error.name && LIQUID_ERROR_NAMES.has(error.name)
|
|
|
|
const isAutotitleError = (error) => error instanceof TitleFromAutotitleError
|
|
const isEmptyTitleError = (error) => error instanceof EmptyTitleError
|
|
|
|
const isFallbackableError = (error) =>
|
|
isLiquidError(error) || isAutotitleError(error) || isEmptyTitleError(error)
|
|
|
|
// Returns a string by wrapping `renderContent()`. The input string to
|
|
// `renderContent` is one that contains Liquid and Markdown. The output
|
|
// is HTML.
|
|
// But what the wrapper does is that it watches out for possible Liquid
|
|
// related rendering errors AND if the context has been prepared with a
|
|
// sync callable that can yield the English equivalent.
|
|
// So it's up to how the `context` is prepared if it has a `getEnglishPage`
|
|
// function. This means, we can know, in the middleware (which is a
|
|
// highter level than `lib/`) how to use the URL to figure out the
|
|
// equivalent English page instance.
|
|
export async function renderContentWithFallback(page, property, context, options) {
|
|
if (!(page instanceof Page)) {
|
|
throw new Error(`The first argument has to be Page instance (not ${typeof page})`)
|
|
}
|
|
if (typeof property !== 'string') {
|
|
throw new Error(`The second argument has to be a string (not ${typeof property})`)
|
|
}
|
|
const template = page[property]
|
|
try {
|
|
const output = await renderContent(template, context, options)
|
|
if (options && options.throwIfEmpty && !output.trim()) {
|
|
throw new EmptyTitleError(`output for property '${property}' became empty`)
|
|
}
|
|
return output
|
|
} catch (error) {
|
|
// Only bother trying to fallback if it was an error we *can* fall back
|
|
// on English for.
|
|
if (isFallbackableError(error) && context.getEnglishPage) {
|
|
const enPage = context.getEnglishPage(context)
|
|
const englishTemplate = enPage[property]
|
|
// If you don't change the context, it'll confuse the liquid plugins
|
|
// like `data.js` that uses `environment.scope.currentLanguage`
|
|
const enContext = Object.assign({}, context, { currentLanguage: 'en' })
|
|
// Try again!
|
|
return await renderContent(englishTemplate, enContext, options)
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
|
|
// Returns the result of executing the first function, but if it fails
|
|
// return the result of executing the second function.
|
|
// In particular, "fails" means if it's deemed an error thrown that we
|
|
// can fall back for.
|
|
// When it executes the fallback function, it creates a shallow copy of
|
|
// the original `context` but with the `currentLanguage:'en'` set on it.
|
|
//
|
|
// You can use this function to do things like this:
|
|
//
|
|
// const title = await executeWithFallback(
|
|
// context,
|
|
// () => renderContent(track.title, context, renderOpts),
|
|
// (enContext) => renderContent(enTrack.title, enContext, renderOpts)
|
|
// )
|
|
//
|
|
export async function executeWithFallback(context, callable, fallback) {
|
|
try {
|
|
return await callable(context)
|
|
} catch (error) {
|
|
if (isFallbackableError(error) && context.currentLanguage !== 'en') {
|
|
const enContext = Object.assign({}, context, { currentLanguage: 'en' })
|
|
return await fallback(enContext)
|
|
}
|
|
throw error
|
|
}
|
|
}
|