diff --git a/.eslintignore b/.eslintignore index 54d3063e00..aeb0777246 100644 --- a/.eslintignore +++ b/.eslintignore @@ -6,6 +6,8 @@ node_modules packages/cli/src/lib/live-server/* +packages/cli/test/**/pagefind/**/*.js + # --- packages/core --- # Ignore JS files that are compiled from TS diff --git a/.eslintrc.js b/.eslintrc.js index 59369755a0..b8e6aa686e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,6 +2,7 @@ /* eslint quotes: ["error", "double"] */ module.exports = { + "ignorePatterns": ["docs/_site/**", "**/dist/**", "**/node_modules/**"], "env": { "node": true, "es6": true, diff --git a/.gitignore b/.gitignore index 118814adde..d351437352 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,9 @@ packages/core/template/*/_site # Generated site (MarkBind) packages/cli/test/functional/*/_site +# Generated pagefind directories for sites and in expected +packages/cli/test/functional/**/pagefind + # Ignore .page-vue-render.js files in functional test and subdirectories on update packages/cli/test/functional/**/*.page-vue-render.js @@ -117,6 +120,9 @@ packages/core/src/lib/markdown-it/patches/**/*.js .nx/cache .nx/workspace-data +# Pagefind fragments +*.pf_fragment + # AI Tools directories (symlinks) .opencode .cline diff --git a/.stylelintrc.js b/.stylelintrc.js index f3276222e8..b883f26813 100644 --- a/.stylelintrc.js +++ b/.stylelintrc.js @@ -6,5 +6,16 @@ module.exports = { // MarkBind generates some blank CSS files when initialising a site, // which violates the no-empty-source rule "no-empty-source": null - } + }, + "overrides": [ + { + // pagefind uses BEM-style class names (e.g., .pagefind-ui__result) as default. + // Since we currently style pagefind's default UI classes, we need to ignore the kebab-case rule here. + // This override should be removed once we no longer rely on pagefind's default CSS classes. + "files": ["**/pagefindSearchBar/**"], + "rules": { + "selector-class-pattern": null + } + } + ] }; diff --git a/docs/userGuide/makingTheSiteSearchable.md b/docs/userGuide/makingTheSiteSearchable.md index 4e28680c49..709939541f 100644 --- a/docs/userGuide/makingTheSiteSearchable.md +++ b/docs/userGuide/makingTheSiteSearchable.md @@ -37,6 +37,112 @@ You can add a search bar component to your website to allow users to search the + + + + +## Using Pagefind (Beta) + + +MarkBind now supports [Pagefind](https://pagefind.app/), a static low-bandwidth search library, as a built-in feature. This provides full-text search capabilities without external services. + + +This is a beta feature and will be refined in future updates. To use it, you must have enableSearch: true in your site.json (this is the default). + + + +The Pagefind index is currently only generated during a full site build (e.g., markbind build). It will not repeatedly update during live reload (markbind serve) when you modify pages. You must restart the server (re-run markbind serve) or rebuild to refresh the search index. + + +To add the Pagefind search bar to your page, simply insert the following element where you want it to appear: + +```md + +``` + +The following UI will be rendered, which is provided by Pagefind: + + + +
+ +### Ignoring Individual Elements from Pagefind Search + +You can exclude specific elements from the search index by adding the `data-pagefind-ignore` attribute to them: + +```html +
+

This content will be in your search index.

