UNPKG

@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
'use strict' // 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 }