From 3d8dd75471763002569ad039b7e82a60dc3e0b63 Mon Sep 17 00:00:00 2001 From: Thaddaeus Chua Date: Wed, 4 Mar 2026 16:32:09 +0800 Subject: [PATCH 01/22] Copy gerteck's pagefind implementation --- docs/userGuide/makingTheSiteSearchable.md | 29 ++++++ package-lock.json | 96 +++++++++++++++++++ packages/core/package.json | 1 + packages/core/src/Page/PageConfig.ts | 7 +- packages/core/src/Page/page.njk | 7 ++ packages/core/src/Page/pagefindScript.ts | 19 ++++ .../core/src/Site/SiteGenerationManager.ts | 37 ++++++- packages/core/src/Site/SitePagesManager.ts | 10 ++ 8 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 packages/core/src/Page/pagefindScript.ts diff --git a/docs/userGuide/makingTheSiteSearchable.md b/docs/userGuide/makingTheSiteSearchable.md index 4e28680c49..e5ef64f9fb 100644 --- a/docs/userGuide/makingTheSiteSearchable.md +++ b/docs/userGuide/makingTheSiteSearchable.md @@ -37,6 +37,35 @@ You can add a search bar component to your website to allow users to search the + + +## Using Pagefind (Beta) + +MarkBind now supports [Pagefind](https://pagefind.app/), a static low-bandwidth search library, as a built-in feature. This provides full-text search capabilities without external services. + + +This is a beta feature and will be refined in future updates. To use it, you must have enableSearch: true in your site.json (this is the default). + + + +The Pagefind index is currently only generated during a full site build (e.g., markbind build). It will not repeatedly update during live reload (markbind serve) when you modify pages. You must restart the server (re-run markbind serve) or rebuild to refresh the search index. + + +To add the Pagefind search bar to your page, simply insert the following `div` where you want it to appear: + +```html +
+``` + +MarkBind will automatically inject the necessary scripts and styles to render the search UI. + +The following UI will be rendered, which is provided by Pagefind: + +
+ + +
+ ## Using External Search Services MarkBind sites can use Algolia Doc Search services easily via the Algolia plugin. Unlike the built-in search, Algolia provides full-text search. See the panel below for more info. diff --git a/package-lock.json b/package-lock.json index 7e016c8985..909f4c26a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4646,6 +4646,84 @@ "dev": true, "license": "MIT" }, + "node_modules/@pagefind/darwin-arm64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.4.0.tgz", + "integrity": "sha512-2vMqkbv3lbx1Awea90gTaBsvpzgRs7MuSgKDxW0m9oV1GPZCZbZBJg/qL83GIUEN2BFlY46dtUZi54pwH+/pTQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.4.0.tgz", + "integrity": "sha512-e7JPIS6L9/cJfow+/IAqknsGqEPjJnVXGjpGm25bnq+NPdoD3c/7fAwr1OXkG4Ocjx6ZGSCijXEV4ryMcH2E3A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/freebsd-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.4.0.tgz", + "integrity": "sha512-WcJVypXSZ+9HpiqZjFXMUobfFfZZ6NzIYtkhQ9eOhZrQpeY5uQFqNWLCk7w9RkMUwBv1HAMDW3YJQl/8OqsV0Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@pagefind/linux-arm64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.4.0.tgz", + "integrity": "sha512-PIt8dkqt4W06KGmQjONw7EZbhDF+uXI7i0XtRLN1vjCUxM9vGPdtJc2mUyVPevjomrGz5M86M8bqTr6cgDp1Uw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/linux-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.4.0.tgz", + "integrity": "sha512-z4oddcWwQ0UHrTHR8psLnVlz6USGJ/eOlDPTDYZ4cI8TK8PgwRUPQZp9D2iJPNIPcS6Qx/E4TebjuGJOyK8Mmg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.4.0.tgz", + "integrity": "sha512-NkT+YAdgS2FPCn8mIA9bQhiBs+xmniMGq1LFPDhcFn0+2yIUEiIG06t7bsZlhdjknEQRTSdT7YitP6fC5qwP0g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "dev": true, @@ -16563,6 +16641,23 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/pagefind": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.4.0.tgz", + "integrity": "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g==", + "license": "MIT", + "bin": { + "pagefind": "lib/runner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.4.0", + "@pagefind/darwin-x64": "1.4.0", + "@pagefind/freebsd-x64": "1.4.0", + "@pagefind/linux-arm64": "1.4.0", + "@pagefind/linux-x64": "1.4.0", + "@pagefind/windows-x64": "1.4.0" + } + }, "node_modules/parent-module": { "version": "1.0.1", "dev": true, @@ -22044,6 +22139,7 @@ "material-icons": "^1.9.1", "moment": "^2.29.4", "nunjucks": "3.2.4", + "pagefind": "^1.4.0", "path-is-inside": "^1.0.2", "simple-git": "^3.22.0", "url-parse": "^1.5.10", diff --git a/packages/core/package.json b/packages/core/package.json index 9f845e5c66..807cbc0232 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -89,6 +89,7 @@ "material-icons": "^1.9.1", "moment": "^2.29.4", "nunjucks": "3.2.4", + "pagefind": "^1.4.0", "path-is-inside": "^1.0.2", "simple-git": "^3.22.0", "url-parse": "^1.5.10", diff --git a/packages/core/src/Page/PageConfig.ts b/packages/core/src/Page/PageConfig.ts index 6d37f73134..e93b4040a9 100644 --- a/packages/core/src/Page/PageConfig.ts +++ b/packages/core/src/Page/PageConfig.ts @@ -23,8 +23,11 @@ export interface PageAssets { vue: string; pageVueRenderJs?: string; layoutUserScriptsAndStyles: string[]; - pluginScripts?: string[], - pluginLinks?: string[], + pluginScripts?: string[]; + pluginLinks?: string[]; + pagefindCss?: string; + pagefindJs?: string; + pagefindScript?: string; } /** diff --git a/packages/core/src/Page/page.njk b/packages/core/src/Page/page.njk index 04c2e12e5a..e522c4aa88 100644 --- a/packages/core/src/Page/page.njk +++ b/packages/core/src/Page/page.njk @@ -19,6 +19,7 @@ {% if asset.bootstrapIcons %} {%- endif -%} {%- if not dev -%}{%- endif -%} + {% if asset.pagefindCss %} {%- endif -%} {%- if asset.pluginLinks -%} {%- for link in asset.pluginLinks -%} {{ link }} @@ -57,6 +58,12 @@ {{ script }} {%- endfor %} {%- endif %} +{%- if asset.pagefindJs %} + +{%- endif %} +{%- if asset.pagefindScript %} + {{ asset.pagefindScript }} +{%- endif %} {%- if asset.scriptBottom %} {%- for scripts in asset.scriptBottom %} {{ scripts }} diff --git a/packages/core/src/Page/pagefindScript.ts b/packages/core/src/Page/pagefindScript.ts new file mode 100644 index 0000000000..0c47a52f16 --- /dev/null +++ b/packages/core/src/Page/pagefindScript.ts @@ -0,0 +1,19 @@ +const PAGEFIND_INPUT_SELECTOR = '#pagefind-search-input'; + +// See https://pagefind.app/docs/ui-usage/ +export const getPagefindScript = (): string => ` + + `; diff --git a/packages/core/src/Site/SiteGenerationManager.ts b/packages/core/src/Site/SiteGenerationManager.ts index 0c180f6a40..0f202a21a1 100644 --- a/packages/core/src/Site/SiteGenerationManager.ts +++ b/packages/core/src/Site/SiteGenerationManager.ts @@ -17,7 +17,7 @@ import * as fsUtil from '../utils/fsUtil'; import * as logger from '../utils/logger'; import { SITE_CONFIG_NAME, LAZY_LOADING_SITE_FILE_NAME, _, - TEMP_FOLDER_NAME, SITE_DATA_NAME, USER_VARIABLES_PATH, + TEMP_FOLDER_NAME, SITE_DATA_NAME, USER_VARIABLES_PATH, TEMPLATE_SITE_ASSET_FOLDER_NAME, } from './constants'; import { LayoutManager } from '../Layout'; import { LayoutConfig } from '../Layout/Layout'; @@ -311,6 +311,9 @@ export class SiteGenerationManager { await this.siteAssets.copyOcticonsAsset(); await this.siteAssets.copyMaterialIconsAsset(); await this.writeSiteData(); + if (this.siteConfig.enableSearch) { + await this.indexSiteWithPagefind(); + } this.calculateBuildTimeForGenerate(startTime, lazyWebsiteGenerationString); if (this.backgroundBuildMode) { this.backgroundBuildNotViewedFiles(); @@ -862,6 +865,38 @@ export class SiteGenerationManager { 1000, ); + /** + * Indexes all the pages of the site using pagefind. + */ + async indexSiteWithPagefind() { + const startTime = new Date(); + logger.info('Creating Pagefind Search Index...'); + // TODO: Update this dynamic import to a static import when migrating to ESM + // eslint-disable-next-line no-eval + const { createIndex, close } = await (eval('import("pagefind")') as Promise); + const { index } = await createIndex({ + keepIndexUrl: true, + verbose: true, + logfile: 'debug.log', + }); + if (index) { + const { errors, page_count } = await index.addDirectory({ path: this.outputPath }); + errors.forEach(error => logger.error(error)); + + const endTime = new Date(); + const totalTime = (endTime.getTime() - startTime.getTime()) / 1000; + logger.info(`Pagefind indexed ${page_count} pages in ${totalTime}s`); + + const pagefindOutputPath = path.join(this.outputPath, TEMPLATE_SITE_ASSET_FOLDER_NAME, 'pagefind'); + await fs.ensureDir(pagefindOutputPath); + await index.writeFiles({ outputPath: pagefindOutputPath }); + logger.info(`Pagefind assets written to ${pagefindOutputPath}`); + } else { + logger.error('Pagefind failed to create index'); + } + await close(); + } + async reloadSiteConfig() { if (this.backgroundBuildMode) { this.stopOngoingBuilds(); diff --git a/packages/core/src/Site/SitePagesManager.ts b/packages/core/src/Site/SitePagesManager.ts index b3de3851bc..889a2b816b 100644 --- a/packages/core/src/Site/SitePagesManager.ts +++ b/packages/core/src/Site/SitePagesManager.ts @@ -5,6 +5,7 @@ import { Template as NunjucksTemplate } from 'nunjucks'; import { Page } from '../Page'; import { PageConfig } from '../Page/PageConfig'; +import { getPagefindScript } from '../Page/pagefindScript'; import { VariableProcessor } from '../variables/VariableProcessor'; import { VariableRenderer } from '../variables/VariableRenderer'; import { ExternalManager } from '../External/ExternalManager'; @@ -141,6 +142,15 @@ 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 + ? path.posix.join(baseAssetsPath || '/', 'pagefind', 'pagefind-ui.css') + : undefined, + pagefindJs: this.siteConfig.enableSearch + ? path.posix.join(baseAssetsPath || '/', 'pagefind', 'pagefind-ui.js') + : undefined, + pagefindScript: this.siteConfig.enableSearch + ? getPagefindScript() + : undefined, }, baseUrlMap: this.baseUrlMap, dev: this.isDevMode, From 86a801ee4df5ef8c78253fc35e32cdf27d484697 Mon Sep 17 00:00:00 2001 From: Thaddaeus Chua Date: Wed, 4 Mar 2026 22:56:42 +0800 Subject: [PATCH 02/22] Add initial agolia styling & remove redundant code --- docs/userGuide/makingTheSiteSearchable.md | 6 +- packages/core/src/Page/PageConfig.ts | 1 - packages/core/src/Page/page.njk | 3 - packages/core/src/Page/pagefindScript.ts | 19 - packages/core/src/Site/SitePagesManager.ts | 4 - packages/vue-components/src/index.js | 2 + .../src/pagefindSearchBar/LogoPagefind.vue | 101 +++++ .../src/pagefindSearchBar/Search.vue | 170 ++++++++ .../src/pagefindSearchBar/assets/search.css | 368 ++++++++++++++++++ 9 files changed, 644 insertions(+), 30 deletions(-) delete mode 100644 packages/core/src/Page/pagefindScript.ts create mode 100644 packages/vue-components/src/pagefindSearchBar/LogoPagefind.vue create mode 100644 packages/vue-components/src/pagefindSearchBar/Search.vue create mode 100644 packages/vue-components/src/pagefindSearchBar/assets/search.css diff --git a/docs/userGuide/makingTheSiteSearchable.md b/docs/userGuide/makingTheSiteSearchable.md index e5ef64f9fb..d1a1ccf4df 100644 --- a/docs/userGuide/makingTheSiteSearchable.md +++ b/docs/userGuide/makingTheSiteSearchable.md @@ -53,15 +53,15 @@ The Pagefind index is currently only generated during a full site build (e.g., < To add the Pagefind search bar to your page, simply insert the following `div` where you want it to appear: -```html -
+```md + ``` MarkBind will automatically inject the necessary scripts and styles to render the search UI. The following UI will be rendered, which is provided by Pagefind: -
+
diff --git a/packages/core/src/Page/PageConfig.ts b/packages/core/src/Page/PageConfig.ts index e93b4040a9..58ffe2c621 100644 --- a/packages/core/src/Page/PageConfig.ts +++ b/packages/core/src/Page/PageConfig.ts @@ -27,7 +27,6 @@ export interface PageAssets { pluginLinks?: string[]; pagefindCss?: string; pagefindJs?: string; - pagefindScript?: string; } /** diff --git a/packages/core/src/Page/page.njk b/packages/core/src/Page/page.njk index e522c4aa88..be7d946441 100644 --- a/packages/core/src/Page/page.njk +++ b/packages/core/src/Page/page.njk @@ -61,9 +61,6 @@ {%- if asset.pagefindJs %} {%- endif %} -{%- if asset.pagefindScript %} - {{ asset.pagefindScript }} -{%- endif %} {%- if asset.scriptBottom %} {%- for scripts in asset.scriptBottom %} {{ scripts }} diff --git a/packages/core/src/Page/pagefindScript.ts b/packages/core/src/Page/pagefindScript.ts deleted file mode 100644 index 0c47a52f16..0000000000 --- a/packages/core/src/Page/pagefindScript.ts +++ /dev/null @@ -1,19 +0,0 @@ -const PAGEFIND_INPUT_SELECTOR = '#pagefind-search-input'; - -// See https://pagefind.app/docs/ui-usage/ -export const getPagefindScript = (): string => ` - - `; diff --git a/packages/core/src/Site/SitePagesManager.ts b/packages/core/src/Site/SitePagesManager.ts index 889a2b816b..1eea8210f3 100644 --- a/packages/core/src/Site/SitePagesManager.ts +++ b/packages/core/src/Site/SitePagesManager.ts @@ -5,7 +5,6 @@ import { Template as NunjucksTemplate } from 'nunjucks'; import { Page } from '../Page'; import { PageConfig } from '../Page/PageConfig'; -import { getPagefindScript } from '../Page/pagefindScript'; import { VariableProcessor } from '../variables/VariableProcessor'; import { VariableRenderer } from '../variables/VariableRenderer'; import { ExternalManager } from '../External/ExternalManager'; @@ -148,9 +147,6 @@ export class SitePagesManager { pagefindJs: this.siteConfig.enableSearch ? path.posix.join(baseAssetsPath || '/', 'pagefind', 'pagefind-ui.js') : undefined, - pagefindScript: this.siteConfig.enableSearch - ? getPagefindScript() - : undefined, }, baseUrlMap: this.baseUrlMap, dev: this.isDevMode, diff --git a/packages/vue-components/src/index.js b/packages/vue-components/src/index.js index 2de3bd9d94..ee8bb02528 100644 --- a/packages/vue-components/src/index.js +++ b/packages/vue-components/src/index.js @@ -37,6 +37,7 @@ import modal from './Modal.vue'; import scrollTopButton from './ScrollTopButton.vue'; import cardstack from './cardstack/CardStack.vue'; import card from './cardstack/Card.vue'; +import Search from './pagefindSearchBar/Search.vue'; const components = { box, @@ -71,6 +72,7 @@ const components = { 'VPopover': Dropdown, 'VTooltip': Tooltip, scrollTopButton, + Search, }; const directives = { diff --git a/packages/vue-components/src/pagefindSearchBar/LogoPagefind.vue b/packages/vue-components/src/pagefindSearchBar/LogoPagefind.vue new file mode 100644 index 0000000000..557f348d5b --- /dev/null +++ b/packages/vue-components/src/pagefindSearchBar/LogoPagefind.vue @@ -0,0 +1,101 @@ + diff --git a/packages/vue-components/src/pagefindSearchBar/Search.vue b/packages/vue-components/src/pagefindSearchBar/Search.vue new file mode 100644 index 0000000000..0e1a464016 --- /dev/null +++ b/packages/vue-components/src/pagefindSearchBar/Search.vue @@ -0,0 +1,170 @@ + + + + + + + diff --git a/packages/vue-components/src/pagefindSearchBar/assets/search.css b/packages/vue-components/src/pagefindSearchBar/assets/search.css new file mode 100644 index 0000000000..ffbd7d76be --- /dev/null +++ b/packages/vue-components/src/pagefindSearchBar/assets/search.css @@ -0,0 +1,368 @@ +:root { + --font-sans: "Inter", --apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, + Droid Sans, Helvetica Neue, sans-serif; + --app-bg: var(--gray1); + --app-text: #000000; + --command-shadow: 0 16px 70px rgb(0 0 0 / 20%); + --lowContrast: #ffffff; + --highContrast: #000000; + --vcp-c-brand: var(--vp-c-brand-2); + --vcp-c-accent: #35495e; + --gray1: hsl(0, 0%, 98%); + --gray2: hsl(0, 0%, 97.3%); + --gray3: hsl(0, 0%, 95.1%); + --gray4: hsl(0, 0%, 93%); + --gray5: hsl(0, 0%, 90.9%); + --gray6: hsl(0, 0%, 88.7%); + --gray7: hsl(0, 0%, 85.8%); + --gray8: hsl(0, 0%, 78%); + --gray9: hsl(0, 0%, 56.1%); + --gray10: hsl(0, 0%, 52.3%); + --gray11: hsl(0, 0%, 43.5%); + --gray12: hsl(0, 0%, 9%); + --grayA1: hsla(0, 0%, 0%, 0.012); + --grayA2: hsla(0, 0%, 0%, 0.027); + --grayA3: hsla(0, 0%, 0%, 0.047); + --grayA4: hsla(0, 0%, 0%, 0.071); + --grayA5: hsla(0, 0%, 0%, 0.09); + --grayA6: hsla(0, 0%, 0%, 0.114); + --grayA7: hsla(0, 0%, 0%, 0.141); + --grayA8: hsla(0, 0%, 0%, 0.22); + --grayA9: hsla(0, 0%, 0%, 0.439); + --grayA10: hsla(0, 0%, 0%, 0.478); + --grayA11: hsla(0, 0%, 0%, 0.565); + --grayA12: hsla(0, 0%, 0%, 0.91); + --blue1: hsl(206, 100%, 99.2%); + --blue2: hsl(210, 100%, 98%); + --blue3: hsl(209, 100%, 96.5%); + --blue4: hsl(210, 98.8%, 94%); + --blue5: hsl(209, 95%, 90.1%); + --blue6: hsl(209, 81.2%, 84.5%); + --blue7: hsl(208, 77.5%, 76.9%); + --blue8: hsl(206, 81.9%, 65.3%); + --blue9: hsl(206, 100%, 50%); + --blue10: hsl(208, 100%, 47.3%); + --blue11: hsl(211, 100%, 43.2%); + --blue12: hsl(211, 100%, 15%); +} +.dark { + --app-bg: var(--gray1); + --app-text: #ffffff; + --lowContrast: #000000; + --highContrast: #ffffff; + --gray1: hsl(0, 0%, 8.5%); + --gray2: hsl(0, 0%, 11%); + --gray3: hsl(0, 0%, 13.6%); + --gray4: hsl(0, 0%, 15.8%); + --gray5: hsl(0, 0%, 17.9%); + --gray6: hsl(0, 0%, 20.5%); + --gray7: hsl(0, 0%, 24.3%); + --gray8: hsl(0, 0%, 31.2%); + --gray9: hsl(0, 0%, 43.9%); + --gray10: hsl(0, 0%, 49.4%); + --gray11: hsl(0, 0%, 62.8%); + --gray12: hsl(0, 0%, 93%); + --grayA1: hsla(0, 0%, 100%, 0); + --grayA2: hsla(0, 0%, 100%, 0.026); + --grayA3: hsla(0, 0%, 100%, 0.056); + --grayA4: hsla(0, 0%, 100%, 0.077); + --grayA5: hsla(0, 0%, 100%, 0.103); + --grayA6: hsla(0, 0%, 100%, 0.129); + --grayA7: hsla(0, 0%, 100%, 0.172); + --grayA8: hsla(0, 0%, 100%, 0.249); + --grayA9: hsla(0, 0%, 100%, 0.386); + --grayA10: hsla(0, 0%, 100%, 0.446); + --grayA11: hsla(0, 0%, 100%, 0.592); + --grayA12: hsla(0, 0%, 100%, 0.923); + --blue1: hsl(212, 35%, 9.2%); + --blue2: hsl(216, 50%, 11.8%); + --blue3: hsl(214, 59.4%, 15.3%); + --blue4: hsl(214, 65.8%, 17.9%); + --blue5: hsl(213, 71.2%, 20.2%); + --blue6: hsl(212, 77.4%, 23.1%); + --blue7: hsl(211, 85.1%, 27.4%); + --blue8: hsl(211, 89.7%, 34.1%); + --blue9: hsl(206, 100%, 50%); + --blue10: hsl(209, 100%, 60.6%); + --blue11: hsl(210, 100%, 66.1%); + --blue12: hsl(206, 98%, 95.8%); +} + +div [command-dialog-mask] { + background-color: rgba(0, 0, 0, 0.3); + height: 100vh; + left: 0; + position: fixed; + top: 0; + width: 100vw; + z-index: 200; +} +div [command-dialog-wrapper] { + position: relative; + background: var(--gray2); + border-radius: 6px; + box-shadow: none; + flex-direction: column; + margin: 20vh auto auto; + max-width: 560px; +} +div [command-dialog-footer] { + border-top: 1px solid var(--gray6); + align-items: center; + background: var(--gray4); + color: var(--gray11); + border-radius: 0 0 8px 8px; + box-shadow: none; + display: flex; + flex-direction: row-reverse; + flex-shrink: 0; + height: 44px; + justify-content: space-between; + padding: 0 12px; + position: relative; + user-select: none; + width: 100%; + z-index: 300; + font-size: 12px; +} +.algolia [command-input] { + font-family: var(--font-sans); + width: 100%; + font-size: 18px; + padding: 12px; + outline: none; + background: var(--bg); + color: var(--gray12); + caret-color: var(--vcp-c-brand); + margin: 0; +} +.algolia [command-input]::placeholder { + color: var(--gray9); +} +.algolia [command-list] { + height: var(--command-list-height); + max-height: 360px; + overflow: auto; + overscroll-behavior: contain; + transition: 100ms ease; + transition-property: height; +} +.algolia .detail-list [command-item] { + min-height: 56px; + max-height: 112px; + padding: 10px 16px; + height: auto; +} +.algolia .detail-list [command-item] .des { + word-break: break-all; + white-space: wrap; + margin-top: 6px; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; +} +.algolia [command-item] { + position: relative; + content-visibility: auto; + cursor: pointer; + height: 56px; + font-size: 14px; + display: flex; + align-items: center; + gap: 12px; + padding: 0px 16px; + color: var(--gray12); + user-select: none; + will-change: background, color; + transition: all 150ms ease; + transition-property: none; + border-radius: 4px; + margin-top: 4px; + background-color: var(--lowContrast); +} +.algolia [command-item]:first-child { + margin-top: 0px; +} +.algolia [command-item][aria-selected="true"], +.algolia [command-item]:hover { + background: var(--vcp-c-brand); + color: #fff; +} +.algolia [command-item][aria-selected="true"] svg, +.algolia [command-item]:hover svg { + color: #fff; +} +.algolia [command-item][aria-selected="true"] [command-linear-shortcuts], +.algolia [command-item]:hover [command-linear-shortcuts] { + display: flex; + margin-left: auto; + gap: 8px; +} +.algolia [command-item][aria-selected="true"] [command-linear-shortcuts] kbd, +.algolia [command-item]:hover [command-linear-shortcuts] kbd { + font-family: var(--font-sans); + font-size: 13px; + color: var(--gray11); +} +.algolia [command-item]:active { + transition-property: background; + background: var(--gray4); +} +.algolia [command-item] svg { + width: 16px; + height: 16px; + color: var(--gray10); +} +.algolia [command-empty=""] { + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + height: 64px; + white-space: pre-wrap; + color: var(--gray11); +} +.algolia [command-dialog-mask] { + background-color: rgba(75, 75, 75, 0.8); +} +.algolia [command-dialog-header] { + padding: 12px; +} +.algolia [command-dialog-body] { + padding: 0 12px 12px; +} +.algolia [command-dialog-footer] { + align-items: center; + border-radius: 0 0 8px 8px; + box-shadow: + 0 -1px 0 0 #e0e3e8, + 0 -3px 6px 0 rgba(69, 98, 155, 0.12); + display: flex; + flex-direction: row-reverse; + flex-shrink: 0; + height: 44px; + justify-content: space-between; + padding: 0 12px; + position: relative; + user-select: none; + width: 100%; + z-index: 300; +} +.algolia [command-group-heading] { + color: var(--vcp-c-brand); + font-size: 0.85em; + font-weight: 600; + line-height: 32px; + margin: 0 -4px; + padding: 0 4px; + top: 0; + z-index: 10; + width: 100%; +} + +.algolia .command-palette-commands { + color: var(--docsearch-muted-color); + display: flex; + list-style: none; + margin: 0; + padding: 0; +} +@media screen and (max-width: 560px) { + .algolia .command-palette-commands { + display: none; + } + + div [command-dialog-wrapper] { + margin: 0; + height: 100vh; + } + .algolia [command-dialog-footer] { + justify-content: center; + } + .algolia [command-input] { + padding: 6px 4px; + } + .algolia [command-list] { + max-height: calc(100vh - 120px); + } +} +.algolia .command-palette-commands li { + display: flex; + align-items: center; +} +.algolia .command-palette-commands li:not(:last-of-type) { + margin-right: 0.8em; +} +.algolia .command-palette-logo a { + display: flex; + align-items: center; + gap: 8px; +} +.algolia .command-palette-logo svg { + height: 24px; + width: 24px; +} +.algolia .command-palette-commands-key { + align-items: center; + background: var(--gray3); + border-radius: 2px; + display: flex; + height: 18px; + justify-content: center; + margin-right: 0.4em; + padding: 0 0 1px; + color: var(--gray11); + border: 0; + width: 20px; +} +.dark .algolia [command-dialog-footer] { + box-shadow: none; +} +div[command-group] { + display: block !important; +} +div[command-item] { + display: flex !important; +} +.search-dialog div[command-item] > div.link { + width: 100%; +} +.search-dialog div[command-item] .title { + display: flex; + justify-content: space-between; +} + +.search-dialog div[command-item] .title i.prefix { + color: var(--vp-c-brand-1); +} +.search-dialog div[command-item]:hover .title i.prefix, +.search-dialog div[command-item][aria-selected="true"] .title i.prefix { + color: #fff; +} + +.search-dialog div[command-item] .des { + text-overflow: ellipsis; + overflow: hidden; + word-break: keep-all; + white-space: nowrap; +} +.search-dialog div[command-item] .headings { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; +} + +.search-dialog div[command-item] .date { + min-width: 86px; + text-align: right; +} +.search-dialog div[command-item] mark { + background: none; + color: var(--vp-c-brand-1); +} +.search-dialog div[command-item][aria-selected="true"] mark, +.search-dialog div[command-item]:hover mark { + color: inherit; + text-decoration: underline; +} From 73f10b88464c742957b85595b6099cf2a171ff27 Mon Sep 17 00:00:00 2001 From: Thaddaeus Chua Date: Sat, 7 Mar 2026 17:26:55 +0800 Subject: [PATCH 03/22] Implement temporary url fix --- .../src/pagefindSearchBar/Search.vue | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/vue-components/src/pagefindSearchBar/Search.vue b/packages/vue-components/src/pagefindSearchBar/Search.vue index 0e1a464016..d3cc9805e9 100644 --- a/packages/vue-components/src/pagefindSearchBar/Search.vue +++ b/packages/vue-components/src/pagefindSearchBar/Search.vue @@ -22,6 +22,21 @@ watch(showModal, (isOpen) => { resetFilters: true, showImages: false, // Pagefind UI default styles will be applied here + processResult: (result) => { + // Remove the '/markbind' prefixt + if (result.url.startsWith('/markbind/')) { + result.url = result.url.substring(9); + } + // Also process subresults (headings within the page) + if (result.sub_results && Array.isArray(result.sub_results)) { + result.sub_results.forEach((subResult) => { + if (subResult.url.startsWith('/markbind/')) { + subResult.url = subResult.url.substring(9); + } + }); + } + return result; + }, }); // Focus the input inside the new structure @@ -29,6 +44,18 @@ watch(showModal, (isOpen) => { if (input) { input.focus(); } + + // Fix for Redirection & Modal Closing: + // We listen for clicks on the results. If a result is clicked, + // we close the modal. This is especially important for anchors (#). + const container = document.querySelector('#pagefind-search-input'); + container.addEventListener('click', (e) => { + const anchor = e.target.closest('a'); + if (anchor) { + // Close the modal before the browser navigates + showModal.value = false; + } + }); } }); } From 1b154373676a51ccd9e4e1cea1b6ac8232cb2cdc Mon Sep 17 00:00:00 2001 From: Thaddaeus Chua Date: Sat, 7 Mar 2026 20:34:30 +0800 Subject: [PATCH 04/22] Add arrow key navigation support --- .../src/pagefindSearchBar/Search.vue | 169 ++++++++++++++++-- 1 file changed, 158 insertions(+), 11 deletions(-) diff --git a/packages/vue-components/src/pagefindSearchBar/Search.vue b/packages/vue-components/src/pagefindSearchBar/Search.vue index d3cc9805e9..ba7145a533 100644 --- a/packages/vue-components/src/pagefindSearchBar/Search.vue +++ b/packages/vue-components/src/pagefindSearchBar/Search.vue @@ -1,7 +1,7 @@