+
+ This content and all its children will be excluded from search. +
+
+``` + +For more details, see the [Pagefind documentation on removing individual elements](https://pagefind.app/docs/indexing/#removing-individual-elements-from-the-index). + +### Using Pagefind Configuration + +You can customize Pagefind's indexing behavior by adding a `pagefind` configuration in your `site.json`. This allows you to control which content is indexed and how search works. + +#### Excluding Content from Search Index + +You can use the `exclude_selectors` option to exclude specific elements from the search index. This is useful if you are migrating from Algolia and want to reuse your existing CSS class selectors. + +In your `site.json`: + +```json +{ + "pagefind": { + "exclude_selectors": [".algolia-no-index", "[class*='algolia-no-index']"] + } +} +``` + +This tells Pagefind to exclude any element with the `algolia-no-index` class (or containing it in a space-separated list) from the search index, similar to using `data-pagefind-ignore`. + +#### Limiting Which Pages Are Searchable + +You can use the `glob` option to limit which pages are indexed by Pagefind. This is useful when you want search results to only show pages from specific sections of your site. + +In your `site.json`: + +```json +{ + "pagefind": { + "glob": [ + "devGuide", + "userGuide/*" + ] + } +} +``` + +MarkBind supports glob patterns and will automatically append `.html` to your patterns if not specified. For example: +- `"devGuide"` becomes `"devGuide/**/*.html"` +- `"devGuide/*"` becomes `"devGuide/*.html"` +- `"**/devGuide/**"` becomes `"**/devGuide/**/*.html"` +- `"*.html"` remains `"*.html"` (no change needed) + +Only pages matching these glob patterns will appear in search results. This can be particularly useful for: +- Multi-site setups where you want to search only specific sections +- Including only certain directories from search results + +For more details on glob patterns, see the [Pagefind documentation](https://pagefind.app/docs/config-options/#glob). + + + +Additional Pagefind configuration options may be supported in future releases: + +- **`root_selector`**: Allows specifying a custom root element for indexing (default: `html`). Useful for sites with specific content containers. +- **`force_language`**: Forces a specific language for indexing (e.g., `"en"`, `"pt"`). Improves search accuracy for multilingual sites. + + + + + +
+ ## Using External Search Services MarkBind sites can use Algolia Doc Search services easily via the Algolia plugin. Unlike the built-in search, Algolia provides full-text search. See the panel below for more info. diff --git a/package-lock.json b/package-lock.json index ac24fad8e4..e6e19519ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4753,6 +4753,84 @@ "dev": true, "license": "MIT" }, + "node_modules/@pagefind/darwin-arm64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.4.0.tgz", + "integrity": "sha512-2vMqkbv3lbx1Awea90gTaBsvpzgRs7MuSgKDxW0m9oV1GPZCZbZBJg/qL83GIUEN2BFlY46dtUZi54pwH+/pTQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.4.0.tgz", + "integrity": "sha512-e7JPIS6L9/cJfow+/IAqknsGqEPjJnVXGjpGm25bnq+NPdoD3c/7fAwr1OXkG4Ocjx6ZGSCijXEV4ryMcH2E3A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/freebsd-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.4.0.tgz", + "integrity": "sha512-WcJVypXSZ+9HpiqZjFXMUobfFfZZ6NzIYtkhQ9eOhZrQpeY5uQFqNWLCk7w9RkMUwBv1HAMDW3YJQl/8OqsV0Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@pagefind/linux-arm64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.4.0.tgz", + "integrity": "sha512-PIt8dkqt4W06KGmQjONw7EZbhDF+uXI7i0XtRLN1vjCUxM9vGPdtJc2mUyVPevjomrGz5M86M8bqTr6cgDp1Uw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/linux-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.4.0.tgz", + "integrity": "sha512-z4oddcWwQ0UHrTHR8psLnVlz6USGJ/eOlDPTDYZ4cI8TK8PgwRUPQZp9D2iJPNIPcS6Qx/E4TebjuGJOyK8Mmg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.4.0.tgz", + "integrity": "sha512-NkT+YAdgS2FPCn8mIA9bQhiBs+xmniMGq1LFPDhcFn0+2yIUEiIG06t7bsZlhdjknEQRTSdT7YitP6fC5qwP0g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "dev": true, @@ -16722,6 +16800,23 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/pagefind": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.4.0.tgz", + "integrity": "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g==", + "license": "MIT", + "bin": { + "pagefind": "lib/runner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.4.0", + "@pagefind/darwin-x64": "1.4.0", + "@pagefind/freebsd-x64": "1.4.0", + "@pagefind/linux-arm64": "1.4.0", + "@pagefind/linux-x64": "1.4.0", + "@pagefind/windows-x64": "1.4.0" + } + }, "node_modules/parent-module": { "version": "1.0.1", "dev": true, @@ -22140,6 +22235,7 @@ "material-icons": "^1.9.1", "moment": "^2.29.4", "nunjucks": "3.2.4", + "pagefind": "^1.4.0", "path-is-inside": "^1.0.2", "simple-git": "^3.22.0", "url-parse": "^1.5.10", diff --git a/packages/cli/test/functional/test.ts b/packages/cli/test/functional/test.ts index 8db339da13..80fd9f774d 100644 --- a/packages/cli/test/functional/test.ts +++ b/packages/cli/test/functional/test.ts @@ -50,12 +50,14 @@ expectedErrors.forEach((error, index) => { logger.info(`${index + 1}: ${error}`); }); +const GENERATED_DIRECTORIES_TO_IGNORE = ['**/pagefind/**']; + testSites.forEach((siteName) => { console.log(`Running ${siteName} tests`); try { execSync(`node ${CLI_PATH} build ${siteName}`, execOptions); const siteIgnoredFiles = plantumlGeneratedFilesForTestSites[siteName]; - compare(siteName, 'expected', '_site', siteIgnoredFiles); + compare(siteName, 'expected', '_site', siteIgnoredFiles, GENERATED_DIRECTORIES_TO_IGNORE); } catch (err) { if (_.isError(err)) { printFailedMessage(err, siteName); @@ -74,7 +76,8 @@ testConvertSites.forEach((sitePath) => { execSync(`node ${CLI_PATH} init ${nonMarkBindSitePath} -c`, execOptions); execSync(`node ${CLI_PATH} build ${nonMarkBindSitePath}`, execOptions); const siteIgnoredFiles = plantumlGeneratedFilesForConvertSites[siteName]; - compare(sitePath, 'expected', 'non_markbind_site/_site', siteIgnoredFiles); + compare(sitePath, 'expected', 'non_markbind_site/_site', siteIgnoredFiles, + GENERATED_DIRECTORIES_TO_IGNORE); } catch (err) { if (_.isError(err)) { printFailedMessage(err, sitePath); @@ -98,7 +101,7 @@ testTemplateSites.forEach((templateAndSitePath) => { execSync(`node ${CLI_PATH} init ${siteCreationTempPath} --template ${flag}`, execOptions); execSync(`node ${CLI_PATH} build ${siteCreationTempPath}`, execOptions); const siteIgnoredFiles = plantumlGeneratedFilesForTemplateSites[siteName]; - compare(sitePath, 'expected', 'tmp/_site', siteIgnoredFiles); + compare(sitePath, 'expected', 'tmp/_site', siteIgnoredFiles, GENERATED_DIRECTORIES_TO_IGNORE); } catch (err) { if (_.isError(err)) { printFailedMessage(err, sitePath); @@ -141,7 +144,7 @@ function testEmptyDirectoryBuild() { } catch (err) { // Verify that test_empty directory remains empty using compare() try { - compare(siteRootName, 'expected', 'empty_dir', [], true); + compare(siteRootName, 'expected', 'empty_dir', [], [], true); } catch (compareErr) { if (_.isError(compareErr)) { printFailedMessage(compareErr, siteRootName); diff --git a/packages/cli/test/functional/testUtil/compare.ts b/packages/cli/test/functional/testUtil/compare.ts index d522f5a19c..c631b14f7b 100644 --- a/packages/cli/test/functional/testUtil/compare.ts +++ b/packages/cli/test/functional/testUtil/compare.ts @@ -13,6 +13,11 @@ const TEST_BLACKLIST = ignore().add([ '*.log', '*.woff', '*.woff2', + '*.pf_fragment', + '*.pf_index', + '*.pf_meta', + '*.wasm.pagefind', + 'wasm.unknown.pagefind', ]); const CRLF_REGEX = /\r\n/g; @@ -54,10 +59,12 @@ function getDirectoryStructure(dirPath: string) { * @param {string} expectedSiteRelativePath - Relative path to expected site output (default: "expected") * @param {string} siteRelativePath - Relative path to actual generated site output (default: "_site") * @param {string[]} ignoredPaths - Specify any paths to ignore for comparison, but still check for existence. + * @param {string[]} ignoredDirectories - Specify any directories to ignore for comparison (e.g. 'pagefind') * @param {boolean} compareDirectories - Whether to compare directory structures (default: false) */ function compare(root: string, expectedSiteRelativePath = 'expected', siteRelativePath = '_site', - ignoredPaths: string[] = [], compareDirectories = false) { + ignoredPaths: string[] = [], ignoredDirectories: string[] = [], + compareDirectories = false) { const expectedDirectory = path.join(root, expectedSiteRelativePath); const actualDirectory = path.join(root, siteRelativePath); @@ -73,8 +80,9 @@ function compare(root: string, expectedSiteRelativePath = 'expected', siteRelati } } - let expectedPaths = walkSync(expectedDirectory, { directories: false }); - let actualPaths = walkSync(actualDirectory, { directories: false }); + const walkSyncOptions = { directories: false, ignore: ignoredDirectories }; + let expectedPaths = walkSync(expectedDirectory, walkSyncOptions); + let actualPaths = walkSync(actualDirectory, walkSyncOptions); // Vue render JS files (*.page-vue-render.js) are not committed to version control, // so we exclude them from the comparison to avoid false positive diffs. diff --git a/packages/cli/test/functional/test_site/expected/bugs/index.html b/packages/cli/test/functional/test_site/expected/bugs/index.html index 7d3c9c86d9..32e743057d 100644 --- a/packages/cli/test/functional/test_site/expected/bugs/index.html +++ b/packages/cli/test/functional/test_site/expected/bugs/index.html @@ -16,6 +16,7 @@ + @@ -359,5 +360,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/index.html b/packages/cli/test/functional/test_site/expected/index.html index a5f7e0cade..2a68ba9cf4 100644 --- a/packages/cli/test/functional/test_site/expected/index.html +++ b/packages/cli/test/functional/test_site/expected/index.html @@ -16,6 +16,7 @@ + @@ -1025,5 +1026,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/sub_site/index.html b/packages/cli/test/functional/test_site/expected/sub_site/index.html index d7eb616457..31ada973da 100644 --- a/packages/cli/test/functional/test_site/expected/sub_site/index.html +++ b/packages/cli/test/functional/test_site/expected/sub_site/index.html @@ -15,6 +15,7 @@ + @@ -366,5 +367,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/sub_site/nested_sub_site/index.html b/packages/cli/test/functional/test_site/expected/sub_site/nested_sub_site/index.html index 8e9dfb243b..3734b76de8 100644 --- a/packages/cli/test/functional/test_site/expected/sub_site/nested_sub_site/index.html +++ b/packages/cli/test/functional/test_site/expected/sub_site/nested_sub_site/index.html @@ -15,6 +15,7 @@ + @@ -357,5 +358,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/sub_site/nested_sub_site/testNunjucksPathResolving.html b/packages/cli/test/functional/test_site/expected/sub_site/nested_sub_site/testNunjucksPathResolving.html index a3038969fb..38608aceb0 100644 --- a/packages/cli/test/functional/test_site/expected/sub_site/nested_sub_site/testNunjucksPathResolving.html +++ b/packages/cli/test/functional/test_site/expected/sub_site/nested_sub_site/testNunjucksPathResolving.html @@ -16,6 +16,7 @@ + @@ -363,5 +364,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/sub_site/testNunjucksPathResolving.html b/packages/cli/test/functional/test_site/expected/sub_site/testNunjucksPathResolving.html index a3038969fb..38608aceb0 100644 --- a/packages/cli/test/functional/test_site/expected/sub_site/testNunjucksPathResolving.html +++ b/packages/cli/test/functional/test_site/expected/sub_site/testNunjucksPathResolving.html @@ -16,6 +16,7 @@ + @@ -363,5 +364,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testAltFrontMatterInvalidKeyValue.html b/packages/cli/test/functional/test_site/expected/testAltFrontMatterInvalidKeyValue.html index e6c83d7e9d..f95952c9f0 100644 --- a/packages/cli/test/functional/test_site/expected/testAltFrontMatterInvalidKeyValue.html +++ b/packages/cli/test/functional/test_site/expected/testAltFrontMatterInvalidKeyValue.html @@ -16,6 +16,7 @@ + @@ -357,5 +358,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testAltFrontMatterParsing.html b/packages/cli/test/functional/test_site/expected/testAltFrontMatterParsing.html index 59c5c1b1cb..75ba02c030 100644 --- a/packages/cli/test/functional/test_site/expected/testAltFrontMatterParsing.html +++ b/packages/cli/test/functional/test_site/expected/testAltFrontMatterParsing.html @@ -16,6 +16,7 @@ + @@ -356,5 +357,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testAnchorGeneration.html b/packages/cli/test/functional/test_site/expected/testAnchorGeneration.html index cc5b09c804..e650797621 100644 --- a/packages/cli/test/functional/test_site/expected/testAnchorGeneration.html +++ b/packages/cli/test/functional/test_site/expected/testAnchorGeneration.html @@ -16,6 +16,7 @@ + @@ -425,5 +426,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testAnnotate.html b/packages/cli/test/functional/test_site/expected/testAnnotate.html index e91c04c0f6..2720c6d11f 100644 --- a/packages/cli/test/functional/test_site/expected/testAnnotate.html +++ b/packages/cli/test/functional/test_site/expected/testAnnotate.html @@ -16,6 +16,7 @@ + @@ -536,5 +537,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testAntiFOUCStyles.html b/packages/cli/test/functional/test_site/expected/testAntiFOUCStyles.html index 91ad22d130..e64e564f1d 100644 --- a/packages/cli/test/functional/test_site/expected/testAntiFOUCStyles.html +++ b/packages/cli/test/functional/test_site/expected/testAntiFOUCStyles.html @@ -16,6 +16,7 @@ + @@ -380,5 +381,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testBootstrapIconInPage.html b/packages/cli/test/functional/test_site/expected/testBootstrapIconInPage.html index bb24c7fcb9..03d7dfb182 100644 --- a/packages/cli/test/functional/test_site/expected/testBootstrapIconInPage.html +++ b/packages/cli/test/functional/test_site/expected/testBootstrapIconInPage.html @@ -14,6 +14,7 @@ + @@ -195,5 +196,6 @@ }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testCenterText.html b/packages/cli/test/functional/test_site/expected/testCenterText.html index c035854ba2..8d4aead703 100644 --- a/packages/cli/test/functional/test_site/expected/testCenterText.html +++ b/packages/cli/test/functional/test_site/expected/testCenterText.html @@ -16,6 +16,7 @@ + @@ -356,5 +357,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testCodeBlocks.html b/packages/cli/test/functional/test_site/expected/testCodeBlocks.html index 0e092c9b39..88a1ed3818 100644 --- a/packages/cli/test/functional/test_site/expected/testCodeBlocks.html +++ b/packages/cli/test/functional/test_site/expected/testCodeBlocks.html @@ -16,6 +16,7 @@ + @@ -579,5 +580,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testDates.html b/packages/cli/test/functional/test_site/expected/testDates.html index dbb0ee66dc..554b73b8c4 100644 --- a/packages/cli/test/functional/test_site/expected/testDates.html +++ b/packages/cli/test/functional/test_site/expected/testDates.html @@ -16,6 +16,7 @@ + @@ -362,5 +363,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testEmptyAltFrontMatter.html b/packages/cli/test/functional/test_site/expected/testEmptyAltFrontMatter.html index 14bc7eea84..bf52675edc 100644 --- a/packages/cli/test/functional/test_site/expected/testEmptyAltFrontMatter.html +++ b/packages/cli/test/functional/test_site/expected/testEmptyAltFrontMatter.html @@ -19,6 +19,7 @@ + @@ -363,5 +364,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testEmptyFrontmatter.html b/packages/cli/test/functional/test_site/expected/testEmptyFrontmatter.html index 3c443bf29e..03e11d7c2e 100644 --- a/packages/cli/test/functional/test_site/expected/testEmptyFrontmatter.html +++ b/packages/cli/test/functional/test_site/expected/testEmptyFrontmatter.html @@ -19,6 +19,7 @@ + @@ -363,5 +364,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testExternalScripts.html b/packages/cli/test/functional/test_site/expected/testExternalScripts.html index 1ab7c4dbeb..8f4c64b1aa 100644 --- a/packages/cli/test/functional/test_site/expected/testExternalScripts.html +++ b/packages/cli/test/functional/test_site/expected/testExternalScripts.html @@ -19,6 +19,7 @@ + @@ -367,5 +368,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testFontAwesomeInPage.html b/packages/cli/test/functional/test_site/expected/testFontAwesomeInPage.html index 8f1b26d54d..77e7a81679 100644 --- a/packages/cli/test/functional/test_site/expected/testFontAwesomeInPage.html +++ b/packages/cli/test/functional/test_site/expected/testFontAwesomeInPage.html @@ -14,6 +14,7 @@ + @@ -200,5 +201,6 @@ }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testGlyphiconInPage.html b/packages/cli/test/functional/test_site/expected/testGlyphiconInPage.html index 9bfbdf6298..62c9caca40 100644 --- a/packages/cli/test/functional/test_site/expected/testGlyphiconInPage.html +++ b/packages/cli/test/functional/test_site/expected/testGlyphiconInPage.html @@ -14,6 +14,7 @@ + @@ -197,5 +198,6 @@ }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testHr.html b/packages/cli/test/functional/test_site/expected/testHr.html index 51219d6d60..e48627dda5 100644 --- a/packages/cli/test/functional/test_site/expected/testHr.html +++ b/packages/cli/test/functional/test_site/expected/testHr.html @@ -16,6 +16,7 @@ + @@ -365,5 +366,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testIconsInSiteLayout.html b/packages/cli/test/functional/test_site/expected/testIconsInSiteLayout.html index d3f58857c2..049dc7927d 100644 --- a/packages/cli/test/functional/test_site/expected/testIconsInSiteLayout.html +++ b/packages/cli/test/functional/test_site/expected/testIconsInSiteLayout.html @@ -15,6 +15,7 @@ + @@ -199,5 +200,6 @@ }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testImages.html b/packages/cli/test/functional/test_site/expected/testImages.html index 3663badc40..7e5b07e0df 100644 --- a/packages/cli/test/functional/test_site/expected/testImages.html +++ b/packages/cli/test/functional/test_site/expected/testImages.html @@ -16,6 +16,7 @@ + @@ -363,5 +364,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testIncludeBoilerplate.html b/packages/cli/test/functional/test_site/expected/testIncludeBoilerplate.html index d3b2f2ebb2..45e284aff3 100644 --- a/packages/cli/test/functional/test_site/expected/testIncludeBoilerplate.html +++ b/packages/cli/test/functional/test_site/expected/testIncludeBoilerplate.html @@ -16,6 +16,7 @@ + @@ -447,5 +448,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testIncludeMultipleModals.html b/packages/cli/test/functional/test_site/expected/testIncludeMultipleModals.html index b356acd97d..e10f26bebf 100644 --- a/packages/cli/test/functional/test_site/expected/testIncludeMultipleModals.html +++ b/packages/cli/test/functional/test_site/expected/testIncludeMultipleModals.html @@ -16,6 +16,7 @@ + @@ -362,5 +363,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testIncludePluginsRendered.html b/packages/cli/test/functional/test_site/expected/testIncludePluginsRendered.html index da1c4270cd..a2e04a094f 100644 --- a/packages/cli/test/functional/test_site/expected/testIncludePluginsRendered.html +++ b/packages/cli/test/functional/test_site/expected/testIncludePluginsRendered.html @@ -16,6 +16,7 @@ + @@ -357,5 +358,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testLayouts.html b/packages/cli/test/functional/test_site/expected/testLayouts.html index 18bdd7253d..96779f963d 100644 --- a/packages/cli/test/functional/test_site/expected/testLayouts.html +++ b/packages/cli/test/functional/test_site/expected/testLayouts.html @@ -19,6 +19,7 @@ + @@ -363,5 +364,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testLayoutsOverride.html b/packages/cli/test/functional/test_site/expected/testLayoutsOverride.html index 3aa15700c6..0c548051ef 100644 --- a/packages/cli/test/functional/test_site/expected/testLayoutsOverride.html +++ b/packages/cli/test/functional/test_site/expected/testLayoutsOverride.html @@ -19,6 +19,7 @@ + @@ -363,5 +364,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testLayoutsOverrideWithAltFrontmatter.html b/packages/cli/test/functional/test_site/expected/testLayoutsOverrideWithAltFrontmatter.html index 1f662a2ce9..46d28ee8a1 100644 --- a/packages/cli/test/functional/test_site/expected/testLayoutsOverrideWithAltFrontmatter.html +++ b/packages/cli/test/functional/test_site/expected/testLayoutsOverrideWithAltFrontmatter.html @@ -19,6 +19,7 @@ + @@ -363,5 +364,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testLayoutsWithAltFrontMatter.html b/packages/cli/test/functional/test_site/expected/testLayoutsWithAltFrontMatter.html index cf92fd1ee5..8692e0e011 100644 --- a/packages/cli/test/functional/test_site/expected/testLayoutsWithAltFrontMatter.html +++ b/packages/cli/test/functional/test_site/expected/testLayoutsWithAltFrontMatter.html @@ -19,6 +19,7 @@ + @@ -363,5 +364,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testLinks.html b/packages/cli/test/functional/test_site/expected/testLinks.html index 34cef2f298..7f083c94a2 100644 --- a/packages/cli/test/functional/test_site/expected/testLinks.html +++ b/packages/cli/test/functional/test_site/expected/testLinks.html @@ -16,6 +16,7 @@ + @@ -366,5 +367,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testList.html b/packages/cli/test/functional/test_site/expected/testList.html index 8f3cff9165..4b3544ffd3 100644 --- a/packages/cli/test/functional/test_site/expected/testList.html +++ b/packages/cli/test/functional/test_site/expected/testList.html @@ -16,6 +16,7 @@ + @@ -1294,5 +1295,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testMaterialIconsInPage.html b/packages/cli/test/functional/test_site/expected/testMaterialIconsInPage.html index 7fc1572275..cc2bf50765 100644 --- a/packages/cli/test/functional/test_site/expected/testMaterialIconsInPage.html +++ b/packages/cli/test/functional/test_site/expected/testMaterialIconsInPage.html @@ -14,6 +14,7 @@ + @@ -195,5 +196,6 @@ }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testMath.html b/packages/cli/test/functional/test_site/expected/testMath.html index da2a809f19..0d9d1f3810 100644 --- a/packages/cli/test/functional/test_site/expected/testMath.html +++ b/packages/cli/test/functional/test_site/expected/testMath.html @@ -16,6 +16,7 @@ + @@ -622,5 +623,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testMermaid.html b/packages/cli/test/functional/test_site/expected/testMermaid.html index 03dfd4bb1f..cdb5a896c4 100644 --- a/packages/cli/test/functional/test_site/expected/testMermaid.html +++ b/packages/cli/test/functional/test_site/expected/testMermaid.html @@ -16,6 +16,7 @@ + @@ -441,5 +442,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testModals.html b/packages/cli/test/functional/test_site/expected/testModals.html index a93bb11e28..7f39286199 100644 --- a/packages/cli/test/functional/test_site/expected/testModals.html +++ b/packages/cli/test/functional/test_site/expected/testModals.html @@ -16,6 +16,7 @@ + @@ -498,5 +499,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testNunjucksPathResolving.html b/packages/cli/test/functional/test_site/expected/testNunjucksPathResolving.html index a3038969fb..38608aceb0 100644 --- a/packages/cli/test/functional/test_site/expected/testNunjucksPathResolving.html +++ b/packages/cli/test/functional/test_site/expected/testNunjucksPathResolving.html @@ -16,6 +16,7 @@ + @@ -363,5 +364,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testOcticonInPage.html b/packages/cli/test/functional/test_site/expected/testOcticonInPage.html index 89b02fcffa..58504f38d3 100644 --- a/packages/cli/test/functional/test_site/expected/testOcticonInPage.html +++ b/packages/cli/test/functional/test_site/expected/testOcticonInPage.html @@ -14,6 +14,7 @@ + @@ -208,5 +209,6 @@ }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testPageNav.html b/packages/cli/test/functional/test_site/expected/testPageNav.html index 165081d2fc..16a4e3d5ac 100644 --- a/packages/cli/test/functional/test_site/expected/testPageNav.html +++ b/packages/cli/test/functional/test_site/expected/testPageNav.html @@ -16,6 +16,7 @@ + @@ -371,5 +372,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testPageNavPrint.html b/packages/cli/test/functional/test_site/expected/testPageNavPrint.html index 1d04e797a9..0ca0bd736f 100644 --- a/packages/cli/test/functional/test_site/expected/testPageNavPrint.html +++ b/packages/cli/test/functional/test_site/expected/testPageNavPrint.html @@ -16,6 +16,7 @@ + @@ -369,5 +370,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testPageNavTarget.html b/packages/cli/test/functional/test_site/expected/testPageNavTarget.html index 84642d2602..65301b6e9e 100644 --- a/packages/cli/test/functional/test_site/expected/testPageNavTarget.html +++ b/packages/cli/test/functional/test_site/expected/testPageNavTarget.html @@ -16,6 +16,7 @@ + @@ -359,5 +360,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testPageNavWithOnlyTitle.html b/packages/cli/test/functional/test_site/expected/testPageNavWithOnlyTitle.html index 4a1fab5ffb..f8514a77da 100644 --- a/packages/cli/test/functional/test_site/expected/testPageNavWithOnlyTitle.html +++ b/packages/cli/test/functional/test_site/expected/testPageNavWithOnlyTitle.html @@ -16,6 +16,7 @@ + @@ -359,5 +360,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testPageNavWithoutTitleAndNavHeadings.html b/packages/cli/test/functional/test_site/expected/testPageNavWithoutTitleAndNavHeadings.html index 6ffb17cbf7..6939cbca7c 100644 --- a/packages/cli/test/functional/test_site/expected/testPageNavWithoutTitleAndNavHeadings.html +++ b/packages/cli/test/functional/test_site/expected/testPageNavWithoutTitleAndNavHeadings.html @@ -16,6 +16,7 @@ + @@ -355,5 +356,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testPanelMarkdownParsing.html b/packages/cli/test/functional/test_site/expected/testPanelMarkdownParsing.html index 1002eb7762..6621bcb926 100644 --- a/packages/cli/test/functional/test_site/expected/testPanelMarkdownParsing.html +++ b/packages/cli/test/functional/test_site/expected/testPanelMarkdownParsing.html @@ -16,6 +16,7 @@ + @@ -413,5 +414,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testPanels.html b/packages/cli/test/functional/test_site/expected/testPanels.html index e49b6cd4eb..a7f5d1076a 100644 --- a/packages/cli/test/functional/test_site/expected/testPanels.html +++ b/packages/cli/test/functional/test_site/expected/testPanels.html @@ -16,6 +16,7 @@ + @@ -371,5 +372,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testPanelsClosingTransition.html b/packages/cli/test/functional/test_site/expected/testPanelsClosingTransition.html index 63988c168f..80501edb40 100644 --- a/packages/cli/test/functional/test_site/expected/testPanelsClosingTransition.html +++ b/packages/cli/test/functional/test_site/expected/testPanelsClosingTransition.html @@ -16,6 +16,7 @@ + @@ -399,5 +400,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testPlantUML.html b/packages/cli/test/functional/test_site/expected/testPlantUML.html index 3e8d398270..ae43b60151 100644 --- a/packages/cli/test/functional/test_site/expected/testPlantUML.html +++ b/packages/cli/test/functional/test_site/expected/testPlantUML.html @@ -16,6 +16,7 @@ + @@ -361,5 +362,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testPopoverTrigger.html b/packages/cli/test/functional/test_site/expected/testPopoverTrigger.html index 33705e8118..eb257f4edf 100644 --- a/packages/cli/test/functional/test_site/expected/testPopoverTrigger.html +++ b/packages/cli/test/functional/test_site/expected/testPopoverTrigger.html @@ -16,6 +16,7 @@ + @@ -361,5 +362,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testPopovers.html b/packages/cli/test/functional/test_site/expected/testPopovers.html index 83c46c2aea..b16cee8dc4 100644 --- a/packages/cli/test/functional/test_site/expected/testPopovers.html +++ b/packages/cli/test/functional/test_site/expected/testPopovers.html @@ -16,6 +16,7 @@ + @@ -446,5 +447,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testSingleAltFrontMatter.html b/packages/cli/test/functional/test_site/expected/testSingleAltFrontMatter.html index 44ae7ec39f..0b941c2bd6 100644 --- a/packages/cli/test/functional/test_site/expected/testSingleAltFrontMatter.html +++ b/packages/cli/test/functional/test_site/expected/testSingleAltFrontMatter.html @@ -16,6 +16,7 @@ + @@ -354,5 +355,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testSourceContainScript.html b/packages/cli/test/functional/test_site/expected/testSourceContainScript.html index 8653081494..2d0848e35e 100644 --- a/packages/cli/test/functional/test_site/expected/testSourceContainScript.html +++ b/packages/cli/test/functional/test_site/expected/testSourceContainScript.html @@ -16,6 +16,7 @@ + @@ -365,5 +366,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testThumbnails.html b/packages/cli/test/functional/test_site/expected/testThumbnails.html index ec394d4126..1985c3f05c 100644 --- a/packages/cli/test/functional/test_site/expected/testThumbnails.html +++ b/packages/cli/test/functional/test_site/expected/testThumbnails.html @@ -16,6 +16,7 @@ + @@ -453,5 +454,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testTooltipSpacing.html b/packages/cli/test/functional/test_site/expected/testTooltipSpacing.html index c3b9bc9527..3d3fcb6f5f 100644 --- a/packages/cli/test/functional/test_site/expected/testTooltipSpacing.html +++ b/packages/cli/test/functional/test_site/expected/testTooltipSpacing.html @@ -16,6 +16,7 @@ + @@ -363,5 +364,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testTree.html b/packages/cli/test/functional/test_site/expected/testTree.html index dfa5b00fed..b0ce89a0ee 100644 --- a/packages/cli/test/functional/test_site/expected/testTree.html +++ b/packages/cli/test/functional/test_site/expected/testTree.html @@ -16,6 +16,7 @@ + @@ -428,5 +429,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testVariableContainsInclude.html b/packages/cli/test/functional/test_site/expected/testVariableContainsInclude.html index 66f546887a..14c6877edf 100644 --- a/packages/cli/test/functional/test_site/expected/testVariableContainsInclude.html +++ b/packages/cli/test/functional/test_site/expected/testVariableContainsInclude.html @@ -16,6 +16,7 @@ + @@ -353,5 +354,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/testWeb3FormPlugin.html b/packages/cli/test/functional/test_site/expected/testWeb3FormPlugin.html index ab849bf179..5a0745c94f 100644 --- a/packages/cli/test/functional/test_site/expected/testWeb3FormPlugin.html +++ b/packages/cli/test/functional/test_site/expected/testWeb3FormPlugin.html @@ -16,6 +16,7 @@ + @@ -407,5 +408,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/test_md_fragment.html b/packages/cli/test/functional/test_site/expected/test_md_fragment.html index 78a3d7fde9..31e4586b29 100644 --- a/packages/cli/test/functional/test_site/expected/test_md_fragment.html +++ b/packages/cli/test/functional/test_site/expected/test_md_fragment.html @@ -15,6 +15,7 @@ + @@ -353,5 +354,6 @@

Heading in footer should not be }); + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_algolia_plugin/expected/index.html b/packages/cli/test/functional/test_site_algolia_plugin/expected/index.html index 16407ec409..b32640633d 100644 --- a/packages/cli/test/functional/test_site_algolia_plugin/expected/index.html +++ b/packages/cli/test/functional/test_site_algolia_plugin/expected/index.html @@ -12,6 +12,7 @@ + @@ -138,4 +139,6 @@ document.getElementsByTagName('head')[0].appendChild(style); + + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/404.html b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/404.html index c605590550..2ab9e81265 100644 --- a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/404.html +++ b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/404.html @@ -11,6 +11,7 @@ + @@ -82,5 +83,6 @@ + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/Home.html b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/Home.html index 8decc9d47f..cd81d441c7 100644 --- a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/Home.html +++ b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/Home.html @@ -10,6 +10,7 @@ + @@ -73,5 +74,6 @@ + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/Page-1.html b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/Page-1.html index 28ec10938f..08af6259d7 100644 --- a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/Page-1.html +++ b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/Page-1.html @@ -10,6 +10,7 @@ + @@ -73,5 +74,6 @@

Page 1 \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/_Footer.html b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/_Footer.html index 74184d9c0d..f7e802da47 100644 --- a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/_Footer.html +++ b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/_Footer.html @@ -10,6 +10,7 @@ + @@ -73,5 +74,6 @@ + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/_Sidebar.html b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/_Sidebar.html index 99b35c8678..e5ec458162 100644 --- a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/_Sidebar.html +++ b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/_Sidebar.html @@ -10,6 +10,7 @@ + @@ -76,5 +77,6 @@ + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/about.html b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/about.html index 48f05caae7..e73b5612ab 100644 --- a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/about.html +++ b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/about.html @@ -10,6 +10,7 @@ + @@ -74,5 +75,6 @@

About \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/contents/topic1.html b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/contents/topic1.html index 1fa10b03ec..0e19c0e15c 100644 --- a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/contents/topic1.html +++ b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/contents/topic1.html @@ -10,6 +10,7 @@ + @@ -78,5 +79,6 @@

Topic 1 \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/contents/topic2.html b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/contents/topic2.html index 391d02a22f..f25bb0febd 100644 --- a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/contents/topic2.html +++ b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/contents/topic2.html @@ -10,6 +10,7 @@ + @@ -77,5 +78,6 @@ + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/contents/topic3a.html b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/contents/topic3a.html index df82c37810..1e9ccdd891 100644 --- a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/contents/topic3a.html +++ b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/contents/topic3a.html @@ -10,6 +10,7 @@ + @@ -77,5 +78,6 @@ + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/contents/topic3b.html b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/contents/topic3b.html index 6c385bf249..8af123b0cd 100644 --- a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/contents/topic3b.html +++ b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/contents/topic3b.html @@ -10,6 +10,7 @@ + @@ -77,5 +78,6 @@ + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/index.html b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/index.html index 3de8febfeb..b1c0715985 100644 --- a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/index.html +++ b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/index.html @@ -10,6 +10,7 @@ + @@ -73,5 +74,6 @@ + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/404.html b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/404.html index 3433167f6d..b8f55949e2 100644 --- a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/404.html +++ b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/404.html @@ -11,6 +11,7 @@ + @@ -119,5 +120,6 @@ + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/Home.html b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/Home.html index 52d31af4dd..63ec5a7f13 100644 --- a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/Home.html +++ b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/Home.html @@ -10,6 +10,7 @@ + @@ -110,5 +111,6 @@ + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/Page-1.html b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/Page-1.html index 8544472b4f..b130f9ac75 100644 --- a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/Page-1.html +++ b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/Page-1.html @@ -10,6 +10,7 @@ + @@ -110,5 +111,6 @@

Page 1 \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/README.html b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/README.html index a2e22e255e..849d441cad 100644 --- a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/README.html +++ b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/README.html @@ -10,6 +10,7 @@ + @@ -111,5 +112,6 @@

Readme \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/about.html b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/about.html index 1f572049bb..46ad53d71b 100644 --- a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/about.html +++ b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/about.html @@ -10,6 +10,7 @@ + @@ -111,5 +112,6 @@

About \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/contents/topic1.html b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/contents/topic1.html index db943575e8..ba39325727 100644 --- a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/contents/topic1.html +++ b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/contents/topic1.html @@ -10,6 +10,7 @@ + @@ -114,5 +115,6 @@

Topic 1 \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/contents/topic2.html b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/contents/topic2.html index 47bed8d558..df73165eb2 100644 --- a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/contents/topic2.html +++ b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/contents/topic2.html @@ -10,6 +10,7 @@ + @@ -113,5 +114,6 @@ + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/contents/topic3a.html b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/contents/topic3a.html index 90e75c5d96..e521204fd6 100644 --- a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/contents/topic3a.html +++ b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/contents/topic3a.html @@ -10,6 +10,7 @@ + @@ -113,5 +114,6 @@ + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/contents/topic3b.html b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/contents/topic3b.html index 47cb995afb..af1b560e67 100644 --- a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/contents/topic3b.html +++ b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/contents/topic3b.html @@ -10,6 +10,7 @@ + @@ -113,5 +114,6 @@ + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/index.html b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/index.html index 4588c54c43..8c9d5c790e 100644 --- a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/index.html +++ b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/index.html @@ -10,6 +10,7 @@ + @@ -111,5 +112,6 @@

Readme \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/test_folder/extra_1.html b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/test_folder/extra_1.html index 6a1ae0ede2..20fa0d7548 100644 --- a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/test_folder/extra_1.html +++ b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/test_folder/extra_1.html @@ -10,6 +10,7 @@ + @@ -110,5 +111,6 @@

Sample cont + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/test_folder/extra_2.html b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/test_folder/extra_2.html index eb6b225bc1..74d5fafe75 100644 --- a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/test_folder/extra_2.html +++ b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/test_folder/extra_2.html @@ -10,6 +10,7 @@ + @@ -110,5 +111,6 @@

Sample cont + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/test_folder/extra_3.html b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/test_folder/extra_3.html index cf618a9db5..27c80db52f 100644 --- a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/test_folder/extra_3.html +++ b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/test_folder/extra_3.html @@ -10,6 +10,7 @@ + @@ -110,5 +111,6 @@

Sample cont + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_special_tags/expected/index.html b/packages/cli/test/functional/test_site_special_tags/expected/index.html index e44d36a9b5..887016a2a1 100644 --- a/packages/cli/test/functional/test_site_special_tags/expected/index.html +++ b/packages/cli/test/functional/test_site_special_tags/expected/index.html @@ -11,6 +11,7 @@ + @@ -104,5 +105,6 @@

So far as to comply with t + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_table_plugin/expected/index.html b/packages/cli/test/functional/test_site_table_plugin/expected/index.html index 65eb9e1784..94752d909b 100644 --- a/packages/cli/test/functional/test_site_table_plugin/expected/index.html +++ b/packages/cli/test/functional/test_site_table_plugin/expected/index.html @@ -12,6 +12,7 @@ + @@ -430,4 +431,6 @@

Welcome to MarkBind + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_default/expected/404.html b/packages/cli/test/functional/test_site_templates/test_default/expected/404.html index 54542b2f39..15ae45b31e 100644 --- a/packages/cli/test/functional/test_site_templates/test_default/expected/404.html +++ b/packages/cli/test/functional/test_site_templates/test_default/expected/404.html @@ -10,6 +10,7 @@ + @@ -39,5 +40,6 @@ + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_default/expected/contents/topic1.html b/packages/cli/test/functional/test_site_templates/test_default/expected/contents/topic1.html index 09753f6476..9e9302ea71 100644 --- a/packages/cli/test/functional/test_site_templates/test_default/expected/contents/topic1.html +++ b/packages/cli/test/functional/test_site_templates/test_default/expected/contents/topic1.html @@ -12,6 +12,7 @@ + @@ -109,5 +110,6 @@

Topic 1 \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_default/expected/contents/topic2.html b/packages/cli/test/functional/test_site_templates/test_default/expected/contents/topic2.html index 1a27ef505c..b02dac5ebb 100644 --- a/packages/cli/test/functional/test_site_templates/test_default/expected/contents/topic2.html +++ b/packages/cli/test/functional/test_site_templates/test_default/expected/contents/topic2.html @@ -12,6 +12,7 @@ + @@ -109,5 +110,6 @@

Topic 2 \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_default/expected/contents/topic3a.html b/packages/cli/test/functional/test_site_templates/test_default/expected/contents/topic3a.html index d6c512cd54..cff4ba6ae2 100644 --- a/packages/cli/test/functional/test_site_templates/test_default/expected/contents/topic3a.html +++ b/packages/cli/test/functional/test_site_templates/test_default/expected/contents/topic3a.html @@ -12,6 +12,7 @@ + @@ -109,5 +110,6 @@

Topic 3a \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_default/expected/contents/topic3b.html b/packages/cli/test/functional/test_site_templates/test_default/expected/contents/topic3b.html index a3c64a8d50..ebcc1d2992 100644 --- a/packages/cli/test/functional/test_site_templates/test_default/expected/contents/topic3b.html +++ b/packages/cli/test/functional/test_site_templates/test_default/expected/contents/topic3b.html @@ -12,6 +12,7 @@ + @@ -109,5 +110,6 @@

Topic 3b \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_default/expected/index.html b/packages/cli/test/functional/test_site_templates/test_default/expected/index.html index b6a6566d97..47654b0f2d 100644 --- a/packages/cli/test/functional/test_site_templates/test_default/expected/index.html +++ b/packages/cli/test/functional/test_site_templates/test_default/expected/index.html @@ -12,6 +12,7 @@ + @@ -157,5 +158,6 @@
User Guide: Syntax Overview + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_minimal/expected/index.html b/packages/cli/test/functional/test_site_templates/test_minimal/expected/index.html index f8046125a1..296624bd9b 100644 --- a/packages/cli/test/functional/test_site_templates/test_minimal/expected/index.html +++ b/packages/cli/test/functional/test_site_templates/test_minimal/expected/index.html @@ -11,6 +11,7 @@ + @@ -39,5 +40,6 @@

Welcome to MarkBind \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_portfolio/expected/index.html b/packages/cli/test/functional/test_site_templates/test_portfolio/expected/index.html index da90312585..d692bf5e08 100644 --- a/packages/cli/test/functional/test_site_templates/test_portfolio/expected/index.html +++ b/packages/cli/test/functional/test_site_templates/test_portfolio/expected/index.html @@ -12,6 +12,7 @@ + @@ -251,5 +252,6 @@

Project title \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Configuration.html b/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Configuration.html index 02778195db..0fdfb24ca0 100644 --- a/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Configuration.html +++ b/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Configuration.html @@ -12,6 +12,7 @@ + @@ -178,5 +179,6 @@

Configuration guide \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Design.html b/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Design.html index a5686a66e5..ba76eeb4eb 100644 --- a/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Design.html +++ b/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Design.html @@ -12,6 +12,7 @@ + @@ -272,5 +273,6 @@

Component 2 MarkBind.setupWithSearch() + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/DevOps.html b/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/DevOps.html index 874a7c60d3..13571129ad 100644 --- a/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/DevOps.html +++ b/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/DevOps.html @@ -12,6 +12,7 @@ + @@ -235,5 +236,6 @@

Making a release \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/DeveloperGuide.html b/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/DeveloperGuide.html index 0e4338bb62..f5b3a0f9c0 100644 --- a/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/DeveloperGuide.html +++ b/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/DeveloperGuide.html @@ -12,6 +12,7 @@ + @@ -196,5 +197,6 @@

Acknowledgements \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Documentation.html b/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Documentation.html index a7fba121cb..e56811538d 100644 --- a/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Documentation.html +++ b/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Documentation.html @@ -12,6 +12,7 @@ + @@ -199,5 +200,6 @@

Documentation Guide \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Implementation.html b/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Implementation.html index d60dcd0b05..4af853d462 100644 --- a/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Implementation.html +++ b/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Implementation.html @@ -12,6 +12,7 @@ + @@ -221,5 +222,6 @@

[Proposed] Data archiving \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Requirements.html b/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Requirements.html index bac9bbbe27..457352d11c 100644 --- a/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Requirements.html +++ b/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Requirements.html @@ -12,6 +12,7 @@ + @@ -277,5 +278,6 @@

Non-Functional Requirements \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/SettingUp.html b/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/SettingUp.html index ce80c4485c..90206827a9 100644 --- a/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/SettingUp.html +++ b/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/SettingUp.html @@ -12,6 +12,7 @@ + @@ -235,5 +236,6 @@

Before writing code \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Testing.html b/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Testing.html index dcc5d8e2f2..33a3579fe2 100644 --- a/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Testing.html +++ b/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/Testing.html @@ -12,6 +12,7 @@ + @@ -210,5 +211,6 @@

Types of tests \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/TracingCode.html b/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/TracingCode.html index b7f3fb3926..6a0f53f07e 100644 --- a/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/TracingCode.html +++ b/packages/cli/test/functional/test_site_templates/test_project/expected/developerGuide/TracingCode.html @@ -12,6 +12,7 @@ + @@ -234,5 +235,6 @@

Tracing the execution path \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_project/expected/index.html b/packages/cli/test/functional/test_site_templates/test_project/expected/index.html index 631e854599..51c7f4ed34 100644 --- a/packages/cli/test/functional/test_site_templates/test_project/expected/index.html +++ b/packages/cli/test/functional/test_site_templates/test_project/expected/index.html @@ -12,6 +12,7 @@ + @@ -195,5 +196,6 @@

ProjectEx \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_project/expected/team/AboutUs.html b/packages/cli/test/functional/test_site_templates/test_project/expected/team/AboutUs.html index 8f452fb666..0408394b09 100644 --- a/packages/cli/test/functional/test_site_templates/test_project/expected/team/AboutUs.html +++ b/packages/cli/test/functional/test_site_templates/test_project/expected/team/AboutUs.html @@ -12,6 +12,7 @@ + @@ -223,5 +224,6 @@

James Doe \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_project/expected/team/johndoe.html b/packages/cli/test/functional/test_site_templates/test_project/expected/team/johndoe.html index 792afbcf3a..e3e4d639b3 100644 --- a/packages/cli/test/functional/test_site_templates/test_project/expected/team/johndoe.html +++ b/packages/cli/test/functional/test_site_templates/test_project/expected/team/johndoe.html @@ -12,6 +12,7 @@ + @@ -241,5 +242,6 @@

Project: ProjectEx \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_project/expected/userGuide/FAQ.html b/packages/cli/test/functional/test_site_templates/test_project/expected/userGuide/FAQ.html index fa394d2264..554c860d46 100644 --- a/packages/cli/test/functional/test_site_templates/test_project/expected/userGuide/FAQ.html +++ b/packages/cli/test/functional/test_site_templates/test_project/expected/userGuide/FAQ.html @@ -12,6 +12,7 @@ + @@ -193,5 +194,6 @@

FAQ \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_project/expected/userGuide/Features.html b/packages/cli/test/functional/test_site_templates/test_project/expected/userGuide/Features.html index 0ebe7decae..86a07dd91e 100644 --- a/packages/cli/test/functional/test_site_templates/test_project/expected/userGuide/Features.html +++ b/packages/cli/test/functional/test_site_templates/test_project/expected/userGuide/Features.html @@ -12,6 +12,7 @@ + @@ -225,5 +226,6 @@

Future Feature Z \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_project/expected/userGuide/QuickStart.html b/packages/cli/test/functional/test_site_templates/test_project/expected/userGuide/QuickStart.html index 9d8b41d432..72c80b4616 100644 --- a/packages/cli/test/functional/test_site_templates/test_project/expected/userGuide/QuickStart.html +++ b/packages/cli/test/functional/test_site_templates/test_project/expected/userGuide/QuickStart.html @@ -12,6 +12,7 @@ + @@ -193,5 +194,6 @@

Quick start MarkBind.setupWithSearch() + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_project/expected/userGuide/UserGuide.html b/packages/cli/test/functional/test_site_templates/test_project/expected/userGuide/UserGuide.html index 3a7f770563..90cce628f8 100644 --- a/packages/cli/test/functional/test_site_templates/test_project/expected/userGuide/UserGuide.html +++ b/packages/cli/test/functional/test_site_templates/test_project/expected/userGuide/UserGuide.html @@ -12,6 +12,7 @@ + @@ -194,5 +195,6 @@

Purpose of this Guide MarkBind.setupWithSearch() + \ No newline at end of file diff --git a/packages/core/jest.config.cjs b/packages/core/jest.config.cjs index 6398c0e99f..7a219f3cf3 100644 --- a/packages/core/jest.config.cjs +++ b/packages/core/jest.config.cjs @@ -2,6 +2,7 @@ const config = { verbose: true, moduleNameMapper: { '^(\\.{1,2}/.*)\\.js$': '$1', + '^pagefind$': '/test/__mocks__/pagefind.js', }, testEnvironment: 'node', collectCoverage: true, diff --git a/packages/core/package.json b/packages/core/package.json index 2f43938d87..175a158801 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -90,6 +90,7 @@ "material-icons": "^1.9.1", "moment": "^2.29.4", "nunjucks": "3.2.4", + "pagefind": "^1.4.0", "path-is-inside": "^1.0.2", "simple-git": "^3.22.0", "url-parse": "^1.5.10", diff --git a/packages/core/src/Page/PageConfig.ts b/packages/core/src/Page/PageConfig.ts index b15c725467..8053732136 100644 --- a/packages/core/src/Page/PageConfig.ts +++ b/packages/core/src/Page/PageConfig.ts @@ -23,8 +23,10 @@ export interface PageAssets { vue: string; pageVueRenderJs?: string; layoutUserScriptsAndStyles: string[]; - pluginScripts?: string[], - pluginLinks?: string[], + pluginScripts?: string[]; + pluginLinks?: string[]; + pagefindCss?: string; + pagefindJs?: string; } /** diff --git a/packages/core/src/Page/page.njk b/packages/core/src/Page/page.njk index 04c2e12e5a..be7d946441 100644 --- a/packages/core/src/Page/page.njk +++ b/packages/core/src/Page/page.njk @@ -19,6 +19,7 @@ {% if asset.bootstrapIcons %} {%- endif -%} {%- if not dev -%}{%- endif -%} + {% if asset.pagefindCss %} {%- endif -%} {%- if asset.pluginLinks -%} {%- for link in asset.pluginLinks -%} {{ link }} @@ -57,6 +58,9 @@ {{ script }} {%- endfor %} {%- endif %} +{%- if asset.pagefindJs %} + +{%- endif %} {%- if asset.scriptBottom %} {%- for scripts in asset.scriptBottom %} {{ scripts }} diff --git a/packages/core/src/Site/SiteConfig.ts b/packages/core/src/Site/SiteConfig.ts index b820417dc2..0190ed7a86 100644 --- a/packages/core/src/Site/SiteConfig.ts +++ b/packages/core/src/Site/SiteConfig.ts @@ -64,6 +64,11 @@ export class SiteConfig { plantumlCheck: boolean; + pagefind?: { + exclude_selectors?: string[]; + glob?: string | string[]; + }; + /** * @param siteConfigJson The raw json read from the site configuration file * @param cliBaseUrl As read from the --baseUrl option @@ -103,6 +108,8 @@ export class SiteConfig { // TODO this should probably be in pluginsContext this.plantumlCheck = siteConfigJson.plantumlCheck !== undefined ? siteConfigJson.plantumlCheck : true; // check PlantUML's prerequisite by default + + this.pagefind = siteConfigJson.pagefind; } /** diff --git a/packages/core/src/Site/SiteGenerationManager.ts b/packages/core/src/Site/SiteGenerationManager.ts index 472c029b9c..fcc40c6737 100644 --- a/packages/core/src/Site/SiteGenerationManager.ts +++ b/packages/core/src/Site/SiteGenerationManager.ts @@ -3,6 +3,7 @@ import fs from 'fs-extra'; import path from 'path'; import { fileURLToPath } from 'url'; import walkSync from 'walk-sync'; +import * as pagefind from 'pagefind'; import { SiteAssetsManager } from './SiteAssetsManager.js'; import { SitePagesManager, AddressablePage } from './SitePagesManager.js'; @@ -18,7 +19,7 @@ import * as fsUtil from '../utils/fsUtil.js'; import * as logger from '../utils/logger.js'; import { SITE_CONFIG_NAME, LAZY_LOADING_SITE_FILE_NAME, _, - TEMP_FOLDER_NAME, SITE_DATA_NAME, USER_VARIABLES_PATH, + TEMP_FOLDER_NAME, SITE_DATA_NAME, USER_VARIABLES_PATH, TEMPLATE_SITE_ASSET_FOLDER_NAME, } from './constants.js'; import { LayoutManager } from '../Layout/index.js'; import { LayoutConfig } from '../Layout/Layout.js'; @@ -315,6 +316,10 @@ export class SiteGenerationManager { await this.siteAssets.copyOcticonsAsset(); await this.siteAssets.copyMaterialIconsAsset(); await this.writeSiteData(); + if (this.siteConfig.enableSearch) { + const indexingSucceeded = await this.indexSiteWithPagefind(); + this.sitePages.pagefindIndexingSucceeded = indexingSucceeded; + } this.calculateBuildTimeForGenerate(startTime, lazyWebsiteGenerationString); if (this.backgroundBuildMode) { this.backgroundBuildNotViewedFiles(); @@ -866,6 +871,154 @@ export class SiteGenerationManager { 1000, ); + /** + * Validates that a glob pattern is safe and won't traverse outside the output directory. + * + * @param pattern - The glob pattern to validate + * @returns true if the pattern is safe, false otherwise + */ + // eslint-disable-next-line class-methods-use-this + private isValidGlobPattern(pattern: string): boolean { + if (pattern.includes('..')) { + return false; + } + + const isUnixAbsolutePath = pattern.startsWith('/'); + const isWindowsAbsolutePath = /^[a-zA-Z]:[\\/]/.test(pattern); + if (isUnixAbsolutePath || isWindowsAbsolutePath) { + return false; + } + + return true; + } + + /** + * Normalizes a gitignore-style glob pattern to a valid Wax/Pagefind pattern + * by appending .html extension if not already present. + * Invalid patterns (e.g., path traversal attempts) are logged and return empty string. + * + * @param pattern - The glob pattern from user config (gitignore-style) + * @returns A valid Wax/Pagefind glob pattern, or empty string if invalid + */ + private normalizeGlobPattern(pattern: string): string { + const normalizedPattern = pattern.replace(/\\/g, '/'); + + if (!this.isValidGlobPattern(pattern)) { + logger.error(`Invalid glob pattern rejected (potential path traversal): ${pattern}`); + return ''; + } + + if (normalizedPattern.endsWith('.html')) { + return normalizedPattern; + } + + if (normalizedPattern.endsWith('/**')) { + return `${normalizedPattern}/*.html`; + } + + if (normalizedPattern.endsWith('/*')) { + return `${normalizedPattern}.html`; + } + + if (normalizedPattern.endsWith('/')) { + return `${normalizedPattern}**/*.html`; + } + + return `${normalizedPattern}/**/*.html`; + } + + /** + * Indexes all HTML files in the output directory and logs any errors. + * @param index - The pagefind index instance + * @returns The number of pages indexed + */ + // eslint-disable-next-line class-methods-use-this + private async indexAllHtmlFiles(index: any): Promise { + const result = await index.addDirectory({ path: this.outputPath }); + result.errors.forEach((error: string) => logger.error(error)); + return result.page_count; + } + + /** + * Indexes all the pages of the site using pagefind. + * @returns true if indexing succeeded and pagefind assets were written, false otherwise. + */ + async indexSiteWithPagefind(): Promise { + const startTime = new Date(); + logger.info('Creating Pagefind Search Index...'); + try { + const { createIndex, close } = pagefind; + + const pagefindConfig = this.siteConfig.pagefind || {}; + + const createIndexOptions: Record = { + keepIndexUrl: true, + verbose: true, + logfile: 'debug.log', + }; + + if (pagefindConfig.exclude_selectors) { + createIndexOptions.excludeSelectors = pagefindConfig.exclude_selectors; + } + + const { index } = await createIndex(createIndexOptions); + if (index) { + // Handle glob patterns - support both single string and array of strings + const globValue = pagefindConfig.glob; + const value = globValue ?? []; + const globPatterns = Array.isArray(value) ? value : [value]; + + let totalPageCount = 0; + + if (globPatterns.length > 0) { + const normalizedPatterns = globPatterns + .map(pattern => this.normalizeGlobPattern(pattern)) + .filter(pattern => pattern !== ''); + + if (normalizedPatterns.length > 0) { + const results = await Promise.all( + normalizedPatterns.map(async (normalizedPattern) => { + logger.info(`Pagefind indexing with glob: ${normalizedPattern}`); + const result = await index.addDirectory({ + path: this.outputPath, + glob: normalizedPattern, + }); + + result.errors.forEach((error: string) => logger.error(error)); + + return result.page_count; + }), + ); + + totalPageCount += results.reduce((acc, count) => acc + count, 0); + } else { + logger.warn('All glob patterns were invalid, falling back to indexing all HTML files'); + totalPageCount = await this.indexAllHtmlFiles(index); + } + } else { + totalPageCount = await this.indexAllHtmlFiles(index); + } + + const endTime = new Date(); + const totalTime = (endTime.getTime() - startTime.getTime()) / 1000; + logger.info(`Pagefind indexed ${totalPageCount} pages in ${totalTime}s`); + + const pagefindOutputPath = path.join(this.outputPath, TEMPLATE_SITE_ASSET_FOLDER_NAME, 'pagefind'); + await fs.ensureDir(pagefindOutputPath); + await index.writeFiles({ outputPath: pagefindOutputPath }); + logger.info(`Pagefind assets written to ${pagefindOutputPath}`); + await close(); + return true; + } + logger.error('Pagefind failed to create index'); + await close(); + return false; + } catch (error) { + logger.warn(`Pagefind indexing skipped: ${error}`); + return false; + } + } + async reloadSiteConfig() { if (this.backgroundBuildMode) { this.stopOngoingBuilds(); diff --git a/packages/core/src/Site/SitePagesManager.ts b/packages/core/src/Site/SitePagesManager.ts index 8c9d5522dd..0978f12b17 100644 --- a/packages/core/src/Site/SitePagesManager.ts +++ b/packages/core/src/Site/SitePagesManager.ts @@ -90,6 +90,8 @@ export class SitePagesManager { isDevMode: boolean; + pagefindIndexingSucceeded: boolean; + constructor(rootPath: string, outputPath: string, isDevMode: boolean) { this.rootPath = rootPath; this.outputPath = outputPath; @@ -102,6 +104,7 @@ export class SitePagesManager { this.addressablePages = []; this.addressablePagesSource = []; this.baseUrlMap = new Set(); + this.pagefindIndexingSucceeded = true; } setBaseUrlMap(baseUrlMap: Set) { @@ -145,6 +148,12 @@ export class SitePagesManager { ? 'https://cdn.jsdelivr.net/npm/vue@3.3.11/dist/vue.global.min.js' : path.posix.join(baseAssetsPath, 'js', 'vue.global.prod.min.js'), layoutUserScriptsAndStyles: [], + pagefindCss: this.siteConfig.enableSearch && this.pagefindIndexingSucceeded + ? path.posix.join(baseAssetsPath, 'pagefind', 'pagefind-ui.css') + : undefined, + pagefindJs: this.siteConfig.enableSearch && this.pagefindIndexingSucceeded + ? path.posix.join(baseAssetsPath, 'pagefind', 'pagefind-ui.js') + : undefined, }, baseUrlMap: this.baseUrlMap, dev: this.isDevMode, diff --git a/packages/core/test/__mocks__/pagefind.js b/packages/core/test/__mocks__/pagefind.js new file mode 100644 index 0000000000..2a5fc34da1 --- /dev/null +++ b/packages/core/test/__mocks__/pagefind.js @@ -0,0 +1,17 @@ +/** + * Manual mock for pagefind module + * + * This mock allows tests to spy on and mock the pagefind module functions. + * Jest's moduleNameMapper redirects 'pagefind' imports to this file. + */ + +const mockCreateIndex = jest.fn(); +const mockClose = jest.fn(); + +module.exports = { + createIndex: mockCreateIndex, + close: mockClose, + // Export mock functions directly for test access + __mockCreateIndex: mockCreateIndex, + __mockClose: mockClose, +}; diff --git a/packages/core/test/unit/Site/SiteGenerationManager.test.ts b/packages/core/test/unit/Site/SiteGenerationManager.test.ts index 146b200d01..4e9cdb0559 100644 --- a/packages/core/test/unit/Site/SiteGenerationManager.test.ts +++ b/packages/core/test/unit/Site/SiteGenerationManager.test.ts @@ -1,9 +1,15 @@ import path from 'path'; import fs from 'fs-extra'; +import * as pagefind from 'pagefind'; import { SiteGenerationManager } from '../../../src/Site/SiteGenerationManager.js'; import { PAGE_NJK, SITE_JSON_DEFAULT, + createSiteJsonWithPagefind, + createMockIndex, + createMockPagefind, + createMockPagefindNullIndex, } from '../utils/data.js'; +import * as logger from '../../../src/utils/logger.js'; // We use 'memfs' (via the mocked 'fs' module) to simulate a file system in memory. // This ensures that no actual files are written to the disk during testing, @@ -36,6 +42,12 @@ jest.mock('../../../src/Site/SitePagesManager', () => ({ }), })); +jest.mock('../../../src/utils/logger', () => ({ + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), +})); + // Access mocked constructors to create instances for injection const { SiteAssetsManager } = require('../../../src/Site/SiteAssetsManager.js'); const { SitePagesManager } = require('../../../src/Site/SitePagesManager.js'); @@ -68,6 +80,329 @@ describe('SiteGenerationManager', () => { generationManager.configure(siteAssets, sitePages); }); + describe('normalizeGlobPattern', () => { + const prototypeMethod = (SiteGenerationManager.prototype as any).normalizeGlobPattern; + + test('should return pattern as-is if it ends with .html', () => { + const result = prototypeMethod.call(generationManager, 'page.html'); + expect(result).toBe('page.html'); + }); + + test('should append /*.html if pattern ends with /**', () => { + const result = prototypeMethod.call(generationManager, 'dir/**'); + expect(result).toBe('dir/**/*.html'); + }); + + test('should append .html if pattern ends with /*', () => { + const result = prototypeMethod.call(generationManager, 'dir/*'); + expect(result).toBe('dir/*.html'); + }); + + test('should append **/*.html if pattern ends with /', () => { + const result = prototypeMethod.call(generationManager, 'dir/'); + expect(result).toBe('dir/**/*.html'); + }); + + test('should append /**/*.html for plain directory names', () => { + const result = prototypeMethod.call(generationManager, 'dir'); + expect(result).toBe('dir/**/*.html'); + }); + + test('should return empty string for invalid path traversal patterns', () => { + const result = prototypeMethod.call(generationManager, '../../../etc/**'); + expect(result).toBe(''); + }); + + test('should return empty string for absolute paths', () => { + const result = prototypeMethod.call(generationManager, '/etc/passwd'); + expect(result).toBe(''); + }); + }); + + describe('indexSiteWithPagefind', () => { + beforeEach(() => { + const json = { + ...PAGE_NJK, + 'site.json': SITE_JSON_DEFAULT, + '_site/index.html': 'Test page', + '_site/page1.html': 'Page 1', + }; + mockFs.vol.fromJSON(json, rootPath); + }); + + test('should use default pagefind configuration when not specified', async () => { + // This test verifies that readSiteConfig properly sets up the siteConfig.pagefind + await generationManager.readSiteConfig(); + expect(generationManager.siteConfig.pagefind).toBeUndefined(); + }); + + test('should read pagefind configuration from site.json', async () => { + const customSiteJson = createSiteJsonWithPagefind({ + pagefind: { + exclude_selectors: ['.no-index', '#sidebar'], + }, + }); + mockFs.vol.fromJSON({ + ...PAGE_NJK, + 'site.json': customSiteJson, + '_site/index.html': 'Test', + }, rootPath); + + await generationManager.readSiteConfig(); + expect(generationManager.siteConfig.pagefind).toEqual({ + exclude_selectors: ['.no-index', '#sidebar'], + }); + }); + + test('should read glob configuration as string', async () => { + const customSiteJson = createSiteJsonWithPagefind({ + pagefind: { + glob: '**/docs/*.html', + }, + }); + mockFs.vol.fromJSON({ + ...PAGE_NJK, + 'site.json': customSiteJson, + '_site/index.html': 'Test', + }, rootPath); + + await generationManager.readSiteConfig(); + expect(generationManager.siteConfig.pagefind).toEqual({ + glob: '**/docs/*.html', + }); + }); + + test('should read glob configuration as array', async () => { + const customSiteJson = createSiteJsonWithPagefind({ + pagefind: { + glob: ['**/docs/*.html', '**/guide/*.html'], + }, + }); + mockFs.vol.fromJSON({ + ...PAGE_NJK, + 'site.json': customSiteJson, + '_site/index.html': 'Test', + }, rootPath); + + await generationManager.readSiteConfig(); + expect(generationManager.siteConfig.pagefind).toEqual({ + glob: ['**/docs/*.html', '**/guide/*.html'], + }); + }); + + test('should index site with default configuration', async () => { + const json = { + ...PAGE_NJK, + 'site.json': SITE_JSON_DEFAULT, + '_site/index.html': 'Test page', + }; + mockFs.vol.fromJSON(json, rootPath); + + const mockPagefindInstance = createMockPagefind(createMockIndex({ page_count: 5, errors: [] })); + const pagefindSpy = jest.spyOn(pagefind, 'createIndex').mockResolvedValue( + mockPagefindInstance.createIndex({}) as any, + ); + + await generationManager.readSiteConfig(); + await generationManager.indexSiteWithPagefind(); + + expect(pagefindSpy).toHaveBeenCalledWith({ + keepIndexUrl: true, + verbose: true, + logfile: 'debug.log', + }); + + pagefindSpy.mockRestore(); + }); + + test('should use excludeSelectors from pagefind config', async () => { + const customSiteJson = createSiteJsonWithPagefind({ + pagefind: { + exclude_selectors: ['.no-index', '#sidebar'], + }, + }); + mockFs.vol.fromJSON({ + ...PAGE_NJK, + 'site.json': customSiteJson, + '_site/index.html': 'Test', + }, rootPath); + + const mockPagefindInstance = createMockPagefind(createMockIndex({ page_count: 1, errors: [] })); + const pagefindSpy = jest.spyOn(pagefind, 'createIndex').mockResolvedValue( + mockPagefindInstance.createIndex({}) as any, + ); + + await generationManager.readSiteConfig(); + await generationManager.indexSiteWithPagefind(); + + expect(pagefindSpy).toHaveBeenCalledWith({ + keepIndexUrl: true, + verbose: true, + logfile: 'debug.log', + excludeSelectors: ['.no-index', '#sidebar'], + }); + + pagefindSpy.mockRestore(); + }); + + test('should handle glob pattern as string', async () => { + const customSiteJson = createSiteJsonWithPagefind({ + pagefind: { + glob: '**/docs/*.html', + }, + }); + mockFs.vol.fromJSON({ + ...PAGE_NJK, + 'site.json': customSiteJson, + '_site/index.html': 'Test', + }, rootPath); + + const mockIndex = createMockIndex({ page_count: 3, errors: [] }); + const mockPagefindInstance = createMockPagefind(mockIndex, true); + const pagefindSpy = jest.spyOn(pagefind, 'createIndex').mockResolvedValue( + mockPagefindInstance.createIndex({}) as any, + ); + + await generationManager.readSiteConfig(); + await generationManager.indexSiteWithPagefind(); + + expect(mockIndex.addDirectory).toHaveBeenCalledWith({ + path: outputPath, + glob: '**/docs/*.html', + }); + + pagefindSpy.mockRestore(); + }); + + test('should handle glob pattern as array', async () => { + const customSiteJson = createSiteJsonWithPagefind({ + pagefind: { + glob: ['**/docs/*.html', '**/guide/*.html'], + }, + }); + mockFs.vol.fromJSON({ + ...PAGE_NJK, + 'site.json': customSiteJson, + '_site/index.html': 'Test', + }, rootPath); + + const mockIndex = createMockIndex({ page_count: 2, errors: [] }); + const mockPagefindInstance = createMockPagefind(mockIndex, true); + const pagefindSpy = jest.spyOn(pagefind, 'createIndex').mockResolvedValue( + mockPagefindInstance.createIndex({}) as any, + ); + + await generationManager.readSiteConfig(); + await generationManager.indexSiteWithPagefind(); + + expect(mockIndex.addDirectory).toHaveBeenCalledTimes(2); + expect(mockIndex.addDirectory).toHaveBeenNthCalledWith(1, { + path: outputPath, + glob: '**/docs/*.html', + }); + expect(mockIndex.addDirectory).toHaveBeenNthCalledWith(2, { + path: outputPath, + glob: '**/guide/*.html', + }); + + pagefindSpy.mockRestore(); + }); + + test('should index all HTML files when no glob specified', async () => { + const json = { + ...PAGE_NJK, + 'site.json': SITE_JSON_DEFAULT, + '_site/index.html': 'Test', + }; + mockFs.vol.fromJSON(json, rootPath); + + const mockIndex = createMockIndex({ page_count: 10, errors: [] }); + const mockPagefindInstance = createMockPagefind(mockIndex, true); + const pagefindSpy = jest.spyOn(pagefind, 'createIndex').mockResolvedValue( + mockPagefindInstance.createIndex({}) as any, + ); + + await generationManager.readSiteConfig(); + await generationManager.indexSiteWithPagefind(); + + expect(mockIndex.addDirectory).toHaveBeenCalledWith({ + path: outputPath, + }); + + pagefindSpy.mockRestore(); + }); + + test('should log errors from addDirectory', async () => { + const json = { + ...PAGE_NJK, + 'site.json': SITE_JSON_DEFAULT, + '_site/index.html': 'Test', + }; + mockFs.vol.fromJSON(json, rootPath); + + const mockIndex = createMockIndex({ page_count: 1, errors: ['Error 1', 'Error 2'] }); + const mockPagefindInstance = createMockPagefind(mockIndex, true); + const pagefindSpy = jest.spyOn(pagefind, 'createIndex').mockResolvedValue( + mockPagefindInstance.createIndex({}) as any, + ); + const errorSpy = jest.spyOn(logger, 'error').mockImplementation(); + + await generationManager.readSiteConfig(); + await generationManager.indexSiteWithPagefind(); + + expect(errorSpy).toHaveBeenCalledWith('Error 1'); + expect(errorSpy).toHaveBeenCalledWith('Error 2'); + + pagefindSpy.mockRestore(); + errorSpy.mockRestore(); + }); + + test('should skip indexing when pagefind import fails', async () => { + const json = { + ...PAGE_NJK, + 'site.json': SITE_JSON_DEFAULT, + '_site/index.html': 'Test', + }; + mockFs.vol.fromJSON(json, rootPath); + + const pagefindSpy = jest.spyOn(pagefind, 'createIndex').mockRejectedValue( + new Error('Module not found'), + ); + const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(); + + await generationManager.readSiteConfig(); + await generationManager.indexSiteWithPagefind(); + + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Pagefind indexing skipped')); + + pagefindSpy.mockRestore(); + warnSpy.mockRestore(); + }); + + test('should handle when createIndex returns null index', async () => { + const json = { + ...PAGE_NJK, + 'site.json': SITE_JSON_DEFAULT, + '_site/index.html': 'Test', + }; + mockFs.vol.fromJSON(json, rootPath); + + const mockPagefindInstance = createMockPagefindNullIndex(); + const pagefindSpy = jest.spyOn(pagefind, 'createIndex').mockResolvedValue( + mockPagefindInstance.createIndex({}) as any, + ); + const errorSpy = jest.spyOn(logger, 'error').mockImplementation(); + + await generationManager.readSiteConfig(); + await generationManager.indexSiteWithPagefind(); + + expect(errorSpy).toHaveBeenCalledWith('Pagefind failed to create index'); + + pagefindSpy.mockRestore(); + errorSpy.mockRestore(); + }); + }); + test('collectBaseUrl should collect baseurls correctly for sub nested subsites', async () => { const json = { ...PAGE_NJK, diff --git a/packages/core/test/unit/utils/data.ts b/packages/core/test/unit/utils/data.ts index 326ab17587..7d223f4942 100644 --- a/packages/core/test/unit/utils/data.ts +++ b/packages/core/test/unit/utils/data.ts @@ -90,3 +90,83 @@ const DEFAULT_TEMPLATE_DIRECTORY = path.join(__dirname, '../../../template/defau export function getDefaultTemplateFileFullPath(relativePath: string) { return path.join(DEFAULT_TEMPLATE_DIRECTORY, relativePath); } + +// ============================================================================ +// Factory functions for SiteGenerationManager tests +// ============================================================================ + +type JestFn = typeof jest.fn; + +export interface MockAddDirectoryResult { + page_count: number; + errors?: string[]; +} + +export interface MockIndex { + addDirectory: ReturnType; + writeFiles: ReturnType; +} + +export interface MockPagefind { + createIndex: ReturnType; + close: ReturnType; +} + +/** + * Creates a mock pagefind index + * @param result - The result to return from addDirectory + */ +export function createMockIndex( + result: MockAddDirectoryResult = { page_count: 1, errors: [] }, +): MockIndex { + return { + addDirectory: jest.fn().mockResolvedValue(result), + writeFiles: jest.fn().mockResolvedValue(undefined), + }; +} + +/** + * Creates a mock pagefind module + * @param mockIndex - The mock index to use (from createMockIndex) + * @param returnIndexDirectly - If true, returns { index: mockIndex }, otherwise returns mockIndex directly + */ +export function createMockPagefind(mockIndex: MockIndex, returnIndexDirectly = false): MockPagefind { + return { + createIndex: jest.fn().mockResolvedValue( + returnIndexDirectly ? { index: mockIndex } : mockIndex, + ), + close: jest.fn().mockResolvedValue(undefined), + }; +} + +/** + * Creates a mock pagefind that returns null index (for error testing) + */ +export function createMockPagefindNullIndex(): MockPagefind { + return { + createIndex: jest.fn().mockResolvedValue({ index: null }), + close: jest.fn().mockResolvedValue(undefined), + }; +} + +/** + * Creates a mock pagefind that rejects (for import failure testing) + */ +export function createMockPagefindReject(error: Error = new Error('Module not found')): MockPagefind { + return { + createIndex: jest.fn().mockRejectedValue(error), + close: jest.fn().mockResolvedValue(undefined), + }; +} + +/** + * Creates a site.json string with pagefind configuration + * @param pagefindConfig - The pagefind configuration object (without baseUrl) + */ +export function createSiteJsonWithPagefind(pagefindConfig: Record): string { + const siteJson = { + baseUrl: '', + ...pagefindConfig, + }; + return JSON.stringify(siteJson); +} diff --git a/packages/vue-components/src/__tests__/Search.spec.js b/packages/vue-components/src/__tests__/Search.spec.js new file mode 100644 index 0000000000..806f0e3122 --- /dev/null +++ b/packages/vue-components/src/__tests__/Search.spec.js @@ -0,0 +1,172 @@ +import { mount } from '@vue/test-utils'; +import { nextTick } from 'vue'; +// eslint-disable-next-line import/no-extraneous-dependencies +import constant from 'lodash/constant'; +import Search from '../pagefindSearchBar/Search.vue'; + +describe('Search', () => { + let wrapper; + let mockPagefindUI; + let mockContainer; + + beforeEach(() => { + mockContainer = { + querySelectorAll: jest.fn(() => []), + querySelector: jest.fn(constant(null)), + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + }; + + mockPagefindUI = jest.fn(() => ({})); + window.PagefindUI = mockPagefindUI; + window.addEventListener = jest.fn(); + window.removeEventListener = jest.fn(); + window.MutationObserver = jest.fn(() => ({ + observe: jest.fn(), + disconnect: jest.fn(), + })); + + document.querySelector = jest.fn((selector) => { + if (selector === '#pagefind-search-input') { + return mockContainer; + } + if (selector === '#pagefind-search-input input') { + return { focus: jest.fn() }; + } + return null; + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + if (wrapper) { + wrapper.unmount(); + wrapper = null; + } + document.body.innerHTML = ''; + }); + + test('renders search button', async () => { + wrapper = mount(Search); + await nextTick(); + + const searchBtn = wrapper.find('.nav-search-btn-wait'); + expect(searchBtn.exists()).toBe(true); + expect(wrapper.text()).toContain('Search'); + }); + + test('displays correct metaKey for Mac', async () => { + Object.defineProperty(navigator, 'platform', { + value: 'MacIntel', + configurable: true, + }); + + wrapper = mount(Search); + await nextTick(); + + expect(wrapper.find('.metaKey').text()).toBe('⌘ K'); + }); + + test('displays correct metaKey for non-Mac', async () => { + Object.defineProperty(navigator, 'platform', { + value: 'Win32', + configurable: true, + }); + + wrapper = mount(Search); + await nextTick(); + + expect(wrapper.find('.metaKey').text()).toBe('Ctrl K'); + }); + + test('opens modal on button click', async () => { + wrapper = mount(Search); + await nextTick(); + + await wrapper.find('.nav-search-btn-wait').trigger('click'); + await nextTick(); + + expect(wrapper.find('.algolia').exists()).toBe(true); + }); + + test('opens modal on Cmd+K keyboard shortcut', async () => { + wrapper = mount(Search); + await nextTick(); + + const event = new KeyboardEvent('keydown', { key: 'k', metaKey: true }); + window.addEventListener.mock.calls[0][1](event); + await nextTick(); + + expect(wrapper.find('.algolia').exists()).toBe(true); + }); + + test('opens modal on Ctrl+K keyboard shortcut', async () => { + wrapper = mount(Search); + await nextTick(); + + const event = new KeyboardEvent('keydown', { key: 'k', ctrlKey: true }); + window.addEventListener.mock.calls[0][1](event); + await nextTick(); + + expect(wrapper.find('.algolia').exists()).toBe(true); + }); + + test('closes modal on Escape key', async () => { + wrapper = mount(Search); + await nextTick(); + + await wrapper.find('.nav-search-btn-wait').trigger('click'); + await nextTick(); + + expect(wrapper.find('.algolia').exists()).toBe(true); + + const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' }); + window.addEventListener.mock.calls[0][1](escapeEvent); + await nextTick(); + + expect(wrapper.find('.algolia').exists()).toBe(false); + }); + + test('closes modal on backdrop click', async () => { + wrapper = mount(Search, { attachTo: document.body }); + await nextTick(); + + await wrapper.find('.nav-search-btn-wait').trigger('click'); + await nextTick(); + + expect(wrapper.find('.algolia').exists()).toBe(true); + expect(wrapper.find('[command-dialog-mask]').exists()).toBe(true); + + await wrapper.find('[command-dialog-mask]').trigger('click.self'); + await nextTick(); + + expect(wrapper.find('.algolia').exists()).toBe(false); + }); + + test('initializes PagefindUI when modal opens', async () => { + wrapper = mount(Search); + await nextTick(); + + await wrapper.find('.nav-search-btn-wait').trigger('click'); + await nextTick(); + await nextTick(); + + expect(mockPagefindUI).toHaveBeenCalled(); + }); + + test('adds keydown event listener on mount', async () => { + wrapper = mount(Search); + await nextTick(); + + expect(window.addEventListener).toHaveBeenCalledWith('keydown', expect.any(Function)); + }); + + test('removes keydown event listener on unmount', async () => { + wrapper = mount(Search); + await nextTick(); + + wrapper.unmount(); + + expect(window.removeEventListener).toHaveBeenCalledWith('keydown', expect.any(Function)); + }); +}); diff --git a/packages/vue-components/src/index.js b/packages/vue-components/src/index.js index 2de3bd9d94..ee8bb02528 100644 --- a/packages/vue-components/src/index.js +++ b/packages/vue-components/src/index.js @@ -37,6 +37,7 @@ import modal from './Modal.vue'; import scrollTopButton from './ScrollTopButton.vue'; import cardstack from './cardstack/CardStack.vue'; import card from './cardstack/Card.vue'; +import Search from './pagefindSearchBar/Search.vue'; const components = { box, @@ -71,6 +72,7 @@ const components = { 'VPopover': Dropdown, 'VTooltip': Tooltip, scrollTopButton, + Search, }; const directives = { diff --git a/packages/vue-components/src/pagefindSearchBar/LogoPagefind.vue b/packages/vue-components/src/pagefindSearchBar/LogoPagefind.vue new file mode 100644 index 0000000000..557f348d5b --- /dev/null +++ b/packages/vue-components/src/pagefindSearchBar/LogoPagefind.vue @@ -0,0 +1,101 @@ + diff --git a/packages/vue-components/src/pagefindSearchBar/Search.vue b/packages/vue-components/src/pagefindSearchBar/Search.vue new file mode 100644 index 0000000000..b7bbf63c70 --- /dev/null +++ b/packages/vue-components/src/pagefindSearchBar/Search.vue @@ -0,0 +1,437 @@ + + + + + + + diff --git a/packages/vue-components/src/pagefindSearchBar/assets/search.css b/packages/vue-components/src/pagefindSearchBar/assets/search.css new file mode 100644 index 0000000000..ecab74658b --- /dev/null +++ b/packages/vue-components/src/pagefindSearchBar/assets/search.css @@ -0,0 +1,369 @@ +:root { + --font-sans: "Inter", -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, + Droid Sans, Helvetica Neue, sans-serif; + --app-bg: var(--gray1); + --app-text: #000000; + --command-shadow: 0 16px 70px rgb(0 0 0 / 20%); + --lowContrast: #ffffff; + --highContrast: #000000; + --vcp-c-brand: var(--vp-c-brand-2); + --vcp-c-accent: #35495e; + --gray1: hsl(0, 0%, 98%); + --gray2: hsl(0, 0%, 97.3%); + --gray3: hsl(0, 0%, 95.1%); + --gray4: hsl(0, 0%, 93%); + --gray5: hsl(0, 0%, 90.9%); + --gray6: hsl(0, 0%, 88.7%); + --gray7: hsl(0, 0%, 85.8%); + --gray8: hsl(0, 0%, 78%); + --gray9: hsl(0, 0%, 56.1%); + --gray10: hsl(0, 0%, 52.3%); + --gray11: hsl(0, 0%, 43.5%); + --gray12: hsl(0, 0%, 9%); + --grayA1: hsla(0, 0%, 0%, 0.012); + --grayA2: hsla(0, 0%, 0%, 0.027); + --grayA3: hsla(0, 0%, 0%, 0.047); + --grayA4: hsla(0, 0%, 0%, 0.071); + --grayA5: hsla(0, 0%, 0%, 0.09); + --grayA6: hsla(0, 0%, 0%, 0.114); + --grayA7: hsla(0, 0%, 0%, 0.141); + --grayA8: hsla(0, 0%, 0%, 0.22); + --grayA9: hsla(0, 0%, 0%, 0.439); + --grayA10: hsla(0, 0%, 0%, 0.478); + --grayA11: hsla(0, 0%, 0%, 0.565); + --grayA12: hsla(0, 0%, 0%, 0.91); + --blue1: hsl(206, 100%, 99.2%); + --blue2: hsl(210, 100%, 98%); + --blue3: hsl(209, 100%, 96.5%); + --blue4: hsl(210, 98.8%, 94%); + --blue5: hsl(209, 95%, 90.1%); + --blue6: hsl(209, 81.2%, 84.5%); + --blue7: hsl(208, 77.5%, 76.9%); + --blue8: hsl(206, 81.9%, 65.3%); + --blue9: hsl(206, 100%, 50%); + --blue10: hsl(208, 100%, 47.3%); + --blue11: hsl(211, 100%, 43.2%); + --blue12: hsl(211, 100%, 15%); +} +.dark { + --app-bg: var(--gray1); + --app-text: #ffffff; + --lowContrast: #000000; + --highContrast: #ffffff; + --gray1: hsl(0, 0%, 8.5%); + --gray2: hsl(0, 0%, 11%); + --gray3: hsl(0, 0%, 13.6%); + --gray4: hsl(0, 0%, 15.8%); + --gray5: hsl(0, 0%, 17.9%); + --gray6: hsl(0, 0%, 20.5%); + --gray7: hsl(0, 0%, 24.3%); + --gray8: hsl(0, 0%, 31.2%); + --gray9: hsl(0, 0%, 43.9%); + --gray10: hsl(0, 0%, 49.4%); + --gray11: hsl(0, 0%, 62.8%); + --gray12: hsl(0, 0%, 93%); + --grayA1: hsla(0, 0%, 100%, 0); + --grayA2: hsla(0, 0%, 100%, 0.026); + --grayA3: hsla(0, 0%, 100%, 0.056); + --grayA4: hsla(0, 0%, 100%, 0.077); + --grayA5: hsla(0, 0%, 100%, 0.103); + --grayA6: hsla(0, 0%, 100%, 0.129); + --grayA7: hsla(0, 0%, 100%, 0.172); + --grayA8: hsla(0, 0%, 100%, 0.249); + --grayA9: hsla(0, 0%, 100%, 0.386); + --grayA10: hsla(0, 0%, 100%, 0.446); + --grayA11: hsla(0, 0%, 100%, 0.592); + --grayA12: hsla(0, 0%, 100%, 0.923); + --blue1: hsl(212, 35%, 9.2%); + --blue2: hsl(216, 50%, 11.8%); + --blue3: hsl(214, 59.4%, 15.3%); + --blue4: hsl(214, 65.8%, 17.9%); + --blue5: hsl(213, 71.2%, 20.2%); + --blue6: hsl(212, 77.4%, 23.1%); + --blue7: hsl(211, 85.1%, 27.4%); + --blue8: hsl(211, 89.7%, 34.1%); + --blue9: hsl(206, 100%, 50%); + --blue10: hsl(209, 100%, 60.6%); + --blue11: hsl(210, 100%, 66.1%); + --blue12: hsl(206, 98%, 95.8%); +} + +div [command-dialog-mask] { + background-color: rgba(0, 0, 0, 0.3); + height: 100vh; + left: 0; + position: fixed; + top: 0; + width: 100vw; + z-index: 200; +} +div [command-dialog-wrapper] { + position: relative; + background: var(--gray2); + border-radius: 6px; + box-shadow: none; + flex-direction: column; + margin: 20vh auto auto; + max-width: 560px; + box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.51); +} +div [command-dialog-footer] { + border-top: 1px solid var(--gray6); + align-items: center; + background: var(--gray4); + color: var(--gray11); + border-radius: 0 0 8px 8px; + box-shadow: none; + display: flex; + flex-direction: row-reverse; + flex-shrink: 0; + height: 44px; + justify-content: space-between; + padding: 0 12px; + position: relative; + user-select: none; + width: 100%; + z-index: 300; + font-size: 12px; +} +.algolia [command-input] { + font-family: var(--font-sans); + width: 100%; + font-size: 18px; + padding: 12px; + outline: none; + background: var(--bg); + color: var(--gray12); + caret-color: var(--vcp-c-brand); + margin: 0; +} +.algolia [command-input]::placeholder { + color: var(--gray9); +} +.algolia [command-list] { + height: var(--command-list-height); + max-height: 360px; + overflow: auto; + overscroll-behavior: contain; + transition: 100ms ease; + transition-property: height; +} +.algolia .detail-list [command-item] { + min-height: 56px; + max-height: 112px; + padding: 10px 16px; + height: auto; +} +.algolia .detail-list [command-item] .des { + word-break: break-all; + white-space: wrap; + margin-top: 6px; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; +} +.algolia [command-item] { + position: relative; + content-visibility: auto; + cursor: pointer; + height: 56px; + font-size: 14px; + display: flex; + align-items: center; + gap: 12px; + padding: 0px 16px; + color: var(--gray12); + user-select: none; + will-change: background, color; + transition: all 150ms ease; + transition-property: none; + border-radius: 4px; + margin-top: 4px; + background-color: var(--lowContrast); +} +.algolia [command-item]:first-child { + margin-top: 0px; +} +.algolia [command-item][aria-selected="true"], +.algolia [command-item]:hover { + background: var(--vcp-c-brand); + color: #fff; +} +.algolia [command-item][aria-selected="true"] svg, +.algolia [command-item]:hover svg { + color: #fff; +} +.algolia [command-item][aria-selected="true"] [command-linear-shortcuts], +.algolia [command-item]:hover [command-linear-shortcuts] { + display: flex; + margin-left: auto; + gap: 8px; +} +.algolia [command-item][aria-selected="true"] [command-linear-shortcuts] kbd, +.algolia [command-item]:hover [command-linear-shortcuts] kbd { + font-family: var(--font-sans); + font-size: 13px; + color: var(--gray11); +} +.algolia [command-item]:active { + transition-property: background; + background: var(--gray4); +} +.algolia [command-item] svg { + width: 16px; + height: 16px; + color: var(--gray10); +} +.algolia [command-empty=""] { + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + height: 64px; + white-space: pre-wrap; + color: var(--gray11); +} +.algolia [command-dialog-mask] { + background-color: rgba(75, 75, 75, 0.8); +} +.algolia [command-dialog-header] { + padding: 12px; +} +.algolia [command-dialog-body] { + padding: 0 12px 12px; +} +.algolia [command-dialog-footer] { + align-items: center; + border-radius: 0 0 8px 8px; + box-shadow: + 0 -1px 0 0 #e0e3e8, + 0 -3px 6px 0 rgba(69, 98, 155, 0.12); + display: flex; + flex-direction: row-reverse; + flex-shrink: 0; + height: 44px; + justify-content: space-between; + padding: 0 12px; + position: relative; + user-select: none; + width: 100%; + z-index: 300; +} +.algolia [command-group-heading] { + color: var(--vcp-c-brand); + font-size: 0.85em; + font-weight: 600; + line-height: 32px; + margin: 0 -4px; + padding: 0 4px; + top: 0; + z-index: 10; + width: 100%; +} + +.algolia .command-palette-commands { + color: var(--docsearch-muted-color); + display: flex; + list-style: none; + margin: 0; + padding: 0; +} +@media screen and (max-width: 560px) { + .algolia .command-palette-commands { + display: none; + } + + div [command-dialog-wrapper] { + margin: 0; + height: 100vh; + } + .algolia [command-dialog-footer] { + justify-content: center; + } + .algolia [command-input] { + padding: 6px 4px; + } + .algolia [command-list] { + max-height: calc(100vh - 120px); + } +} +.algolia .command-palette-commands li { + display: flex; + align-items: center; +} +.algolia .command-palette-commands li:not(:last-of-type) { + margin-right: 0.8em; +} +.algolia .command-palette-logo a { + display: flex; + align-items: center; + gap: 8px; +} +.algolia .command-palette-logo svg { + height: 24px; + width: 24px; +} +.algolia .command-palette-commands-key { + align-items: center; + background: var(--gray3); + border-radius: 2px; + display: flex; + height: 18px; + justify-content: center; + margin-right: 0.4em; + padding: 0 0 1px; + color: var(--gray11); + border: 0; + width: 20px; +} +.dark .algolia [command-dialog-footer] { + box-shadow: none; +} +div[command-group] { + display: block !important; +} +div[command-item] { + display: flex !important; +} +.search-dialog div[command-item] > div.link { + width: 100%; +} +.search-dialog div[command-item] .title { + display: flex; + justify-content: space-between; +} + +.search-dialog div[command-item] .title i.prefix { + color: var(--vp-c-brand-1); +} +.search-dialog div[command-item]:hover .title i.prefix, +.search-dialog div[command-item][aria-selected="true"] .title i.prefix { + color: #fff; +} + +.search-dialog div[command-item] .des { + text-overflow: ellipsis; + overflow: hidden; + word-break: keep-all; + white-space: nowrap; +} +.search-dialog div[command-item] .headings { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; +} + +.search-dialog div[command-item] .date { + min-width: 86px; + text-align: right; +} +.search-dialog div[command-item] mark { + background: none; + color: var(--vp-c-brand-1); +} +.search-dialog div[command-item][aria-selected="true"] mark, +.search-dialog div[command-item]:hover mark { + color: inherit; + text-decoration: underline; +}