@antora/lunr-extension
Version:
An Antora extension that adds offline, full-text search powered by Lunr to your Antora documentation site.
121 lines (112 loc) • 4.73 kB
JavaScript
// The name of the package in order to give the Antora logger a useful name
const { name: packageName } = require('../package.json')
const fs = require('node:fs')
const { promises: fsp } = fs
const generateIndex = require('./generate-index')
const LazyReadable = require('./lazy-readable')
const MultiFileReadStream = require('./multi-file-read-stream')
const ospath = require('node:path')
const template = require('./template')
/**
* Lunr integration for an Antora documentation site.
*
* @module lunr-extension
*/
function register({
config: { languages, indexLatestOnly, extraStopWords, indexByHeading, snippetLength = 100, ...unknownOptions },
}) {
const logger = this.getLogger(packageName)
if (Object.keys(unknownOptions).length) {
const keys = Object.keys(unknownOptions)
throw new Error(`Unrecognized option${keys.length > 1 ? 's' : ''} specified for ${packageName}: ${keys.join(', ')}`)
}
this.on('uiLoaded', async ({ playbook, uiCatalog }) => {
playbook.env.SITE_SEARCH_PROVIDER = 'lunr'
const uiOutputDir = playbook.ui.outputDir
vendorJsFile(uiCatalog, logger, uiOutputDir, 'lunr/lunr.min.js', 'lunr.js')
// Extracts the language codes from the language-stopwords object
const languageCodes = (languages || []).map((it) => (it[Object.keys(it)] ? Object.keys(it)[0] : it))
const otherLanguages = languageCodes.filter((it) => it !== 'en')
if (otherLanguages.length) {
playbook.env.SITE_SEARCH_LANGUAGES = otherLanguages.join(',')
const languageRequires = []
languageRequires.push('lunr-languages/lunr.stemmer.support.js')
if (otherLanguages.includes('ja') || otherLanguages.includes('jp')) {
// needed for Japanese Support
languageRequires.push('lunr-languages/tinyseg.js')
}
if (otherLanguages.includes('th')) {
// needed for Thai support
languageRequires.push('lunr-languages/wordcut.js')
}
otherLanguages.map((lang) => languageRequires.push(`lunr-languages/lunr.${lang}.js`))
vendorJsFile(uiCatalog, logger, uiOutputDir, languageRequires, 'lunr-languages.js')
}
assetFile(uiCatalog, logger, uiOutputDir, 'css', 'search.css')
assetFile(uiCatalog, logger, uiOutputDir, 'js', 'search-ui.js')
const searchScriptsPartialPath = 'partials/search-scripts.hbs'
if (uiCatalog.findByType('partial').some(({ path }) => path === searchScriptsPartialPath)) return
const searchScriptsPartialFilepath = ospath.join(__dirname, '../data', searchScriptsPartialPath)
uiCatalog.addFile({
contents: Buffer.from(template(await fsp.readFile(searchScriptsPartialFilepath, 'utf8'), { snippetLength })),
path: searchScriptsPartialPath,
stem: 'search-scripts',
type: 'partial',
})
})
this.on('beforePublish', ({ playbook, siteCatalog, contentCatalog }) => {
delete playbook.env.SITE_SEARCH_PROVIDER
delete playbook.env.SITE_SEARCH_LANGUAGES
const index = generateIndex(playbook, contentCatalog, {
indexLatestOnly,
languages,
extraStopWords,
indexByHeading,
logger,
})
siteCatalog.addFile(generateIndex.createIndexFile(index))
})
}
function assetFile (
uiCatalog,
logger,
uiOutputDir,
assetDir,
basename,
assetPath = assetDir + '/' + basename,
contents = new LazyReadable(() => fs.createReadStream(ospath.join(__dirname, '../data', assetPath))),
overwrite = false
) {
const outputDir = uiOutputDir + '/' + assetDir
const existingFile = uiCatalog.findByType('asset').some(({ path }) => path === assetPath)
if (existingFile) {
if (overwrite) {
logger.warn(`Please remove the following file from your UI since it is managed by ${packageName}: ${assetPath}`)
existingFile.contents = contents
delete existingFile.stat
} else {
logger.info(`The following file already exists in your UI: ${assetPath}, skipping`)
}
} else {
uiCatalog.addFile({
contents,
type: 'asset',
path: assetPath,
out: { dirname: outputDir, path: outputDir + '/' + basename, basename },
})
}
}
function vendorJsFile (uiCatalog, logger, uiOutputDir, requireRequest, basename = requireRequest.split('/').pop()) {
let contents
if (Array.isArray(requireRequest)) {
const filepaths = requireRequest.map(require.resolve)
contents = new LazyReadable(() => new MultiFileReadStream(filepaths))
} else {
const filepath = require.resolve(requireRequest)
contents = new LazyReadable(() => fs.createReadStream(filepath))
}
const jsVendorDir = 'js/vendor'
assetFile(uiCatalog, logger, uiOutputDir, jsVendorDir, basename, jsVendorDir + '/' + basename, contents)
}
module.exports = { generateIndex, register }