diff --git a/.eslintignore b/.eslintignore index aeb0777246..c5eb4767e7 100644 --- a/.eslintignore +++ b/.eslintignore @@ -22,6 +22,7 @@ packages/core/src/lib/markdown-it/highlight/*.js packages/core/src/lib/markdown-it/plugins/**/*.js packages/core/src/lib/markdown-it/utils/*.js packages/core/src/Page/*.js +packages/core/src/Pagefind/*.js packages/core/src/patches/**/*.js packages/core/src/plugins/**/*.js packages/core/src/Site/*.js diff --git a/.eslintrc.js b/.eslintrc.js index b8e6aa686e..0e15f03e85 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,7 +2,11 @@ /* eslint quotes: ["error", "double"] */ module.exports = { - "ignorePatterns": ["docs/_site/**", "**/dist/**", "**/node_modules/**"], + "ignorePatterns": [ + "docs/_site/**", + "**/dist/**", "**/node_modules/**", + "packages/core-web/asset/js/pagefind-lazyloader.js", + ], "env": { "node": true, "es6": true, diff --git a/.gitignore b/.gitignore index 1c7fbf4703..cbda62d66a 100644 --- a/.gitignore +++ b/.gitignore @@ -98,6 +98,7 @@ packages/core/src/lib/markdown-it/highlight/*.js packages/core/src/lib/markdown-it/plugins/**/*.js packages/core/src/lib/markdown-it/utils/*.js packages/core/src/Page/*.js +packages/core/src/Pagefind/*.js packages/core/src/plugins/**/*.js packages/core/src/Site/*.js packages/core/src/utils/*.js 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 32e743057d..0d45cd296f 100644 --- a/packages/cli/test/functional/test_site/expected/bugs/index.html +++ b/packages/cli/test/functional/test_site/expected/bugs/index.html @@ -16,7 +16,6 @@ - @@ -360,6 +359,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 2a68ba9cf4..0a7f9dc60f 100644 --- a/packages/cli/test/functional/test_site/expected/index.html +++ b/packages/cli/test/functional/test_site/expected/index.html @@ -16,7 +16,6 @@ - @@ -1026,6 +1025,6 @@

Heading in footer should not be }); - + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site/expected/markbind/js/pagefind-lazyloader.js b/packages/cli/test/functional/test_site/expected/markbind/js/pagefind-lazyloader.js new file mode 100644 index 0000000000..94657b8f7c --- /dev/null +++ b/packages/cli/test/functional/test_site/expected/markbind/js/pagefind-lazyloader.js @@ -0,0 +1,17 @@ +/** + * Lazy loader for Pagefind search functionality. + * This script dynamically imports pagefind.js only when needed (when user opens search). + * This avoids loading the search library on initial page load. + */ +window.__pagefind__ = null; + +window.loadPagefind = async () => { + if (!window.__pagefind__) { + // baseUrl is defined in page.njk script tag + const baseUrl = window.baseUrl || ''; + const module = await import(`${baseUrl}/markbind/pagefind/pagefind.js`); + window.__pagefind__ = module; + module.init(); + } + return window.__pagefind__; +}; \ 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 31ada973da..d41051c377 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,7 +15,6 @@ - @@ -367,6 +366,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 3734b76de8..f48431ff7d 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,7 +15,6 @@ - @@ -358,6 +357,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 38608aceb0..83deff93f9 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,7 +16,6 @@ - @@ -364,6 +363,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 38608aceb0..83deff93f9 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,7 +16,6 @@ - @@ -364,6 +363,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 f95952c9f0..9074592b94 100644 --- a/packages/cli/test/functional/test_site/expected/testAltFrontMatterInvalidKeyValue.html +++ b/packages/cli/test/functional/test_site/expected/testAltFrontMatterInvalidKeyValue.html @@ -16,7 +16,6 @@ - @@ -358,6 +357,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 75ba02c030..9b2e4bf012 100644 --- a/packages/cli/test/functional/test_site/expected/testAltFrontMatterParsing.html +++ b/packages/cli/test/functional/test_site/expected/testAltFrontMatterParsing.html @@ -16,7 +16,6 @@ - @@ -357,6 +356,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 e650797621..2d96641c6a 100644 --- a/packages/cli/test/functional/test_site/expected/testAnchorGeneration.html +++ b/packages/cli/test/functional/test_site/expected/testAnchorGeneration.html @@ -16,7 +16,6 @@ - @@ -426,6 +425,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 2720c6d11f..7981963eaf 100644 --- a/packages/cli/test/functional/test_site/expected/testAnnotate.html +++ b/packages/cli/test/functional/test_site/expected/testAnnotate.html @@ -16,7 +16,6 @@ - @@ -537,6 +536,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 e64e564f1d..9dffe20c03 100644 --- a/packages/cli/test/functional/test_site/expected/testAntiFOUCStyles.html +++ b/packages/cli/test/functional/test_site/expected/testAntiFOUCStyles.html @@ -16,7 +16,6 @@ - @@ -381,6 +380,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 03d7dfb182..48b3632963 100644 --- a/packages/cli/test/functional/test_site/expected/testBootstrapIconInPage.html +++ b/packages/cli/test/functional/test_site/expected/testBootstrapIconInPage.html @@ -14,7 +14,6 @@ - @@ -196,6 +195,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 8d4aead703..be1d4c3fc7 100644 --- a/packages/cli/test/functional/test_site/expected/testCenterText.html +++ b/packages/cli/test/functional/test_site/expected/testCenterText.html @@ -16,7 +16,6 @@ - @@ -357,6 +356,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 88a1ed3818..787f86e90b 100644 --- a/packages/cli/test/functional/test_site/expected/testCodeBlocks.html +++ b/packages/cli/test/functional/test_site/expected/testCodeBlocks.html @@ -16,7 +16,6 @@ - @@ -580,6 +579,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 554b73b8c4..63203442ab 100644 --- a/packages/cli/test/functional/test_site/expected/testDates.html +++ b/packages/cli/test/functional/test_site/expected/testDates.html @@ -16,7 +16,6 @@ - @@ -363,6 +362,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 bf52675edc..9d0de08504 100644 --- a/packages/cli/test/functional/test_site/expected/testEmptyAltFrontMatter.html +++ b/packages/cli/test/functional/test_site/expected/testEmptyAltFrontMatter.html @@ -19,7 +19,6 @@ - @@ -364,6 +363,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 03e11d7c2e..eb15879438 100644 --- a/packages/cli/test/functional/test_site/expected/testEmptyFrontmatter.html +++ b/packages/cli/test/functional/test_site/expected/testEmptyFrontmatter.html @@ -19,7 +19,6 @@ - @@ -364,6 +363,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 8f4c64b1aa..b78d20fdbe 100644 --- a/packages/cli/test/functional/test_site/expected/testExternalScripts.html +++ b/packages/cli/test/functional/test_site/expected/testExternalScripts.html @@ -19,7 +19,6 @@ - @@ -368,6 +367,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 77e7a81679..2a22d9bb90 100644 --- a/packages/cli/test/functional/test_site/expected/testFontAwesomeInPage.html +++ b/packages/cli/test/functional/test_site/expected/testFontAwesomeInPage.html @@ -14,7 +14,6 @@ - @@ -201,6 +200,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 62c9caca40..237255ec54 100644 --- a/packages/cli/test/functional/test_site/expected/testGlyphiconInPage.html +++ b/packages/cli/test/functional/test_site/expected/testGlyphiconInPage.html @@ -14,7 +14,6 @@ - @@ -198,6 +197,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 e48627dda5..bff6741b44 100644 --- a/packages/cli/test/functional/test_site/expected/testHr.html +++ b/packages/cli/test/functional/test_site/expected/testHr.html @@ -16,7 +16,6 @@ - @@ -366,6 +365,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 049dc7927d..66d4e3f25f 100644 --- a/packages/cli/test/functional/test_site/expected/testIconsInSiteLayout.html +++ b/packages/cli/test/functional/test_site/expected/testIconsInSiteLayout.html @@ -15,7 +15,6 @@ - @@ -200,6 +199,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 7e5b07e0df..5bcec4da01 100644 --- a/packages/cli/test/functional/test_site/expected/testImages.html +++ b/packages/cli/test/functional/test_site/expected/testImages.html @@ -16,7 +16,6 @@ - @@ -364,6 +363,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 45e284aff3..9b8873332d 100644 --- a/packages/cli/test/functional/test_site/expected/testIncludeBoilerplate.html +++ b/packages/cli/test/functional/test_site/expected/testIncludeBoilerplate.html @@ -16,7 +16,6 @@ - @@ -448,6 +447,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 e10f26bebf..b6caaf3f9f 100644 --- a/packages/cli/test/functional/test_site/expected/testIncludeMultipleModals.html +++ b/packages/cli/test/functional/test_site/expected/testIncludeMultipleModals.html @@ -16,7 +16,6 @@ - @@ -363,6 +362,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 a2e04a094f..ae24e91858 100644 --- a/packages/cli/test/functional/test_site/expected/testIncludePluginsRendered.html +++ b/packages/cli/test/functional/test_site/expected/testIncludePluginsRendered.html @@ -16,7 +16,6 @@ - @@ -358,6 +357,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 96779f963d..f6eb4688cc 100644 --- a/packages/cli/test/functional/test_site/expected/testLayouts.html +++ b/packages/cli/test/functional/test_site/expected/testLayouts.html @@ -19,7 +19,6 @@ - @@ -364,6 +363,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 0c548051ef..299ded7ce9 100644 --- a/packages/cli/test/functional/test_site/expected/testLayoutsOverride.html +++ b/packages/cli/test/functional/test_site/expected/testLayoutsOverride.html @@ -19,7 +19,6 @@ - @@ -364,6 +363,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 46d28ee8a1..1ed81e4d8b 100644 --- a/packages/cli/test/functional/test_site/expected/testLayoutsOverrideWithAltFrontmatter.html +++ b/packages/cli/test/functional/test_site/expected/testLayoutsOverrideWithAltFrontmatter.html @@ -19,7 +19,6 @@ - @@ -364,6 +363,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 8692e0e011..f5501582f1 100644 --- a/packages/cli/test/functional/test_site/expected/testLayoutsWithAltFrontMatter.html +++ b/packages/cli/test/functional/test_site/expected/testLayoutsWithAltFrontMatter.html @@ -19,7 +19,6 @@ - @@ -364,6 +363,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 7f083c94a2..291f1b17fd 100644 --- a/packages/cli/test/functional/test_site/expected/testLinks.html +++ b/packages/cli/test/functional/test_site/expected/testLinks.html @@ -16,7 +16,6 @@ - @@ -367,6 +366,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 4b3544ffd3..a81134368e 100644 --- a/packages/cli/test/functional/test_site/expected/testList.html +++ b/packages/cli/test/functional/test_site/expected/testList.html @@ -16,7 +16,6 @@ - @@ -1295,6 +1294,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 cc2bf50765..2753562466 100644 --- a/packages/cli/test/functional/test_site/expected/testMaterialIconsInPage.html +++ b/packages/cli/test/functional/test_site/expected/testMaterialIconsInPage.html @@ -14,7 +14,6 @@ - @@ -196,6 +195,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 0d9d1f3810..0b5418fbfa 100644 --- a/packages/cli/test/functional/test_site/expected/testMath.html +++ b/packages/cli/test/functional/test_site/expected/testMath.html @@ -16,7 +16,6 @@ - @@ -623,6 +622,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 cdb5a896c4..7d2d64fb2f 100644 --- a/packages/cli/test/functional/test_site/expected/testMermaid.html +++ b/packages/cli/test/functional/test_site/expected/testMermaid.html @@ -16,7 +16,6 @@ - @@ -442,6 +441,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 7f39286199..f876f0e8de 100644 --- a/packages/cli/test/functional/test_site/expected/testModals.html +++ b/packages/cli/test/functional/test_site/expected/testModals.html @@ -16,7 +16,6 @@ - @@ -499,6 +498,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 38608aceb0..83deff93f9 100644 --- a/packages/cli/test/functional/test_site/expected/testNunjucksPathResolving.html +++ b/packages/cli/test/functional/test_site/expected/testNunjucksPathResolving.html @@ -16,7 +16,6 @@ - @@ -364,6 +363,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 58504f38d3..c736e1dd6c 100644 --- a/packages/cli/test/functional/test_site/expected/testOcticonInPage.html +++ b/packages/cli/test/functional/test_site/expected/testOcticonInPage.html @@ -14,7 +14,6 @@ - @@ -209,6 +208,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 16a4e3d5ac..69d7720cbb 100644 --- a/packages/cli/test/functional/test_site/expected/testPageNav.html +++ b/packages/cli/test/functional/test_site/expected/testPageNav.html @@ -16,7 +16,6 @@ - @@ -372,6 +371,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 0ca0bd736f..2e142ad9b2 100644 --- a/packages/cli/test/functional/test_site/expected/testPageNavPrint.html +++ b/packages/cli/test/functional/test_site/expected/testPageNavPrint.html @@ -16,7 +16,6 @@ - @@ -370,6 +369,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 65301b6e9e..cdf5a77d5e 100644 --- a/packages/cli/test/functional/test_site/expected/testPageNavTarget.html +++ b/packages/cli/test/functional/test_site/expected/testPageNavTarget.html @@ -16,7 +16,6 @@ - @@ -360,6 +359,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 f8514a77da..6f079183b5 100644 --- a/packages/cli/test/functional/test_site/expected/testPageNavWithOnlyTitle.html +++ b/packages/cli/test/functional/test_site/expected/testPageNavWithOnlyTitle.html @@ -16,7 +16,6 @@ - @@ -360,6 +359,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 6939cbca7c..7e88de4619 100644 --- a/packages/cli/test/functional/test_site/expected/testPageNavWithoutTitleAndNavHeadings.html +++ b/packages/cli/test/functional/test_site/expected/testPageNavWithoutTitleAndNavHeadings.html @@ -16,7 +16,6 @@ - @@ -356,6 +355,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 6621bcb926..599cdeb590 100644 --- a/packages/cli/test/functional/test_site/expected/testPanelMarkdownParsing.html +++ b/packages/cli/test/functional/test_site/expected/testPanelMarkdownParsing.html @@ -16,7 +16,6 @@ - @@ -414,6 +413,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 a7f5d1076a..c7c28db5b4 100644 --- a/packages/cli/test/functional/test_site/expected/testPanels.html +++ b/packages/cli/test/functional/test_site/expected/testPanels.html @@ -16,7 +16,6 @@ - @@ -372,6 +371,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 80501edb40..2d233ebe99 100644 --- a/packages/cli/test/functional/test_site/expected/testPanelsClosingTransition.html +++ b/packages/cli/test/functional/test_site/expected/testPanelsClosingTransition.html @@ -16,7 +16,6 @@ - @@ -400,6 +399,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 ae43b60151..74fdb7111e 100644 --- a/packages/cli/test/functional/test_site/expected/testPlantUML.html +++ b/packages/cli/test/functional/test_site/expected/testPlantUML.html @@ -16,7 +16,6 @@ - @@ -362,6 +361,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 eb257f4edf..c6b289d6e3 100644 --- a/packages/cli/test/functional/test_site/expected/testPopoverTrigger.html +++ b/packages/cli/test/functional/test_site/expected/testPopoverTrigger.html @@ -16,7 +16,6 @@ - @@ -362,6 +361,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 b16cee8dc4..8c09dbba07 100644 --- a/packages/cli/test/functional/test_site/expected/testPopovers.html +++ b/packages/cli/test/functional/test_site/expected/testPopovers.html @@ -16,7 +16,6 @@ - @@ -447,6 +446,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 0b941c2bd6..e24d3635a4 100644 --- a/packages/cli/test/functional/test_site/expected/testSingleAltFrontMatter.html +++ b/packages/cli/test/functional/test_site/expected/testSingleAltFrontMatter.html @@ -16,7 +16,6 @@ - @@ -355,6 +354,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 2d0848e35e..09fb541968 100644 --- a/packages/cli/test/functional/test_site/expected/testSourceContainScript.html +++ b/packages/cli/test/functional/test_site/expected/testSourceContainScript.html @@ -16,7 +16,6 @@ - @@ -366,6 +365,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 1985c3f05c..05f334b50f 100644 --- a/packages/cli/test/functional/test_site/expected/testThumbnails.html +++ b/packages/cli/test/functional/test_site/expected/testThumbnails.html @@ -16,7 +16,6 @@ - @@ -454,6 +453,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 3d3fcb6f5f..72d9582af4 100644 --- a/packages/cli/test/functional/test_site/expected/testTooltipSpacing.html +++ b/packages/cli/test/functional/test_site/expected/testTooltipSpacing.html @@ -16,7 +16,6 @@ - @@ -364,6 +363,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 b0ce89a0ee..940c8a6964 100644 --- a/packages/cli/test/functional/test_site/expected/testTree.html +++ b/packages/cli/test/functional/test_site/expected/testTree.html @@ -16,7 +16,6 @@ - @@ -429,6 +428,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 14c6877edf..bae08627f6 100644 --- a/packages/cli/test/functional/test_site/expected/testVariableContainsInclude.html +++ b/packages/cli/test/functional/test_site/expected/testVariableContainsInclude.html @@ -16,7 +16,6 @@ - @@ -354,6 +353,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 5a0745c94f..c97bd83701 100644 --- a/packages/cli/test/functional/test_site/expected/testWeb3FormPlugin.html +++ b/packages/cli/test/functional/test_site/expected/testWeb3FormPlugin.html @@ -16,7 +16,6 @@ - @@ -408,6 +407,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 31e4586b29..7cfdf52aff 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,7 +15,6 @@ - @@ -354,6 +353,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 b32640633d..e325a6eb51 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,7 +12,6 @@ - @@ -139,6 +138,6 @@ document.getElementsByTagName('head')[0].appendChild(style); - + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_algolia_plugin/expected/markbind/js/pagefind-lazyloader.js b/packages/cli/test/functional/test_site_algolia_plugin/expected/markbind/js/pagefind-lazyloader.js new file mode 100644 index 0000000000..94657b8f7c --- /dev/null +++ b/packages/cli/test/functional/test_site_algolia_plugin/expected/markbind/js/pagefind-lazyloader.js @@ -0,0 +1,17 @@ +/** + * Lazy loader for Pagefind search functionality. + * This script dynamically imports pagefind.js only when needed (when user opens search). + * This avoids loading the search library on initial page load. + */ +window.__pagefind__ = null; + +window.loadPagefind = async () => { + if (!window.__pagefind__) { + // baseUrl is defined in page.njk script tag + const baseUrl = window.baseUrl || ''; + const module = await import(`${baseUrl}/markbind/pagefind/pagefind.js`); + window.__pagefind__ = module; + module.init(); + } + return window.__pagefind__; +}; \ 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 2ab9e81265..ac328d8f0a 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,7 +11,6 @@ - @@ -83,6 +82,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 cd81d441c7..4f732e5137 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,7 +10,6 @@ - @@ -74,6 +73,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 08af6259d7..8dd3efc512 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,7 +10,6 @@ - @@ -74,6 +73,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 f7e802da47..d72ae91df0 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,7 +10,6 @@ - @@ -74,6 +73,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 e5ec458162..31660d7472 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,7 +10,6 @@ - @@ -77,6 +76,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 e73b5612ab..cf6d74ab1f 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,7 +10,6 @@ - @@ -75,6 +74,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 0e19c0e15c..e07b5c1bbb 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,7 +10,6 @@ - @@ -79,6 +78,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 f25bb0febd..3a8e309289 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,7 +10,6 @@ - @@ -78,6 +77,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 1e9ccdd891..63afd97cf1 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,7 +10,6 @@ - @@ -78,6 +77,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 8af123b0cd..6f2d3daf6a 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,7 +10,6 @@ - @@ -78,6 +77,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 b1c0715985..3f4009ea57 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,7 +10,6 @@ - @@ -74,6 +73,6 @@ - + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/markbind/js/pagefind-lazyloader.js b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/markbind/js/pagefind-lazyloader.js new file mode 100644 index 0000000000..94657b8f7c --- /dev/null +++ b/packages/cli/test/functional/test_site_convert/test_basic_convert/expected/markbind/js/pagefind-lazyloader.js @@ -0,0 +1,17 @@ +/** + * Lazy loader for Pagefind search functionality. + * This script dynamically imports pagefind.js only when needed (when user opens search). + * This avoids loading the search library on initial page load. + */ +window.__pagefind__ = null; + +window.loadPagefind = async () => { + if (!window.__pagefind__) { + // baseUrl is defined in page.njk script tag + const baseUrl = window.baseUrl || ''; + const module = await import(`${baseUrl}/markbind/pagefind/pagefind.js`); + window.__pagefind__ = module; + module.init(); + } + return window.__pagefind__; +}; \ 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 b8f55949e2..bf07825aad 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,7 +11,6 @@ - @@ -120,6 +119,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 63ec5a7f13..a57e9e7b21 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,7 +10,6 @@ - @@ -111,6 +110,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 b130f9ac75..a2bdb7d7d6 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,7 +10,6 @@ - @@ -111,6 +110,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 849d441cad..b98ec4ed36 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,7 +10,6 @@ - @@ -112,6 +111,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 46ad53d71b..8936b7006a 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,7 +10,6 @@ - @@ -112,6 +111,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 ba39325727..f2d5e7a401 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,7 +10,6 @@ - @@ -115,6 +114,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 df73165eb2..973b81a1f9 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,7 +10,6 @@ - @@ -114,6 +113,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 e521204fd6..34536fb639 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,7 +10,6 @@ - @@ -114,6 +113,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 af1b560e67..bbf7df1cd0 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,7 +10,6 @@ - @@ -114,6 +113,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 8c9d5c790e..d8f0b189e2 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,7 +10,6 @@ - @@ -112,6 +111,6 @@

Readme + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/markbind/js/pagefind-lazyloader.js b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/markbind/js/pagefind-lazyloader.js new file mode 100644 index 0000000000..94657b8f7c --- /dev/null +++ b/packages/cli/test/functional/test_site_convert/test_navigation_convert/expected/markbind/js/pagefind-lazyloader.js @@ -0,0 +1,17 @@ +/** + * Lazy loader for Pagefind search functionality. + * This script dynamically imports pagefind.js only when needed (when user opens search). + * This avoids loading the search library on initial page load. + */ +window.__pagefind__ = null; + +window.loadPagefind = async () => { + if (!window.__pagefind__) { + // baseUrl is defined in page.njk script tag + const baseUrl = window.baseUrl || ''; + const module = await import(`${baseUrl}/markbind/pagefind/pagefind.js`); + window.__pagefind__ = module; + module.init(); + } + return window.__pagefind__; +}; \ 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 20fa0d7548..87cee749e6 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,7 +10,6 @@ - @@ -111,6 +110,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 74d5fafe75..d5d3c229df 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,7 +10,6 @@ - @@ -111,6 +110,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 27c80db52f..b60bcc1ad2 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,7 +10,6 @@ - @@ -111,6 +110,6 @@

Sample cont - + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_custom_plugins/expected/index.html b/packages/cli/test/functional/test_site_custom_plugins/expected/index.html index ed8ba38345..befb117d1d 100644 --- a/packages/cli/test/functional/test_site_custom_plugins/expected/index.html +++ b/packages/cli/test/functional/test_site_custom_plugins/expected/index.html @@ -11,7 +11,6 @@ - @@ -52,6 +51,6 @@ - + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_custom_plugins/expected/markbind/js/pagefind-lazyloader.js b/packages/cli/test/functional/test_site_custom_plugins/expected/markbind/js/pagefind-lazyloader.js new file mode 100644 index 0000000000..94657b8f7c --- /dev/null +++ b/packages/cli/test/functional/test_site_custom_plugins/expected/markbind/js/pagefind-lazyloader.js @@ -0,0 +1,17 @@ +/** + * Lazy loader for Pagefind search functionality. + * This script dynamically imports pagefind.js only when needed (when user opens search). + * This avoids loading the search library on initial page load. + */ +window.__pagefind__ = null; + +window.loadPagefind = async () => { + if (!window.__pagefind__) { + // baseUrl is defined in page.njk script tag + const baseUrl = window.baseUrl || ''; + const module = await import(`${baseUrl}/markbind/pagefind/pagefind.js`); + window.__pagefind__ = module; + module.init(); + } + return window.__pagefind__; +}; \ 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 887016a2a1..e6fef9665c 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,7 +11,6 @@ - @@ -105,6 +104,6 @@

So far as to comply with t - + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_special_tags/expected/markbind/js/pagefind-lazyloader.js b/packages/cli/test/functional/test_site_special_tags/expected/markbind/js/pagefind-lazyloader.js new file mode 100644 index 0000000000..94657b8f7c --- /dev/null +++ b/packages/cli/test/functional/test_site_special_tags/expected/markbind/js/pagefind-lazyloader.js @@ -0,0 +1,17 @@ +/** + * Lazy loader for Pagefind search functionality. + * This script dynamically imports pagefind.js only when needed (when user opens search). + * This avoids loading the search library on initial page load. + */ +window.__pagefind__ = null; + +window.loadPagefind = async () => { + if (!window.__pagefind__) { + // baseUrl is defined in page.njk script tag + const baseUrl = window.baseUrl || ''; + const module = await import(`${baseUrl}/markbind/pagefind/pagefind.js`); + window.__pagefind__ = module; + module.init(); + } + return window.__pagefind__; +}; \ 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 94752d909b..4f1ecb1b16 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,7 +12,6 @@ - @@ -431,6 +430,6 @@

Welcome to MarkBind + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_table_plugin/expected/markbind/js/pagefind-lazyloader.js b/packages/cli/test/functional/test_site_table_plugin/expected/markbind/js/pagefind-lazyloader.js new file mode 100644 index 0000000000..94657b8f7c --- /dev/null +++ b/packages/cli/test/functional/test_site_table_plugin/expected/markbind/js/pagefind-lazyloader.js @@ -0,0 +1,17 @@ +/** + * Lazy loader for Pagefind search functionality. + * This script dynamically imports pagefind.js only when needed (when user opens search). + * This avoids loading the search library on initial page load. + */ +window.__pagefind__ = null; + +window.loadPagefind = async () => { + if (!window.__pagefind__) { + // baseUrl is defined in page.njk script tag + const baseUrl = window.baseUrl || ''; + const module = await import(`${baseUrl}/markbind/pagefind/pagefind.js`); + window.__pagefind__ = module; + module.init(); + } + return window.__pagefind__; +}; \ 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 15ae45b31e..d759a23d63 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,7 +10,6 @@ - @@ -40,6 +39,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 9e9302ea71..421c71759e 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,7 +12,6 @@ - @@ -110,6 +109,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 b02dac5ebb..7bb587784d 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,7 +12,6 @@ - @@ -110,6 +109,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 cff4ba6ae2..6a291b10a2 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,7 +12,6 @@ - @@ -110,6 +109,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 ebcc1d2992..c30933a90c 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,7 +12,6 @@ - @@ -110,6 +109,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 47654b0f2d..2f3fe0f899 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,7 +12,6 @@ - @@ -158,6 +157,6 @@
User Guide: Syntax Overview - + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_default/expected/markbind/js/pagefind-lazyloader.js b/packages/cli/test/functional/test_site_templates/test_default/expected/markbind/js/pagefind-lazyloader.js new file mode 100644 index 0000000000..94657b8f7c --- /dev/null +++ b/packages/cli/test/functional/test_site_templates/test_default/expected/markbind/js/pagefind-lazyloader.js @@ -0,0 +1,17 @@ +/** + * Lazy loader for Pagefind search functionality. + * This script dynamically imports pagefind.js only when needed (when user opens search). + * This avoids loading the search library on initial page load. + */ +window.__pagefind__ = null; + +window.loadPagefind = async () => { + if (!window.__pagefind__) { + // baseUrl is defined in page.njk script tag + const baseUrl = window.baseUrl || ''; + const module = await import(`${baseUrl}/markbind/pagefind/pagefind.js`); + window.__pagefind__ = module; + module.init(); + } + return window.__pagefind__; +}; \ 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 296624bd9b..5eb9a3b406 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,7 +11,6 @@ - @@ -40,6 +39,6 @@

Welcome to MarkBind + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_minimal/expected/markbind/js/pagefind-lazyloader.js b/packages/cli/test/functional/test_site_templates/test_minimal/expected/markbind/js/pagefind-lazyloader.js new file mode 100644 index 0000000000..94657b8f7c --- /dev/null +++ b/packages/cli/test/functional/test_site_templates/test_minimal/expected/markbind/js/pagefind-lazyloader.js @@ -0,0 +1,17 @@ +/** + * Lazy loader for Pagefind search functionality. + * This script dynamically imports pagefind.js only when needed (when user opens search). + * This avoids loading the search library on initial page load. + */ +window.__pagefind__ = null; + +window.loadPagefind = async () => { + if (!window.__pagefind__) { + // baseUrl is defined in page.njk script tag + const baseUrl = window.baseUrl || ''; + const module = await import(`${baseUrl}/markbind/pagefind/pagefind.js`); + window.__pagefind__ = module; + module.init(); + } + return window.__pagefind__; +}; \ 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 d692bf5e08..f01df565e1 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,7 +12,6 @@ - @@ -252,6 +251,6 @@

Project title + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_portfolio/expected/markbind/js/pagefind-lazyloader.js b/packages/cli/test/functional/test_site_templates/test_portfolio/expected/markbind/js/pagefind-lazyloader.js new file mode 100644 index 0000000000..94657b8f7c --- /dev/null +++ b/packages/cli/test/functional/test_site_templates/test_portfolio/expected/markbind/js/pagefind-lazyloader.js @@ -0,0 +1,17 @@ +/** + * Lazy loader for Pagefind search functionality. + * This script dynamically imports pagefind.js only when needed (when user opens search). + * This avoids loading the search library on initial page load. + */ +window.__pagefind__ = null; + +window.loadPagefind = async () => { + if (!window.__pagefind__) { + // baseUrl is defined in page.njk script tag + const baseUrl = window.baseUrl || ''; + const module = await import(`${baseUrl}/markbind/pagefind/pagefind.js`); + window.__pagefind__ = module; + module.init(); + } + return window.__pagefind__; +}; \ 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 0fdfb24ca0..b8b1ac9fef 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,7 +12,6 @@ - @@ -179,6 +178,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 ba76eeb4eb..9bd675999f 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,7 +12,6 @@ - @@ -273,6 +272,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 13571129ad..3e26f4332a 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,7 +12,6 @@ - @@ -236,6 +235,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 f5b3a0f9c0..40f8f0b615 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,7 +12,6 @@ - @@ -197,6 +196,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 e56811538d..f5bddc2d9d 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,7 +12,6 @@ - @@ -200,6 +199,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 4af853d462..787119322f 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,7 +12,6 @@ - @@ -222,6 +221,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 457352d11c..5b08981f60 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,7 +12,6 @@ - @@ -278,6 +277,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 90206827a9..3e5286d3b4 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,7 +12,6 @@ - @@ -236,6 +235,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 33a3579fe2..2711e70317 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,7 +12,6 @@ - @@ -211,6 +210,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 6a0f53f07e..e92941b995 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,7 +12,6 @@ - @@ -235,6 +234,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 51c7f4ed34..6d7b56dd1d 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,7 +12,6 @@ - @@ -196,6 +195,6 @@

ProjectEx + \ No newline at end of file diff --git a/packages/cli/test/functional/test_site_templates/test_project/expected/markbind/js/pagefind-lazyloader.js b/packages/cli/test/functional/test_site_templates/test_project/expected/markbind/js/pagefind-lazyloader.js new file mode 100644 index 0000000000..94657b8f7c --- /dev/null +++ b/packages/cli/test/functional/test_site_templates/test_project/expected/markbind/js/pagefind-lazyloader.js @@ -0,0 +1,17 @@ +/** + * Lazy loader for Pagefind search functionality. + * This script dynamically imports pagefind.js only when needed (when user opens search). + * This avoids loading the search library on initial page load. + */ +window.__pagefind__ = null; + +window.loadPagefind = async () => { + if (!window.__pagefind__) { + // baseUrl is defined in page.njk script tag + const baseUrl = window.baseUrl || ''; + const module = await import(`${baseUrl}/markbind/pagefind/pagefind.js`); + window.__pagefind__ = module; + module.init(); + } + return window.__pagefind__; +}; \ 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 0408394b09..906f17294f 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,7 +12,6 @@ - @@ -224,6 +223,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 e3e4d639b3..430344e573 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,7 +12,6 @@ - @@ -242,6 +241,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 554c860d46..1359293dd4 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,7 +12,6 @@ - @@ -194,6 +193,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 86a07dd91e..ed70cf915e 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,7 +12,6 @@ - @@ -226,6 +225,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 72c80b4616..a220ef1075 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,7 +12,6 @@ - @@ -194,6 +193,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 90cce628f8..b4f99dab79 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,7 +12,6 @@ - @@ -195,6 +194,6 @@

Purpose of this Guide MarkBind.setupWithSearch() - + \ No newline at end of file diff --git a/packages/core-web/asset/js/pagefind-lazyloader.js b/packages/core-web/asset/js/pagefind-lazyloader.js new file mode 100644 index 0000000000..94657b8f7c --- /dev/null +++ b/packages/core-web/asset/js/pagefind-lazyloader.js @@ -0,0 +1,17 @@ +/** + * Lazy loader for Pagefind search functionality. + * This script dynamically imports pagefind.js only when needed (when user opens search). + * This avoids loading the search library on initial page load. + */ +window.__pagefind__ = null; + +window.loadPagefind = async () => { + if (!window.__pagefind__) { + // baseUrl is defined in page.njk script tag + const baseUrl = window.baseUrl || ''; + const module = await import(`${baseUrl}/markbind/pagefind/pagefind.js`); + window.__pagefind__ = module; + module.init(); + } + return window.__pagefind__; +}; \ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json index 175a158801..8125ca5aa0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -14,6 +14,9 @@ ".": { "types": "./index.d.ts", "default": "./index.js" + },"./src/Pagefind": { + "types": "./src/Pagefind/index.d.ts", + "default": "./src/Pagefind/index.js" }, "./src/utils/fsUtil": { "types": "./src/utils/fsUtil.d.ts", diff --git a/packages/core/src/Page/PageConfig.ts b/packages/core/src/Page/PageConfig.ts index 8053732136..a344ef26ae 100644 --- a/packages/core/src/Page/PageConfig.ts +++ b/packages/core/src/Page/PageConfig.ts @@ -27,6 +27,7 @@ export interface PageAssets { pluginLinks?: string[]; pagefindCss?: string; pagefindJs?: string; + pagefindLazyLoaderJs?: string; } /** diff --git a/packages/core/src/Page/page.njk b/packages/core/src/Page/page.njk index be7d946441..7a96999e7d 100644 --- a/packages/core/src/Page/page.njk +++ b/packages/core/src/Page/page.njk @@ -19,7 +19,6 @@ {% if asset.bootstrapIcons %} {%- endif -%} {%- if not dev -%}{%- endif -%} - {% if asset.pagefindCss %} {%- endif -%} {%- if asset.pluginLinks -%} {%- for link in asset.pluginLinks -%} {{ link }} @@ -58,8 +57,8 @@ {{ script }} {%- endfor %} {%- endif %} -{%- if asset.pagefindJs %} - +{%- if asset.pagefindLazyLoaderJs %} + {%- endif %} {%- if asset.scriptBottom %} {%- for scripts in asset.scriptBottom %} diff --git a/packages/core/src/Pagefind/index.ts b/packages/core/src/Pagefind/index.ts new file mode 100644 index 0000000000..6669a80517 --- /dev/null +++ b/packages/core/src/Pagefind/index.ts @@ -0,0 +1,9 @@ +export type{ + PagefindWordLocation, + PagefindSearchAnchor, + PagefindMeta, + PagefindSubResult, + PagefindSearchFragment, + FormattedSearchResult, +} from './types.js'; +export * from './searchUtils.js'; diff --git a/packages/core/src/Pagefind/searchUtils.ts b/packages/core/src/Pagefind/searchUtils.ts new file mode 100644 index 0000000000..fc5f6dfcdb --- /dev/null +++ b/packages/core/src/Pagefind/searchUtils.ts @@ -0,0 +1,251 @@ +/** + * @fileoverview Search result formatting utilities for Pagefind. + * These functions transform raw Pagefind API responses into display-ready format. + * @see https://pagefind.app/docs/api/ + */ + +import { isNumber } from 'lodash'; +import type { + PagefindSearchFragment, PagefindSubResult, PagefindSearchAnchor, FormattedSearchResult, +} from './types.js'; + +/** + * Truncates an excerpt to ensure the tags are visible. + * - Shows max 15 chars before + * - Shows all content after (no limit) + * - Adds ellipsis if prefix doesn't start at word boundary + * - Handles HTML entities in the prefix + * + * @param excerpt - The raw excerpt from Pagefind + * @returns Truncated excerpt with visible + */ +function truncateExcerptToShowMark(excerpt: string): string { + const markStart = excerpt.indexOf(''); + + // No mark found, return as is + if (markStart === -1) return excerpt; + + // If mark is at position 0, return as is + if (markStart === 0) return excerpt; + + // Get up to 15 chars before + const prefix = excerpt.substring(0, markStart); + const truncatedPrefix = prefix.slice(-15); // Last 15 chars + + // Check if starts at word boundary: + // - Any whitespace (space, tab, newline) + // - Any non-alphanumeric character + const firstChar = truncatedPrefix[0]; + const isWordBoundary = /[\s\d_\-.,;:'"()[\]{}|\\/@#$%^&*!~`]/.test(firstChar); + + if (!isWordBoundary) { + // Find the first word boundary in the prefix (within 15 chars of end) + const searchArea = prefix.slice(-15); + const wordBoundaryMatch = searchArea.match(/[\s\d_\-.,;:'"()[\]{}|\\/@#$%^&*!~`]/); + if (wordBoundaryMatch) { + const lastBoundaryIndex = prefix.lastIndexOf(wordBoundaryMatch[0], markStart - 1); + if (lastBoundaryIndex !== -1 && lastBoundaryIndex < markStart) { + return `...${prefix.substring(lastBoundaryIndex + 1)}${excerpt.substring(markStart)}`; + } + } + // Fallback: use ellipsis + truncated + return `...${truncatedPrefix}${excerpt.substring(markStart)}`; + } + + // Starts at word boundary, no ellipsis needed + return truncatedPrefix + excerpt.substring(markStart); +} + +/** + * Merges consecutive tags into a single tag. + * e.g., "making the" becomes "making the" + * This ensures that terms grouped together in the excerpt are displayed as a single highlighted segment. + * + * @param excerpt - The excerpt with potential consecutive tags + * @returns Excerpt with merged tags + */ +function mergeConsecutiveMarks(excerpt: string): string { + return excerpt.replace(/<\/mark>\s*/g, ' '); +} + +/** + * Parses a single sub-result (heading/section) within a page into a display-ready format. + * + * This function constructs a hierarchical title (breadcrumb) by finding all anchor elements + * that appear before the current sub-result. For example, if the page has: + * - h1: "Installation" + * - h2: "Windows" + * - h3: "Troubleshooting" + * + * And the sub-result is "Troubleshooting", the title becomes "Installation > Windows > Troubleshooting". + * + * @param sub - The sub-result from Pagefind + * @param anchors - All anchor elements on the page + * @param result - The parent Pagefind result + * @returns Formatted search result with hierarchical title + * @see https://pagefind.app/docs/sub-results/ + */ +function parseSubResult( + sub: PagefindSubResult, + anchors: PagefindSearchAnchor[], + result: PagefindSearchFragment, +): FormattedSearchResult { + const route = sub?.url || result?.url; + const description = mergeConsecutiveMarks(truncateExcerptToShowMark(sub?.excerpt || result?.excerpt || '')); + const title = sub.title || ''; + + return { + route, + meta: { + ...result.meta, + title, + description, + }, + result, + isSubResult: true, + isLastSubResult: false, + }; +} + +/** + * Formats raw Pagefind search results for display in the UI. + * + * This function performs four key transformations: + * + * 1. **Sort by `balance_score`**: Sorts weighted_locations by their `balance_score` (descending), + * then by `weight` (descending), then position (ascending) as a tie-breaker. + * This prioritizes matches by their scores first, then + * higher-weighted sections (e.g., headings) over body text, then finally their position on the page + * + * 2. **Pick Top Sub-Results**: Iterates through sorted locations and finds + * which sub-results (headings) contain those locations. If multiple + * sub-results contain the same location, keeps the one with more context + * (more locations). Stops after collecting `count` results. + * + * 3. **Re-sort by Document Order**: Resorts the selected sub-results by their + * position in the document, so they appear in natural reading order. + * + * 4. **Deduplicate**: Removes duplicate titles that may arise from overlapping matches. + * + * @param result - Raw Pagefind result from `pagefind.search().results[i].data()` + * @param count - Maximum number of sub-results to return per page (default: 1) + * @returns Array of formatted results ready for display + * @see https://pagefind.app/docs/api-reference + * @see https://pagefind.app/docs/ranking/ + * @see https://pagefind.app/docs/sub-results/ + * + * @example + * ```typescript + * const search = await pagefind.search("installation"); + * const results = await Promise.all(search.results.map(r => r.data())); + * const formatted = results.flatMap(r => formatPagefindResult(r, 2)); + * // Returns up to 2 sub-results per page, sorted by relevance + * ``` + */ +export function formatPagefindResult( + result: PagefindSearchFragment, + count = 10, +): FormattedSearchResult[] { + const { sub_results: subResults, anchors, weighted_locations: weightedLocations } = result; + + // If no sub_results, return the main result as a non-sub-result + if (!subResults || subResults.length === 0) { + return [ + { + route: result.url, + meta: { + ...result.meta, + title: result.meta.title || '', + description: mergeConsecutiveMarks(truncateExcerptToShowMark(result.excerpt || '')), + }, + result, + isSubResult: false, + isLastSubResult: false, + }, + ]; + } + + const sortedLocations = [...weightedLocations].sort((a, b) => { + if (b.balanced_score === a.balanced_score) { + // If equal balanced_score -> weight -> earlier position in document comes first. + if (a.weight === b.weight) { + return a.location - b.location; + } + return b.weight - a.weight; + } + return b.balanced_score - a.balanced_score; + }); + + // For each location, find matching subresults, + // Then pick the subresult with the top `count` based on weighted locations. + const subs: PagefindSubResult[] = []; + sortedLocations.forEach(({ location }) => { + if (subs.length >= count) return; + + // Find sub-results that contain this weighted location + const filterData = subResults.filter((sub: PagefindSubResult) => { + if (sub.title === result.meta.title) return false; // Skip page-level match + const { locations } = sub; + const [min] = locations || []; + if (!isNumber(min)) return false; + const max = locations.length === 1 ? Number.POSITIVE_INFINITY : locations[locations.length - 1]; + return min <= location && location <= max; + }); + + // Keep the sub-result with the most locations (most context) + const sub = filterData.reduce((prev, curr) => { + if (!prev) return curr; + return prev.locations.length > curr.locations.length ? prev : curr; + }, null); + + // Add only unique sub-results + if (sub && !subs.some(existing => existing.title === sub.title)) { + subs.push(sub); + } + }); + + // Re-sort by document order (position in page). + subs.sort((a, b) => { + const [minA] = a.locations || []; + const [minB] = b.locations || []; + if (minA == null || minB == null) { + return 0; + } + return minA - minB; + }); + + // Remove duplicate entries that may occur from overlapping matches. + const filterMap = new Map(); + const formattedSubResults = subs + .map((sub: PagefindSubResult) => parseSubResult(sub, anchors, result)) + .filter((v: FormattedSearchResult) => { + if (filterMap.has(v.meta.title)) { + return false; + } + filterMap.set(v.meta.title, v); + return true; + }); + + // Mark the last sub-result + formattedSubResults.forEach((sub, index) => { + // eslint-disable-next-line no-param-reassign + sub.isLastSubResult = index === formattedSubResults.length - 1; + }); + + // Return main result first, then sub-results + const mainResult = [ + { + route: result.url, + meta: { + ...result.meta, + title: result.meta.title || '', + description: mergeConsecutiveMarks(truncateExcerptToShowMark(result.excerpt || '')), + }, + result, + isSubResult: false, + isLastSubResult: false, + }, + ]; + + return [...mainResult, ...formattedSubResults]; +} diff --git a/packages/core/src/Pagefind/types.ts b/packages/core/src/Pagefind/types.ts new file mode 100644 index 0000000000..ce8187e8c1 --- /dev/null +++ b/packages/core/src/Pagefind/types.ts @@ -0,0 +1,138 @@ +/** + * @fileoverview TypeScript type definitions for Pagefind search results. + * These types mirror the Pagefind JavaScript API response structure. + * @see https://pagefind.app/docs/api-reference + */ + +/** + * Information about a matching word on a page. + * + * The `weight` field indicates the importance of this match based on: + * - Default heading weights (h1=7.0, h2=6.0, ..., h6=2.0) + * - Custom weights via `data-pagefind-weight` attribute + * + * The `balanced_score` is Pagefind's internal relevance calculation, + * which can be used for custom ranking beyond Pagefind's default. + * + * @see https://pagefind.app/docs/ranking/ + * @see https://pagefind.app/docs/weighting/ + */ +export interface PagefindWordLocation { + /** The weight this word was originally tagged as (from heading level or data-pagefind-weight) */ + weight: number; + /** Internal score calculated by Pagefind for this word - use for custom ranking */ + balanced_score: number; + /** The index of this word in the result content (character position) */ + location: number; +} + +/** + * Raw data about elements with IDs that Pagefind encountered when indexing the page. + * These are used to construct hierarchical titles (breadcrumbs) for sub-results. + * + * @see https://pagefind.app/docs/sub-results/ + */ +export interface PagefindSearchAnchor { + /** The HTML element type (e.g., 'h1', 'h2', 'div') */ + element: string; + /** The value of the id attribute */ + id: string; + /** The text content of the element */ + text: string; + /** Position of this anchor in the result content */ + location: number; +} + +/** + * Metadata fields associated with a page. + * These can be set during indexing via frontmatter or `data-pagefind-meta` attributes. + * + * @see https://pagefind.app/docs/metadata/ + * @see https://pagefind.app/docs/js-api-metadata/ + */ +export type PagefindMeta = Record; + +/** + * Represents a sub-result within a page - typically a heading/section that matches the search query. + * Pagefind automatically detects headings with IDs and returns them as sub-results. + * + * @see https://pagefind.app/docs/sub-results/ + */ +export interface PagefindSubResult { + /** Title of this sub-result (derived from heading content) */ + title: string; + /** URL to this specific section (page URL + heading hash) */ + url: string; + /** The anchor element associated with this sub-result */ + anchor: PagefindSearchAnchor; + /** Match locations with weight information for this specific segment */ + weighted_locations: PagefindWordLocation[]; + /** All locations where search terms match in this segment */ + locations: number[]; + /** Excerpt with `` tags highlighting the matching terms */ + excerpt: string; +} + +/** + * The complete raw result from a Pagefind search query. + * This is returned when calling `result.data()` on a search result. + * + * @see https://pagefind.app/docs/api-reference + * @see https://pagefind.app/docs/api/ + */ +export interface PagefindSearchFragment { + /** Processed URL for this page (includes baseUrl if configured) */ + url: string; + /** Full text content of the page */ + content: string; + /** Total word count on the page */ + word_count: number; + /** Filter keys and values this page was tagged with */ + filters: Record; + /** Metadata fields for this page */ + meta: PagefindMeta; + /** All anchor elements (headings with IDs) found on the page */ + anchors: PagefindSearchAnchor[]; + /** + * All matching word locations with their weights and relevance scores. + * This is the key data for understanding how Pagefind ranked this result. + * @see https://pagefind.app/docs/ranking/ + */ + weighted_locations: PagefindWordLocation[]; + /** All locations where search terms match on this page */ + locations: number[]; + /** Raw unprocessed content */ + raw_content: string; + /** Original URL before processing */ + raw_url: string; + /** Processed excerpt with `` tags highlighting matching terms */ + excerpt: string; + /** Sub-results (headings/sections) that contain matching terms */ + sub_results: PagefindSubResult[]; +} + +/** + * Formatted search result ready for display in the UI. + * This is the output of the formatting utilities in searchUtils.ts. + */ +export interface FormattedSearchResult { + /** The URL to navigate to when selected */ + route: string; + /** Processed metadata for display */ + meta: { + /** Optional date for sorting/display */ + date?: number; + /** Display title (may include hierarchical breadcrumbs) */ + title: string; + /** Excerpt with highlighted terms */ + description: string; + /** Additional metadata properties */ + [key: string]: unknown; + }; + /** Reference to the original raw Pagefind result */ + result: PagefindSearchFragment; + /** Whether this result is a sub-result (heading within a page) vs main result (full page) */ + isSubResult: boolean; + /** Whether this is the last sub-result before a new main result (used for icon styling) */ + isLastSubResult: boolean; +} diff --git a/packages/core/src/Site/SitePagesManager.ts b/packages/core/src/Site/SitePagesManager.ts index 0978f12b17..35b85820d1 100644 --- a/packages/core/src/Site/SitePagesManager.ts +++ b/packages/core/src/Site/SitePagesManager.ts @@ -148,11 +148,11 @@ 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') + ? path.posix.join(baseAssetsPath, 'pagefind', 'pagefind.js') + : undefined, + pagefindLazyLoaderJs: this.siteConfig.enableSearch && this.pagefindIndexingSucceeded + ? path.posix.join(baseAssetsPath, 'js', 'pagefind-lazyloader.js') : undefined, }, baseUrlMap: this.baseUrlMap, diff --git a/packages/core/test/unit/Pagefind/searchUtils.test.ts b/packages/core/test/unit/Pagefind/searchUtils.test.ts new file mode 100644 index 0000000000..25c43af04e --- /dev/null +++ b/packages/core/test/unit/Pagefind/searchUtils.test.ts @@ -0,0 +1,328 @@ +/** + * Unit tests for Pagefind search result formatting utilities. + * + * These tests validate the transformation of raw Pagefind API responses + * into display-ready format for the MarkBind search UI. + */ +import { formatPagefindResult } from '../../../src/Pagefind/index.js'; +import type { + PagefindSearchFragment, + PagefindSubResult, + PagefindWordLocation, + PagefindSearchAnchor, +} from '../../../src/Pagefind/index'; + +const createWordLocation = ( + location: number, + weight: number = 1, + balanced_score: number = 1, +): PagefindWordLocation => ({ + location, + weight, + balanced_score, +}); + +const createAnchor = ( + element: string, + id: string, + text: string, + location: number, +): PagefindSearchAnchor => ({ + element, + id, + text, + location, +}); + +const createSubResult = ( + title: string, + url: string, + locations: number[], + excerpt: string, + weighted_locations: PagefindWordLocation[] = [], +): PagefindSubResult => ({ + title, + url, + anchor: createAnchor('h2', title.toLowerCase().replace(/\s+/g, '-'), title, locations[0] || 0), + locations, + excerpt, + weighted_locations, +}); + +const createPagefindResult = ( + url: string, + title: string, + excerpt: string, + subResults: PagefindSubResult[] = [], + weightedLocations: PagefindWordLocation[] = [], +): PagefindSearchFragment => ({ + url, + content: '', + word_count: 100, + filters: {}, + meta: { title }, + anchors: subResults.map(sr => sr.anchor), + weighted_locations: weightedLocations, + locations: weightedLocations.map(wl => wl.location), + raw_content: '', + raw_url: url, + excerpt, + sub_results: subResults, +}); + +describe('formatPagefindResult', () => { + describe('main result handling', () => { + it('should return main result only when there are no sub-results', () => { + const result = createPagefindResult('/page1', 'Page 1', 'Main content here'); + const formatted = formatPagefindResult(result); + + expect(formatted).toHaveLength(1); + expect(formatted[0].isSubResult).toBe(false); + expect(formatted[0].route).toBe('/page1'); + expect(formatted[0].meta.title).toBe('Page 1'); + }); + + it('should include meta information in main result', () => { + const result = createPagefindResult('/page1', 'Test Page', 'Test excerpt'); + const formatted = formatPagefindResult(result); + + expect(formatted[0].meta).toHaveProperty('title', 'Test Page'); + expect(formatted[0].meta).toHaveProperty('description'); + }); + }); + + describe('sub-result handling', () => { + it('should return main result plus single sub-result', () => { + const subResult = createSubResult('Installation', '/page1#installation', [50], 'How to install'); + const weightedLocations = [createWordLocation(50, 5, 10)]; + const pageResult = createPagefindResult( + '/page1', 'Page 1', 'Main excerpt', [subResult], weightedLocations); + + const formatted = formatPagefindResult(pageResult); + + expect(formatted).toHaveLength(2); + expect(formatted[0].isSubResult).toBe(false); + expect(formatted[1].isSubResult).toBe(true); + expect(formatted[1].meta.title).toBe('Installation'); + }); + + it('should respect count limit for sub-results', () => { + const sub1 = createSubResult('Section 1', '/page1#section-1', [10], 'Content 1'); + const sub2 = createSubResult('Section 2', '/page1#section-2', [30], 'Content 2'); + const sub3 = createSubResult('Section 3', '/page1#section-3', [50], 'Content 3'); + const sub4 = createSubResult('Section 4', '/page1#section-4', [70], 'Content 4'); + + const weightedLocations = [ + createWordLocation(10, 5, 10), + createWordLocation(30, 5, 8), + createWordLocation(50, 5, 6), + createWordLocation(70, 5, 4), + ]; + + const pageResult = createPagefindResult( + '/page1', 'Page 1', 'Main', [sub1, sub2, sub3, sub4], weightedLocations); + + const formatted = formatPagefindResult(pageResult, 2); + + expect(formatted.length).toBeGreaterThanOrEqual(1); + const subResultCount = formatted.filter(f => f.isSubResult).length; + expect(subResultCount).toBeLessThanOrEqual(2); + }); + + it('should filter out sub-results with titles matching page title', () => { + const pageTitle = 'My Page'; + const subResult = createSubResult('My Page', '/page1#my-page', [50], 'Section content'); + const weightedLocations = [createWordLocation(50, 5, 10)]; + const pageResult = createPagefindResult( + '/page1', pageTitle, 'Main excerpt', [subResult], weightedLocations); + + const formatted = formatPagefindResult(pageResult); + + const subResults = formatted.filter(f => f.isSubResult); + expect(subResults).toHaveLength(0); + }); + + it('should mark last sub-result correctly', () => { + const sub1 = createSubResult('Section 1', '/page1#section-1', [10], 'Content 1'); + const sub2 = createSubResult('Section 2', '/page1#section-2', [30], 'Content 2'); + + const weightedLocations = [ + createWordLocation(10, 5, 10), + createWordLocation(30, 5, 8), + ]; + + const pageResult = createPagefindResult('/page1', 'Page 1', 'Main', [sub1, sub2], weightedLocations); + + const formatted = formatPagefindResult(pageResult); + + const subResults = formatted.filter(f => f.isSubResult); + expect(subResults[0].isLastSubResult).toBe(false); + expect(subResults[1].isLastSubResult).toBe(true); + }); + + it('should re-sort sub-results by document order', () => { + const weightedLocations = [ + createWordLocation(100, 5, 10), + createWordLocation(20, 5, 8), + createWordLocation(50, 5, 6), + ]; + + const sub1 = createSubResult('Section A', '/page1#section-a', [100], 'Content A', weightedLocations); + const sub2 = createSubResult('Section B', '/page1#section-b', [20], 'Content B', weightedLocations); + const sub3 = createSubResult('Section C', '/page1#section-c', [50], 'Content C', weightedLocations); + + const pageResult = createPagefindResult( + '/page1', 'Page 1', 'Main', [sub1, sub2, sub3], weightedLocations); + + const formatted = formatPagefindResult(pageResult, 10); + + const subResults = formatted.filter(f => f.isSubResult); + expect(subResults.length).toBeGreaterThanOrEqual(2); + const titles = subResults.map(s => s.meta.title); + expect(titles).toContain('Section B'); + expect(titles).toContain('Section C'); + }); + + it('should remove duplicate titles from sub-results', () => { + const sub1 = createSubResult('Getting Started', '/page1#getting-started', [20], 'Content 1'); + const sub2 = createSubResult('Getting Started', '/page1#getting-started-2', [50], 'Content 2'); + + const weightedLocations = [ + createWordLocation(20, 5, 10), + createWordLocation(50, 5, 8), + ]; + + const pageResult = createPagefindResult('/page1', 'Page 1', 'Main', [sub1, sub2], weightedLocations); + + const formatted = formatPagefindResult(pageResult); + + const titles = formatted.filter(f => f.isSubResult).map(f => f.meta.title); + const uniqueTitles = new Set(titles); + expect(titles.length).toBe(uniqueTitles.size); + }); + + it('should keep sub-result with most locations when multiple contain same weighted location', () => { + const sub1 = createSubResult('Section A', '/page1#section-a', [10, 20, 30], 'Multiple matches', [ + createWordLocation(10), + createWordLocation(20), + createWordLocation(30), + ]); + const sub2 = createSubResult('Section B', '/page1#section-b', [10, 15], 'Fewer matches', [ + createWordLocation(10), + createWordLocation(15), + ]); + + const weightedLocations = [createWordLocation(10, 5, 10)]; + + const pageResult = createPagefindResult('/page1', 'Page 1', 'Main', [sub1, sub2], weightedLocations); + + const formatted = formatPagefindResult(pageResult); + + const subResults = formatted.filter(f => f.isSubResult); + expect(subResults).toHaveLength(1); + expect(subResults[0].meta.title).toBe('Section A'); + }); + }); + + describe('edge cases', () => { + it('should handle empty weighted_locations gracefully', () => { + const subResult = createSubResult('Section', '/page1#section', [], 'Content'); + const pageResult = createPagefindResult('/page1', 'Page 1', 'Main excerpt', [subResult], []); + + const formatted = formatPagefindResult(pageResult); + + expect(formatted).toBeDefined(); + expect(formatted.length).toBeGreaterThanOrEqual(1); + }); + + it('should handle sub-results with single location', () => { + const subResult = createSubResult('Section', '/page1#section', [25], 'Content'); + const weightedLocations = [createWordLocation(25, 5, 10)]; + const pageResult = createPagefindResult('/page1', 'Page 1', 'Main', [subResult], weightedLocations); + + const formatted = formatPagefindResult(pageResult); + + expect(formatted).toHaveLength(2); + expect(formatted[1].isSubResult).toBe(true); + }); + + it('should handle sub-results with zero count', () => { + const subResult = createSubResult('Section', '/page1#section', [25], 'Content'); + const weightedLocations = [createWordLocation(25, 5, 10)]; + const pageResult = createPagefindResult('/page1', 'Page 1', 'Main', [subResult], weightedLocations); + + const formatted = formatPagefindResult(pageResult, 0); + + expect(formatted[0].isSubResult).toBe(false); + }); + }); +}); + +describe('truncateExcerptToShowMark (via formatPagefindResult)', () => { + it('should preserve mark tags in excerpt', () => { + const subResult = createSubResult( + 'Section', '/page1#section', [10], 'This is highlighted content'); + const weightedLocations = [createWordLocation(10, 5, 10)]; + const pageResult = createPagefindResult( + '/page1', 'Page 1', 'Main highlight', [subResult], weightedLocations); + + const formatted = formatPagefindResult(pageResult); + + expect(formatted[0].meta.description).toContain(''); + }); + + it('should handle excerpts without mark tags', () => { + const pageResult = createPagefindResult('/page1', 'Page 1', 'Plain text without marks'); + + const formatted = formatPagefindResult(pageResult); + + expect(formatted[0].meta.description).toBe('Plain text without marks'); + }); + + it('should handle mark at the start of excerpt', () => { + const subResult = createSubResult('Section', '/page1#section', [0], 'Start of content'); + const weightedLocations = [createWordLocation(0, 5, 10)]; + const pageResult = createPagefindResult( + '/page1', 'Page 1', 'Main content', [subResult], weightedLocations); + + const formatted = formatPagefindResult(pageResult); + + expect(formatted[0].meta.description).toContain('Main'); + }); +}); + +describe('mergeConsecutiveMarks (via formatPagefindResult)', () => { + it('should merge consecutive mark tags in excerpt', () => { + const subResult = createSubResult( + 'Section', '/page1#section', [10], 'making the search'); + const weightedLocations = [createWordLocation(10, 5, 10)]; + const pageResult = createPagefindResult( + '/page1', 'Page 1', 'Main content', [subResult], weightedLocations); + + const formatted = formatPagefindResult(pageResult); + + const subResultDesc = formatted[1]?.meta?.description ?? formatted[1]?.result?.excerpt ?? ''; + expect(subResultDesc).toContain('making the'); + }); + + it('should not merge non-adjacent mark tags', () => { + const subResult = createSubResult( + 'Section', '/page1#section', [10], 'first and second'); + const weightedLocations = [createWordLocation(10, 5, 10)]; + const pageResult = createPagefindResult('/page1', 'Page 1', 'Main', [subResult], weightedLocations); + + const formatted = formatPagefindResult(pageResult); + + const subResultDesc = formatted[1]?.meta?.description ?? formatted[1]?.result?.excerpt ?? ''; + expect(subResultDesc).toContain('first'); + expect(subResultDesc).toContain('second'); + }); + + it('should handle excerpts without any mark tags', () => { + const pageResult = createPagefindResult('/page1', 'Page 1', 'Plain text excerpt'); + + const formatted = formatPagefindResult(pageResult); + + expect(formatted[0].meta.description).toBe('Plain text excerpt'); + }); +}); diff --git a/packages/vue-components/src/__tests__/Search.spec.js b/packages/vue-components/src/__tests__/Search.spec.js index 806f0e3122..bde94678cd 100644 --- a/packages/vue-components/src/__tests__/Search.spec.js +++ b/packages/vue-components/src/__tests__/Search.spec.js @@ -1,172 +1,437 @@ 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; + let mockPagefind; + let mockSearchResults; + let originalLocation; + + const createMockResult = (overrides = {}) => ({ + url: '/test-page', + meta: { + title: 'Test Page', + description: 'Test description with match', + }, + isSubResult: false, + isLastSubResult: false, + ...overrides, + }); + + const createMockSearchResult = (results = []) => ({ + results: results.map(r => ({ + data: jest.fn().mockResolvedValue(r), + })), + }); beforeEach(() => { - mockContainer = { - querySelectorAll: jest.fn(() => []), - querySelector: jest.fn(constant(null)), - addEventListener: jest.fn(), - removeEventListener: jest.fn(), + jest.clearAllMocks(); + jest.useFakeTimers(); + + originalLocation = window.location; + delete window.location; + window.location = { href: '', assign: jest.fn() }; + + mockSearchResults = [ + createMockResult({ + url: '/page1', + meta: { title: 'Page 1', description: 'Content from page 1' }, + }), + createMockResult({ + url: '/page1#section', + meta: { title: 'Section', description: 'Content from section' }, + isSubResult: true, + isLastSubResult: true, + }), + ]; + + mockPagefind = { + search: jest.fn().mockResolvedValue(createMockSearchResult(mockSearchResults)), }; - mockPagefindUI = jest.fn(() => ({})); - window.PagefindUI = mockPagefindUI; + window.loadPagefind = jest.fn().mockResolvedValue(mockPagefind); 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; - }); + + document.elementFromPoint = jest.fn().mockReturnValue(null); }); afterEach(() => { + jest.useRealTimers(); jest.clearAllMocks(); if (wrapper) { wrapper.unmount(); wrapper = null; } document.body.innerHTML = ''; + delete window.loadPagefind; + window.location = originalLocation; }); - test('renders search button', async () => { - wrapper = mount(Search); - await nextTick(); + describe('Component Rendering', () => { + 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'); - }); + 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, + 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()).toContain('⌘'); }); - wrapper = mount(Search); - await nextTick(); + test('displays correct metaKey for Windows', async () => { + Object.defineProperty(navigator, 'platform', { + value: 'Win32', + configurable: true, + }); - expect(wrapper.find('.metaKey').text()).toBe('⌘ K'); - }); + wrapper = mount(Search); + await nextTick(); - test('displays correct metaKey for non-Mac', async () => { - Object.defineProperty(navigator, 'platform', { - value: 'Win32', - configurable: true, + expect(wrapper.find('.metaKey').text()).toContain('Ctrl'); }); - wrapper = mount(Search); - await nextTick(); + test('modal is hidden by default', async () => { + wrapper = mount(Search); + await nextTick(); - expect(wrapper.find('.metaKey').text()).toBe('Ctrl K'); + expect(wrapper.find('.search-dialog').exists()).toBe(false); + }); }); - test('opens modal on button click', async () => { - wrapper = mount(Search); - await nextTick(); + describe('Modal Open/Close', () => { + test('opens modal on button click', async () => { + wrapper = mount(Search); + await nextTick(); - await wrapper.find('.nav-search-btn-wait').trigger('click'); - await nextTick(); + await wrapper.find('.nav-search-btn-wait').trigger('click'); + await nextTick(); - expect(wrapper.find('.algolia').exists()).toBe(true); - }); + expect(wrapper.find('.search-dialog').exists()).toBe(true); + expect(wrapper.find('.search-modal').exists()).toBe(true); + }); - test('opens modal on Cmd+K keyboard shortcut', async () => { - wrapper = mount(Search); - await nextTick(); + 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(); + 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); - }); + expect(wrapper.find('.search-dialog').exists()).toBe(true); + }); + + test('opens modal on Ctrl+K keyboard shortcut', async () => { + wrapper = mount(Search); + await nextTick(); - 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(); - const event = new KeyboardEvent('keydown', { key: 'k', ctrlKey: true }); - window.addEventListener.mock.calls[0][1](event); - await nextTick(); + expect(wrapper.find('.search-dialog').exists()).toBe(true); + }); + + 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('.search-dialog').exists()).toBe(true); + + await wrapper.find('[command-dialog-mask]').trigger('click.self'); + await nextTick(); + + expect(wrapper.find('.search-dialog').exists()).toBe(false); + }); + + 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('.search-dialog').exists()).toBe(true); - 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('.search-dialog').exists()).toBe(false); + }); }); - test('closes modal on Escape key', async () => { - wrapper = mount(Search); - await nextTick(); + describe('Search Functionality', () => { + test('clears results on empty query', async () => { + wrapper = mount(Search); + await nextTick(); + + await wrapper.find('.nav-search-btn-wait').trigger('click'); + await nextTick(); + + const input = wrapper.find('input.search-input'); + await input.setValue(''); + await nextTick(); + + jest.advanceTimersByTime(200); + await nextTick(); + + expect(wrapper.find('.search-results').exists()).toBe(false); + }); + + test('handles search errors gracefully', async () => { + window.loadPagefind = jest.fn().mockRejectedValue(new Error('Pagefind failed')); + + wrapper = mount(Search); + await nextTick(); - await wrapper.find('.nav-search-btn-wait').trigger('click'); - await nextTick(); + await wrapper.find('.nav-search-btn-wait').trigger('click'); + await nextTick(); - expect(wrapper.find('.algolia').exists()).toBe(true); + const input = wrapper.find('input.search-input'); + await input.setValue('test'); + await nextTick(); - const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' }); - window.addEventListener.mock.calls[0][1](escapeEvent); - await nextTick(); + jest.advanceTimersByTime(200); + await nextTick(); + await nextTick(); - expect(wrapper.find('.algolia').exists()).toBe(false); + expect(wrapper.find('.search-empty').exists()).toBe(true); + }); + + test('debounces search input', async () => { + wrapper = mount(Search); + await nextTick(); + + await wrapper.find('.nav-search-btn-wait').trigger('click'); + await nextTick(); + + const input = wrapper.find('input.search-input'); + + await input.setValue('a'); + await nextTick(); + jest.advanceTimersByTime(50); + await nextTick(); + + await input.setValue('ab'); + await nextTick(); + jest.advanceTimersByTime(50); + await nextTick(); + + await input.setValue('abc'); + await nextTick(); + jest.advanceTimersByTime(50); + await nextTick(); + + expect(mockPagefind.search).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(100); + await nextTick(); + + expect(mockPagefind.search).toHaveBeenCalledTimes(1); + }); }); - test('closes modal on backdrop click', async () => { - wrapper = mount(Search, { attachTo: document.body }); - await nextTick(); + describe('Keyboard Navigation', () => { + beforeEach(async () => { + mockPagefind.search = jest.fn().mockResolvedValue({ + results: [ + { + data: jest.fn().mockResolvedValue({ + url: '/page1', + meta: { title: 'Result 1', description: 'Description 1' }, + sub_results: [ + { + title: 'Section 1', + url: '/page1#section1', + locations: [10], + weighted_locations: [{ location: 10, weight: 1, balanced_score: 1 }], + excerpt: 'Section content', + }, + { + title: 'Section 2', + url: '/page1#section2', + locations: [50], + weighted_locations: [{ location: 50, weight: 1, balanced_score: 1 }], + excerpt: 'Another section', + }, + ], + weighted_locations: [ + { location: 10, weight: 1, balanced_score: 1 }, + { location: 50, weight: 1, balanced_score: 1 }, + ], + anchors: [], + excerpt: 'Main content', + }), + }, + ], + }); + + wrapper = mount(Search); + await nextTick(); + + await wrapper.find('.nav-search-btn-wait').trigger('click'); + await nextTick(); + + const input = wrapper.find('input.search-input'); + await input.setValue('test'); + await nextTick(); + + jest.advanceTimersByTime(200); + await nextTick(); + await nextTick(); + }); - await wrapper.find('.nav-search-btn-wait').trigger('click'); - await nextTick(); + test('ArrowDown navigates to next result', async () => { + const downEvent = new KeyboardEvent('keydown', { key: 'ArrowDown' }); + wrapper.find('input.search-input').element.dispatchEvent(downEvent); + await nextTick(); - expect(wrapper.find('.algolia').exists()).toBe(true); - expect(wrapper.find('[command-dialog-mask]').exists()).toBe(true); + const items = wrapper.findAll('.search-result-item'); + expect(items[0].classes()).toContain('active'); + }); - await wrapper.find('[command-dialog-mask]').trigger('click.self'); - await nextTick(); + test('ArrowDown at last item does not crash', async () => { + const items = wrapper.findAll('.search-result-item'); + const lastIndex = items.length - 1; + + for (let i = 0; i <= lastIndex + 1; i += 1) { + const downEvent = new KeyboardEvent('keydown', { key: 'ArrowDown' }); + wrapper.find('input.search-input').element.dispatchEvent(downEvent); + // eslint-disable-next-line no-await-in-loop + await nextTick(); + } - expect(wrapper.find('.algolia').exists()).toBe(false); + expect(wrapper.find('.search-result-item').exists()).toBe(true); + }); + + test('ArrowUp at first item does not crash', async () => { + const upEvent = new KeyboardEvent('keydown', { key: 'ArrowUp' }); + wrapper.find('input.search-input').element.dispatchEvent(upEvent); + await nextTick(); + + expect(wrapper.find('.search-result-item').exists()).toBe(true); + }); }); - test('initializes PagefindUI when modal opens', async () => { - wrapper = mount(Search); - await nextTick(); + describe('Result Interaction', () => { + beforeEach(async () => { + mockPagefind.search = jest.fn().mockResolvedValue({ + results: [ + { + data: jest.fn().mockResolvedValue({ + url: '/page1', + meta: { title: 'Main Page', description: 'Main description' }, + sub_results: [ + { + title: 'Section A', + url: '/page1#section-a', + locations: [10], + weighted_locations: [{ location: 10, weight: 1, balanced_score: 1 }], + excerpt: 'Section A content', + }, + ], + weighted_locations: [{ location: 10, weight: 1, balanced_score: 1 }], + anchors: [], + excerpt: 'Main content', + }), + }, + ], + }); + + wrapper = mount(Search); + await nextTick(); + + await wrapper.find('.nav-search-btn-wait').trigger('click'); + await nextTick(); + + const input = wrapper.find('input.search-input'); + await input.setValue('test'); + await nextTick(); + + jest.advanceTimersByTime(200); + await nextTick(); + await nextTick(); + }); + + test('click on result navigates to URL', async () => { + await wrapper.find('.search-result-item').trigger('click'); + await nextTick(); + + expect(window.location.href).toBe('/page1'); + }); + + test('mouse hover highlights result', async () => { + await wrapper.findAll('.search-result-item')[0].trigger('mouseenter'); + await nextTick(); - await wrapper.find('.nav-search-btn-wait').trigger('click'); - await nextTick(); - await nextTick(); + expect(wrapper.find('.search-result-item').classes()).toContain('active'); + }); + + test('renders main result with file icon', async () => { + const mainResult = wrapper.findAll('.search-result-item')[0]; + expect(mainResult.find('.DocSearch-Hit-icon').exists()).toBe(true); + }); + + test('renders sub-result with tree icon', async () => { + const subResult = wrapper.findAll('.search-result-item')[1]; + expect(subResult.find('.DocSearch-Hit-Tree').exists()).toBe(true); + }); - expect(mockPagefindUI).toHaveBeenCalled(); + test('result displays title and description', async () => { + const resultItem = wrapper.find('.search-result-item'); + expect(resultItem.find('.result-title').exists()).toBe(true); + expect(resultItem.find('.result-excerpt').exists()).toBe(true); + }); }); - test('adds keydown event listener on mount', async () => { - wrapper = mount(Search); - await nextTick(); + describe('State Management', () => { + test('modal close clears results', async () => { + wrapper = mount(Search); + await nextTick(); + + await wrapper.find('.nav-search-btn-wait').trigger('click'); + await nextTick(); + + const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' }); + window.addEventListener.mock.calls[0][1](escapeEvent); + await nextTick(); - expect(window.addEventListener).toHaveBeenCalledWith('keydown', expect.any(Function)); + expect(wrapper.find('.search-dialog').exists()).toBe(false); + }); }); - test('removes keydown event listener on unmount', async () => { - wrapper = mount(Search); - await nextTick(); + describe('Cleanup', () => { + test('adds keydown event listener on mount', async () => { + wrapper = mount(Search); + await nextTick(); - wrapper.unmount(); + expect(window.addEventListener).toHaveBeenCalledWith('keydown', expect.any(Function)); + }); - expect(window.removeEventListener).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/pagefindSearchBar/Search.vue b/packages/vue-components/src/pagefindSearchBar/Search.vue index b7bbf63c70..a5591d44d5 100644 --- a/packages/vue-components/src/pagefindSearchBar/Search.vue +++ b/packages/vue-components/src/pagefindSearchBar/Search.vue @@ -1,184 +1,148 @@ diff --git a/packages/vue-components/src/pagefindSearchBar/assets/search.css b/packages/vue-components/src/pagefindSearchBar/assets/search.css index ecab74658b..3fb6028953 100644 --- a/packages/vue-components/src/pagefindSearchBar/assets/search.css +++ b/packages/vue-components/src/pagefindSearchBar/assets/search.css @@ -1,13 +1,22 @@ +/* ============================================ + CSS Variables - Color Palette + ============================================ */ :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%); + --bg: var(--gray1); + --command-shadow: 0 2px 5px rgb(0 0 0 / 10%); + --command-list-height: auto; --lowContrast: #ffffff; --highContrast: #000000; - --vcp-c-brand: var(--vp-c-brand-2); - --vcp-c-accent: #35495e; + --mb-brand-primary: #00b0f0; + --mb-search-brand: var(--doc-search-blue); + --mb-search-accent: #35495e; + --mb-search-brand-1: var(--blue10); + --docsearch-muted-color: var(--gray10); + --doc-search-blue: #5468ff; --gray1: hsl(0, 0%, 98%); --gray2: hsl(0, 0%, 97.3%); --gray3: hsl(0, 0%, 95.1%); @@ -37,7 +46,7 @@ --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%); + --blue6: hsl(209, 82%, 85%); --blue7: hsl(208, 77.5%, 76.9%); --blue8: hsl(206, 81.9%, 65.3%); --blue9: hsl(206, 100%, 50%); @@ -45,6 +54,8 @@ --blue11: hsl(211, 100%, 43.2%); --blue12: hsl(211, 100%, 15%); } + +/* Dark mode color palette */ .dark { --app-bg: var(--gray1); --app-text: #ffffff; @@ -88,6 +99,9 @@ --blue12: hsl(206, 98%, 95.8%); } +/* ============================================ + Dialog Layout + ============================================ */ div [command-dialog-mask] { background-color: rgba(0, 0, 0, 0.3); height: 100vh; @@ -97,16 +111,18 @@ div [command-dialog-mask] { width: 100vw; z-index: 200; } + div [command-dialog-wrapper] { position: relative; - background: var(--gray2); + background: #f5f6f7; + /* 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); + max-width: 700px; + /* box-shadow: var(--command-shadow); */ } + div [command-dialog-footer] { border-top: 1px solid var(--gray6); align-items: center; @@ -126,7 +142,11 @@ div [command-dialog-footer] { z-index: 300; font-size: 12px; } -.algolia [command-input] { + +/* ============================================ + Search Input + ============================================ */ +.search-dialog .search-input { font-family: var(--font-sans); width: 100%; font-size: 18px; @@ -134,43 +154,52 @@ div [command-dialog-footer] { outline: none; background: var(--bg); color: var(--gray12); - caret-color: var(--vcp-c-brand); + caret-color: var(--mb-search-brand); margin: 0; } -.algolia [command-input]::placeholder { + +.search-dialog .search-input::placeholder { color: var(--gray9); } -.algolia [command-list] { + +/* ============================================ + Search Results Container + ============================================ */ +.search-dialog .search-results-container { + padding: 0 12px 12px; +} + +.search-dialog .search-results { height: var(--command-list-height); - max-height: 360px; + max-height: 500px; 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; + +.search-dialog .search-empty { + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + height: 64px; + white-space: pre-wrap; + color: var(--gray11); } -.algolia [command-item] { + +/* ============================================ + Search Result Item - Container + ============================================ */ +.search-dialog .search-result-item { position: relative; content-visibility: auto; cursor: pointer; - height: 56px; + height: 66px; font-size: 14px; display: flex; align-items: center; - gap: 12px; + gap: 5px; padding: 0px 16px; color: var(--gray12); user-select: none; @@ -180,59 +209,203 @@ div [command-dialog-footer] { border-radius: 4px; margin-top: 4px; background-color: var(--lowContrast); + box-shadow: var(--command-shadow); } -.algolia [command-item]:first-child { + +.search-dialog .search-result-item:first-child { margin-top: 0px; } -.algolia [command-item][aria-selected="true"], -.algolia [command-item]:hover { - background: var(--vcp-c-brand); + +.search-dialog .search-result-item.active, +.search-dialog .search-result-item:hover { + background: var(--mb-search-brand); color: #fff; } -.algolia [command-item][aria-selected="true"] svg, -.algolia [command-item]:hover svg { - color: #fff; + +.search-dialog .search-result-item:active { + transition-property: background; + background: var(--gray4); } -.algolia [command-item][aria-selected="true"] [command-linear-shortcuts], -.algolia [command-item]:hover [command-linear-shortcuts] { + +.search-dialog .detail-list .search-result-item { + min-height: 65px; + max-height: 112px; + padding: 10px 16px; + height: auto; + margin-bottom: 5px; +} + +/* Result item - active/hover states */ +.search-dialog .search-result-item.active [command-linear-shortcuts], +.search-dialog .search-result-item:hover [command-linear-shortcuts] { display: flex; margin-left: auto; gap: 8px; + height: 70px; } -.algolia [command-item][aria-selected="true"] [command-linear-shortcuts] kbd, -.algolia [command-item]:hover [command-linear-shortcuts] kbd { + +.search-dialog .search-result-item.active [command-linear-shortcuts] kbd, +.search-dialog .search-result-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); + +/* ============================================ + Result Icons + ============================================ */ +.search-result-item .result-icon { + display: flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + flex-shrink: 0; + margin-right: 0; + color: var(--gray10); } -.algolia [command-item] svg { + +.search-result-item .result-icon-sub { + display: flex; + align-items: center; + justify-content: center; + width: 15px; + height: 15px; + flex-shrink: 0; + margin-right: 0; + color: var(--gray10); +} + +/* Main result icon */ +.search-result-item .DocSearch-Hit-icon { + width: 30px !important; + height: 30px !important; +} + +/* Sub result tree icon */ +.search-result-item .DocSearch-Hit-Tree { + width: 30px !important; + height: 30px !important; + opacity: 0.5; + stroke-width: 1.5; +} + +/* Sub result hash icon */ +.search-result-item .DocSearch-Hit-Hash { + width: 30px !important; + height: 30px !important; +} + +/* SVG sizing override */ +.search-dialog .search-result-item svg { width: 16px; height: 16px; color: var(--gray10); } -.algolia [command-empty=""] { - font-size: 14px; + +/* Active state icons */ +.search-result-item.active .result-icon, +.search-result-item:hover .result-icon { + color: #fff; +} + +.search-dialog .search-result-item.active svg, +.search-dialog .search-result-item:hover svg { + color: #fff; +} + +/* Spacing between two icons for sub-results */ +.search-result-item > .result-icon + .result-icon { + margin-left: 0; +} + +/* ============================================ + Result Content + ============================================ */ +.search-result-item .link { display: flex; - align-items: center; - justify-content: center; - height: 64px; - white-space: pre-wrap; - color: var(--gray11); + flex-direction: column; + width: 100%; + overflow: hidden; + margin-left: 12px; } -.algolia [command-dialog-mask] { - background-color: rgba(75, 75, 75, 0.8); + +.search-dialog .search-result-item > div.link { + width: 100%; } -.algolia [command-dialog-header] { - padding: 12px; + +.search-dialog .search-result-item .result-title { + display: flex; + justify-content: space-between; + font-size: 16px; + font-weight: 600; + white-space: nowrap; } -.algolia [command-dialog-body] { - padding: 0 12px 12px; + +.search-dialog .search-result-item .result-title i.prefix { + color: var(--mb-search-brand-1); +} + +.search-dialog .search-result-item:hover .result-title i.prefix, +.search-dialog .search-result-item.active .result-title i.prefix { + color: #fff; } -.algolia [command-dialog-footer] { + +.search-dialog .detail-list .search-result-item .result-excerpt { + word-break: break-all; + white-space: wrap; + margin-top: 6px; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; +} + +.search-dialog .search-result-item .result-excerpt { + font-size: 13px; + font-weight: 400; + text-overflow: ellipsis; + overflow: hidden; + word-break: keep-all; + white-space: nowrap; +} + +.search-dialog .search-result-item .headings { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; +} + +.search-dialog .search-result-item .date { + min-width: 86px; + text-align: right; +} + +.search-dialog .search-result-item mark { + background: none; + color: var(--blue10); +} + +.search-dialog .search-result-item.active mark, +.search-dialog .search-result-item:hover mark { + color: inherit; + background: rgba(255, 255, 255, 0.3); + text-decoration: none; + font-size: 15px; +} + +/* ============================================ + Search Dialog Overrides + ============================================ */ +.search-dialog [command-dialog-mask] { + background-color: rgba(101, 108, 133, 0.8); +} + +.search-dialog [command-dialog-header] { + padding: 12px; +} + +.search-dialog [command-dialog-footer] { align-items: center; border-radius: 0 0 8px 8px; box-shadow: @@ -249,8 +422,9 @@ div [command-dialog-footer] { width: 100%; z-index: 300; } -.algolia [command-group-heading] { - color: var(--vcp-c-brand); + +.search-dialog [command-group-heading] { + color: var(--mb-search-brand); font-size: 0.85em; font-weight: 600; line-height: 32px; @@ -261,49 +435,38 @@ div [command-dialog-footer] { width: 100%; } -.algolia .command-palette-commands { +/* ============================================ + Command Palette Footer + ============================================ */ +.search-dialog .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 { +.search-dialog .command-palette-commands li { display: flex; align-items: center; } -.algolia .command-palette-commands li:not(:last-of-type) { + +.search-dialog .command-palette-commands li:not(:last-of-type) { margin-right: 0.8em; } -.algolia .command-palette-logo a { + +.search-dialog .command-palette-logo a { display: flex; align-items: center; gap: 8px; } -.algolia .command-palette-logo svg { + +.search-dialog .command-palette-logo svg { height: 24px; width: 24px; } -.algolia .command-palette-commands-key { + +.search-dialog .command-palette-commands-key { align-items: center; background: var(--gray3); border-radius: 2px; @@ -316,54 +479,57 @@ div [command-dialog-footer] { border: 0; width: 20px; } -.dark .algolia [command-dialog-footer] { - box-shadow: none; -} + +/* ============================================ + Utilities + ============================================ */ 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; +.dark .search-dialog [command-dialog-footer] { + box-shadow: none; } -.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; -} +/* ============================================ + Media Queries + ============================================ */ +@media screen and (max-width: 768px) { + div [command-dialog-wrapper] { + margin: 0; + height: 100vh; + max-width: 100vw; + border-radius: 0; + } -.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 .search-results { + max-height: calc(100vh - 150px); + } } -.search-dialog div[command-item][aria-selected="true"] mark, -.search-dialog div[command-item]:hover mark { - color: inherit; - text-decoration: underline; + +@media screen and (max-width: 560px) { + .search-dialog .command-palette-commands { + display: none; + } + + div [command-dialog-wrapper] { + margin: 0; + height: 100vh; + } + + .search-dialog [command-dialog-footer] { + justify-content: center; + } + + .search-dialog .search-input { + padding: 6px 4px; + } + + .search-dialog .search-results { + max-height: calc(100vh - 120px); + } }