Initalize

This commit is contained in:
Your Name
2026-05-03 12:12:57 -04:00
commit 38652eb9b5
10603 changed files with 1762136 additions and 0 deletions

View File

@@ -0,0 +1 @@
1.2.15

View File

@@ -0,0 +1,12 @@
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
[*.js]
indent_style = space
indent_size = 2

View File

@@ -0,0 +1,5 @@
temp
cache
dist
_site
!.vitepress

View File

@@ -0,0 +1,38 @@
{
"env": {
"node": true,
"mocha": true,
"es6": true
},
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "babel-eslint",
"allowImportExportEverywhere": true,
"sourceType": "module",
"ecmaVersion": 9
},
"extends": [
"plugin:vue/recommended",
"google"
],
"rules": {
"vue/multi-word-component-names": 0,
"linebreak-style": 0,
"arrow-parens": ["error",
"as-needed"
],
"max-len": ["error", {
"code": 12000,
"ignoreComments": true
}],
"require-jsdoc": ["error", {
"require": {
"FunctionDeclaration": false,
"MethodDefinition": false,
"ClassDeclaration": false,
"ArrowFunctionExpression": false,
"FunctionExpression": false
}
}]
}
}

View File

@@ -0,0 +1,24 @@
name: vitepress-theme-default-plus
proxy:
cli:
- vitepress-theme-default-plus.lndo.site:5173
services:
cli:
api: 4
image: node:18
command: sleep infinity
ports:
- 5173:5173/http
scanner: false
user: node
build:
app: |
npm install
tooling:
node:
service: cli
npm:
service: cli
vitepress:
service: cli
cmd: npx vitepress

View File

@@ -0,0 +1 @@
20

View File

@@ -0,0 +1,436 @@
## {{ UNRELEASED_VERSION }} - [{{ UNRELEASED_DATE }}]({{ UNRELEASED_LINK }})
## v1.1.5 - [March 5, 2026](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.5)
* Introduced `mvb` option `runtime` to allow explicit runtime targeting and to avoid not-the-best autodetection
## v1.1.4 - [March 5, 2026](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.4)
* Updated to use `npm` Trusted Publishing part 3
## v1.1.3 - [March 5, 2026](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.3)
* Updated to use `npm` Trusted Publishing part 2
## v1.1.2 - [March 5, 2026](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.2)
* Added `bun` support to `mvb`
* Updated to use `npm` Trusted Publishing
## v1.1.1 - [February 14, 2025](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.1)
* Fixed bug causing `mvb` and `vitepress build` to fail when `landoPlugin` is set and `VPL_BASE_URL` or `NETLIFY ` are not
## v1.1.0 - [February 14, 2025](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0)
## New Features
* Added `version` alias information to config
* Added `RegEx` support for `internalDomains` matching
* Added `get-base-url` helper util
* Added `is-faux-internal` helper util
* Added `caching` to `mvb` multiversion build
* Added better dev `branch` detection to `mvb`
* Added `frontmatter.url-loader` to allow `.md` files to pull content from remote `markdown` sources
* Added new `multiversion-vitepress-build` command
* Added `useTags` composable to get docs version information from `git`
* Added `VPLVersionLink` component
* Changed `VPLVersionLink` to _not_ normalize links
* Improved `get-tags` and `mvb` to better handle scenarios with no matchings versions or tags
* Improved robustness of `mvb` build environment generation
* Improved `is-dev-release` to handle `dev` releases that are in front of a prerelease tag
* Improved dev release resolution
* Improved `mvb` and `root` link normalization
* Improved `VPLVersionLink` to only need `version` input
* Switched these docs to `multiversion-vitepress-build`
* Updated to [vitepress@1.5.0](https://github.com/vuejs/vitepress/blob/main/CHANGELOG.md)
* Updated `scss` and `sass` compilation to `modern-compiler`
## Bug Fixes
* Fixed `frontmatter.editLink` to fallback to theme and default `text` if not passed in
* Fixed incorrect `lodash-es/uniq.js` import in `landov3` config set
* Fixed bug causing build error when `metadata` is added to a page with no `git` history
* Fixed bug caused by `icon` being a variable instead of a string in `VPLTeamMembersItem`
* Fixed bug caused by `VPLTeamMembers.members` typing as `Object` instead of `Array`
* Fixed bug causing `robots.txt` policies to not be applied when using `policies` instead of `policy`
* Fixed watch bug on `VPLCollectionItems` preventing tag toggling from working correctly
* Fixed bug in `VPLLink` causing some links to to not normalize correctly
* Fixed bug causing `VPLDocFooter` bleed over with some `frontmatter` combinations of `prev` and `next`
* Fixed `semver` import causing all kinds of chaos when importing this theme as a package
* Fixed bug causing `alias.dev` to always report highest built tag instead of actual dev version
* Fixed `useTags()` to return correct links for sites with non-root `base` considerations take 2
* Fixed `prev-next` calculation when using sites with non `/` bases [#55](https://github.com/lando/vitepress-theme-default-plus/pull/55)
* Fixed bug with `<VPLLink>` not dynamically updating and showing strange behavior for `prev|next` links
* Fixed bug causing `active` to not be correctly set on `VPLNavBarMenuGroup`
* Fixed bug in `normalize-mvb` causing `multiVersionBuild.build` to sometimes not be set correctly
* Fixed `lando` configset bug causing `sidebarEnder` to not merge correctly
* Fixed bug preventing user specified `buildEnd` and `transformPageData` from running after theme's
* Fixed bug preventing `mvb` from correctly setting the `mvbase`
* Fixed duplicate `//` in generated `rss` feed
* Fixed bug causing `robots.txt` build to throw an error when `policies` is `undefined`
* Fixed bug causing `netlify` specific `mvb` to not get current tags
* Fixed `git tag` dev alias detection to incorrectly report `fatal: Not a valid object name undefined` on first attempt
## v1.1.0-beta.24 - [November 23, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.24)
* Improved `mvb` tag fetching to ensure remote tags are canonical for version building, fixes `would clobber existing tag` error(s)
## v1.1.0-beta.23 - [November 13, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.23)
* Changed `mvb` to never cache the main/base/root build
## v1.1.0-beta.22 - [November 13, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.22)
* Added `VPL_MVB_DEV_VERSION` to `mvb` envvars
* Improved `get-tags` to prefer `VPL_MVB_DEV_VERSION` as the `dev` alias if available
## v1.1.0-beta.21 - [November 11, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.21)
* Fixed incorrect `lodash-es/uniq.js` import in `landov3` config set
## v1.1.0-beta.20 - [November 11, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.20)
* Added `frontmatter.url-loader` to allow `.md` files to pull content from remote `markdown` sources
* Fixed `frontmatter.editLink` to fallback to theme and default `text` if not passed in
## v1.1.0-beta.19 - [November 9, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.19)
* Added `version` alias information to config
* Fixed bug preventing user specified `buildEnd` and `transformPageData` from running after theme's
* Fixed bug preventing `mvb` from correctly setting the `mvbase`
* Fixed duplicate `//` in generated `rss` feed
* Improved `mvb` and `root` link normalization
* Improved `VPLVersionLink` to only need `version` input
* Updated to [vitepress@1.5.0](https://github.com/vuejs/vitepress/blob/main/CHANGELOG.md)
## v1.1.0-beta.18 - [November 4, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.18)
* Fixed bug in `normalize-mvb` causing `multiVersionBuild.build` to sometimes not be set correctly
* Fixed `lando` configset bug causing `sidebarEnder` to not merge correctly
## v1.1.0-beta.17 - [November 1, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.17)
* Updated GTM tracking
## v1.1.0-beta.16 - [October 31, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.16)
* Added `RegEx` support for `internalDomains` matching
* Added `get-base-url` helper util
* Added `is-faux-internal` helper util
* Fixed bug causing `active` to not be correctly set on `VPLNavBarMenuGroup`
* Improved `get-tags` and `mvb` to better handle scenarios with no matchings versions or tags
* Standardized support envars to `VPL_` namespacing
* Updated `scss` and `sass` compilation to `modern-compiler`
* Updated to `vitepress@1.4.2`
## v1.1.0-beta.15 - [October 19, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.15)
* Fixed bug with `<VPLLink>` not dynamically updating and showing strange behavior for `prev|next` links
## v1.1.0-beta.14 - [October 3, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.14)
* Update default config sets part cinco
## v1.1.0-beta.13 - [October 2, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.13)
* Fixed `useTags()` to return correct links for sites with non-root `base` considerations take 2
## v1.1.0-beta.12 - [October 2, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.12)
* Added `aliasLinks` as an `export` of `useTags()`
* Fixed `useTags()` to return correct links for sites with non-root `base` considerations
## v1.1.0-beta.11 - [October 2, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.11)
* Update default config sets part четыре
## v1.1.0-beta.10 - [October 2, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.10)
* Update default config sets part tres
## v1.1.0-beta.9 - [October 2, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.9)
* Improved dependency organization
## v1.1.0-beta.8 - [October 2, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.8)
* Update default config sets part deux
## v1.1.0-beta.7 - [October 2, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.7)
* Update default config sets
## v1.1.0-beta.6 - [October 2, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.6)
* Improved dev release resolution
* Fixed `prev-next` calculation when using sites with non `/` bases [#55](https://github.com/lando/vitepress-theme-default-plus/pull/55)
## v1.1.0-beta.5 - [October 1, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.5)
* Changed `VPLVersionLink` to _not_ normalize links
* Fixed bug in `VPLLink` causing some links to to not normalize correctly
* Fixed bug causing `VPLDocFooter` bleed over with some `frontmatter` combinations of `prev` and `next`
* Fixed `semver` import causing all kinds of chaos when importing this theme as a package
## v1.1.0-beta.4 - [October 1, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.4)
* Added `caching` to `mvb` multiversion build
* Added better dev `branch` detection to `mvb`
* Fixed bug causing `alias.dev` to always report highest built tag instead of actual dev version
* Improved robustness of `mvb` build environment generation
## v1.1.0-beta.3 - [September 30, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.3)
* Improved `is-dev-release` to handle `dev` releases that are in front of a prerelease tag
## v1.1.0-beta.2 - [September 30, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.2)
* Fixed bug causing `robots.txt` build to throw an error when `policies` is `undefined`
* Fixed bug causing `netlify` specific `mvb` to not get current tags
## v1.1.0-beta.1 - [September 30, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.1)
## New Features
* Added new `multiversion-vitepress-build` command
* Added `useTags` composable to get docs version information from `git`
* Added `VPLVersionLink` component
* Switched these docs to `multiversion-vitepress-build`
* Updated to `vitepress@1.3.4`
## Bug Fixes
* Fixed bug causing build error when `metadata` is added to a page with no `git` history
* Fixed bug caused by `icon` being a variable instead of a string in `VPLTeamMembersItem`
* Fixed bug caused by `VPLTeamMembers.members` typing as `Object` instead of `Array`
* Fixed bug causing `robots.txt` policies to not be applied when using `policies` instead of `policy`
* Fixed watch bug on `VPLCollectionItems` preventing tag toggling from working correctly
## v1.0.2 - [April 17, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.2)
* Updated `lando` config sets
* Updated to `vitepress@1.1.0`
## v1.0.1 - [April 4, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.1)
## Bug Fixes
* Fixed bug causing auto population of `authors` from `contributors` when `authors` is empty
## v1.0.0 - [April 4, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0)
## New Features
* Added `maintainer` key to `contributors`
* Added `rtfm47` to the `debotify` protocol
* Updated to `vitepress@1.0.2`
## v1.0.0-beta.42 - [April 2, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.42)
## New Features
* Improved `sponsors.data` to accept a URL that returns `json` or `yaml`
## v1.0.0-beta.41 - [March 28, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.41)
## Fixes
* Checked if `authors` is defined by @mtdvlpr in https://github.com/lando/vitepress-theme-default-plus/pull/32
* Converted `undefined` `authors` to empty array by @mtdvlpr in https://github.com/lando/vitepress-theme-default-plus/pull/30
* Falled back to empty array for `authors` by @mtdvlpr in https://github.com/lando/vitepress-theme-default-plus/pull/31
## v1.0.0-beta.40 - [March 5, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.40)
## New Features
* Added `mergeWith` for more explicit contrubutor merging
* Updated to `vitepress@1.0.0-rc.44`
## Fixes
* Changed member data to only render if available [#27](https://github.com/lando/vitepress-theme-default-plus/pull/27)
* Fixed Algolia search from displaying contrib information in source text, requires reindexing on Algolia end
* Fixed Algolia search from attempting to route to internal pages that are actually external
## Internal
* Changed `VPLTeamMembersItem.vue` to wrap name in `div` instead of `h1`
## v1.0.0-beta.39 - [February 17, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.39)
## Fixes
* Fixed: don't enable `carbonAds` by default by @mtdvlpr in https://github.com/lando/vitepress-theme-default-plus/pull/26
* Fixed: correctly compute `hlocation` by @mtdvlpr in https://github.com/lando/vitepress-theme-default-plus/pull/24
## New Contributors
* @mtdvlpr made their first contribution in https://github.com/lando/vitepress-theme-default-plus/pull/26
**Full Changelog**: https://github.com/lando/vitepress-theme-default-plus/compare/v1.0.0-beta.38...v1.0.0-beta.39
## v1.0.0-beta.38 - [February 15, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.38)
* Fixed `layouts` resolution when ingesting theme
## v1.0.0-beta.37 - [February 15, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.37)
* Added `color`, `icon`, `styles` and `tagClass` properties to `<VPLCollectionTag>`
* Fixed some styling bugs in `<VPLCollectionTag>`
* Simplified `useCollection` tagging to an all purpose `tags` export
## v1.0.0-beta.36 - [February 14, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.36)
* Improved exportability of non-essentials
## v1.0.0-beta.35 - [February 14, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.35)
* Switched `lodash` to `lodash-es` because 🤦
## v1.0.0-beta.34 - [February 14, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.34)
* Improved design of `tagging` and related components and composables ❤️
## v1.0.0-beta.33 - [February 13, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.33)
* Updated `lando` config sets
## v1.0.0-beta.32 - [February 12, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.32)
* Added `tagging` as [a thing](https://vitepress-theme-default-plus.lando.dev/guides/tagging-shit.html)
* Improved `createContentLoader` so it returns additional optional `frontmatter` but removes unneeded props for performance reasons
## v1.0.0-beta.31 - [February 9, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.31)
* AUTO DEPLOY FIX 2
## v1.0.0-beta.30 - [February 9, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.30)
* AUTO DEPLOY FIX 1
## v1.0.0-beta.29 - [February 8, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.29)
* Fixed broken `internalDomains` handling to force `target=_self`
## v1.0.0-beta.28 - [February 7, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.28)
* Update sponsor linx
## v1.0.0-beta.27 - [February 7, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.27)
* Fixed bug causing `vitepress build` failures on non-file `git log` locations
* Fixed bug causing `VPLCollectionIcon.vue` to not render correctly on a hard refresh
## v1.0.0-beta.26 - [February 7, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.26)
* Updated to `vitepress@1.0.0-rc.42`
## v1.0.0-beta.25 - [February 7, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.25)
* Guard `prev.id` and `next.id` in `useCollection` like it's nobodies biznizz
## v1.0.0-beta.24 - [February 2, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.24)
* Updated to `vitepress@1.0.0-rc.41`
* Updated `sponsors.yaml`
## v1.0.0-beta.23 - [February 1, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.23)
* Fixed collection `WSOD` in some `undefined` id situations
## v1.0.0-beta.22 - [January 31, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.22)
* Fixed default `sidebarEnder` for `lando` config sets
## v1.0.0-beta.21 - [January 30, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.21)
* Added an `export` for our `createContentLoader`
* Removed reliance on `vite/normalizePath`
* TOMLize netlify plugin settings
## v1.0.0-beta.20 - [January 25, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.20)
* Fixed broken `base` in Lando config sets
* Updated `twitter` to `x`
## v1.0.0-beta.19 - [January 25, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.19)
* Improved `vite` configuration for better portability part 2
## v1.0.0-beta.18 - [January 25, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.18)
* Improved `vite` configuration for better portability
## v1.0.0-beta.17 - [January 25, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.17)
* Dealias all `vitepress` components for better portability
## v1.0.0-beta.16 - [January 25, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.16)
* Updated to latest `vitepress` take 2
## v1.0.0-beta.15 - [January 25, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.15)
* Updated to latest `vitepress`
## v1.0.0-beta.14 - [January 25, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.14)
* Fixed more extension chaos
## v1.0.0-beta.13 - [January 25, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.13)
* Fixed `Cannot find module` on `enhance-app-with-layouts`
## v1.0.0-beta.12 - [January 24, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.12)
* Fixed `<Jobs>` and `<Sponsors>` dark mode background color to be more consistent with `<CarbonAds>`
## v1.0.0-beta.11 - [January 24, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.11)
* Casing matters
## v1.0.0-beta.10 - [January 24, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.10)
* Added ability to append a *single* menu item to the end of the sidebar with the `sidebarEnder` config key
## v1.0.0-beta.9 - [January 24, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.9)
* Fixed `dependency` resolution
## v1.0.0-beta.8 - [January 24, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.8)
* Fixed `dependency` resolution
## v1.0.0-beta.7 - [January 24, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.7)
* Fixed `dependency` resolution
## v1.0.0-beta.6 - [January 24, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.6)
* Fixed automatic `gitRoot` discovery when used outside itself
* Improved `base` usage
* Migrated `lando` config sets to VitePress
## v1.0.0-beta.5 - [January 23, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.5)
* Fixed internal `import` resolutions so theme can be used externally
* Improved install docs to reflect the need to also install `vitepress`
## v1.0.0-beta.4 - [January 23, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.4)
* Fixed bug causing `VPLDocFooter` to not show when toggling between a `collection` and normal doc
* Fixed `netlify` form input text in dark mode so it is visible
* Fixed broken GitHub support link
## v1.0.0-beta.3 - [January 23, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.3)
* First deploy to `npm` part 2
## v1.0.0-beta.2 - [January 23, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.2)
* First deploy to `npm`
## v1.0.0-beta.1 - [January 23, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.0.0-beta.1)
* Migrated [@lando/vuepress-theme-default-plus](https://github.com/lando/vuepress-theme-default-plus) to [VitePress](https://vitepress.dev/)

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Lando Alliance
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,58 @@
# VitePress Default Theme +
This extends the [default VitePress theme](https://vitepress.dev/) with some extra power and features such as:
<div align="center">
![features](./docs/public/images/features.png)
</div>
Or just go to the [docs](https://vitepress-theme-default-plus.lando.dev) for the docs to learn more.
## Docs
* [Overview](https://vitepress-theme-default-plus.lando.dev/overview)
* [Installation](https://vitepress-theme-default-plus.lando.dev/install.html)
* [Usage](https://vitepress-theme-default-plus.lando.dev/usage.html)
* [Configuration](https://vitepress-theme-default-plus.lando.dev/config/config.html)
* [Guides](https://vitepress-theme-default-plus.lando.dev/guides.html)
* [Examples](https://github.com/lando/vitepress-theme-default-plus)
* [Development](https://vitepress-theme-default-plus.lando.dev/development.html)
## Issues, Questions and Support
If you have a question or would like some community support we recommend you [join us on Slack](https://launchpass.com/devwithlando). Note that this is the Slack community for [Lando](https://lando.dev) but we are more than happy to help with this module as well!
If you'd like to report a bug or submit a feature request then please [use the issue queue](https://github.com/lando/vitepress-theme-default-plus/issues/new/choose) in this repo.
## Changelog
We try to log all changes big and small in both [THE CHANGELOG](https://github.com/lando/vitepress-theme-default-plus/blob/main/CHANGELOG.md) and the [release notes](https://github.com/lando/vitepress-theme-default-plus/releases).
## Releasing
To deploy and publish a new version of the package to the `npm` registry you need only [create a release on GitHub](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository) with a [semver](https://semver.org) tag.
Note that prereleases will get pushed to the `edge` tag on the `npm` registry.
## Maintainers
* [@pirog](https://github.com/pirog)
* [@reynoldsalec](https://github.com/reynoldsalec)
## Contributors
<a href="https://github.com/lando/vitepress-theme-default-plus/graphs/contributors">
<img src="https://contrib.rocks/image?repo=lando/vitepress-theme-default-plus" />
</a>
Made with [contributors-img](https://contrib.rocks).
## Other Resources
* [LICENSE](/LICENSE)
* [TERMS OF USE](https://docs.lando.dev/terms)
* [PRIVACY POLICY](https://docs.lando.dev/privacy)
* [CODE OF CONDUCT](https://docs.lando.dev/coc)

254
node_modules/@lando/vitepress-theme-default-plus/bin/mvb.js generated vendored Executable file
View File

@@ -0,0 +1,254 @@
#!/usr/bin/env node
import crypto from 'node:crypto';
import os from 'node:os';
import path from 'node:path';
import {format, inspect} from 'node:util';
import fs from 'fs-extra';
import parser from 'yargs-parser';
import {bold, dim, green, magenta, red} from 'colorette';
import {nanoid} from 'nanoid';
import {resolveConfig} from 'vitepress';
import {default as createExec} from '../utils/create-exec.js';
import {default as detectRuntime} from '../utils/detect-runtime.js';
import {default as getStdOut} from '../utils/parse-stdout.js';
import {default as getBranch} from '../utils/get-branch.js';
import {default as getTags} from '../utils/get-tags.js';
import {default as traverseUp} from '../utils/traverse-up.js';
import Debug from 'debug';
// debugger
const debug = Debug('@lando/mvb'); // eslint-disable-line
// enable debug if applicable
if (process.argv.includes('--debug') || process.env.RUNNER_DEBUG === '1') {
Debug.enable(process.env.DEBUG ?? '*');
}
// kenny loggin utils
const log = (message = '', ...args) => {
message = typeof message === 'string' ? message : inspect(message);
if (debug.enabled) debug(message, ...args);
else process.stdout.write(format(message, ...args) + '\n');
};
// lets start by getting argv
const argv = parser(process.argv.slice(2));
// source dir
const srcDir = argv._[0] ?? 'docs';
// orginal absolute path to source
const osource = path.resolve(process.cwd(), srcDir);
// help
const help = argv.h || argv.help;
// get site config
// @TODO: if no site then throw error? how do we determine that?
const siteConfig = await resolveConfig(osource, 'build', 'production');
const {site} = siteConfig;
// build default options
const defaults = {
ag: site?.themeConfig?.multiVersionBuild?.ag ?? false,
base: site?.base ?? '/',
build: site?.themeConfig?.multiVersionBuild?.build ?? 'stable',
cache: site?.themeConfig?.multiVersionBuild?.cache ?? true,
match: site?.themeConfig?.multiVersionBuild?.match ?? 'v[0-9].*',
outDir: path.relative(process.cwd(), siteConfig.outDir) ?? './.vitepress/dist',
runtime: detectRuntime() === 'bun' ? 'bun' : 'npm',
satisfies: site?.themeConfig?.multiVersionBuild?.satisfies ?? '*',
versionBase: site?.themeConfig?.multiVersionBuild?.base ?? '/v/',
};
// show help/usage if requested
if (help) {
log(`
Usage: ${dim('[CI=1]')} ${bold(`${path.basename(process.argv[1])} <root>`)} ${dim('[--base <base>] [--build <alias>] [--match "<match>"] [--no-cache] [--out-dir <dir>] [--satisifes "<satisfies>"] [--version-base <dir>] [--debug] [--help]')}
${green('Options')}:
--base sets site base ${dim(`[default: ${defaults.base}`)}]
--build uses this version alias for main/root build ${dim(`[default: ${defaults.build}`)}]
--match filters versions from git tags ${dim(`[default: "${defaults.match}"`)}]
--no-cache builds versioned docs every build ${dim(`[default: "${!defaults.cache}"`)}]
--out-dir builds into this location ${dim(`[default: ${defaults.outDir}`)}]
--runtime builds versioned docs using npx or bunx ${dim(`[default: "${defaults.runtime}"`)}]
--satisfies builds versioned docs in this semantic range ${dim(`[default: "${defaults.satisfies}"`)}]
--version-base builds versioned docs in this location ${dim(`[default: ${defaults.versionBase}`)}]
--debug shows debug messages
-h, --help displays this message
${green('Environment Variables')}:
CI installs in CI mode (e.g. does not prompt for user input)
`);
process.exit(0);
}
// intial feedback
debug('received argv %o', argv);
debug('default options %o', defaults);
log('found site %s at %s', magenta(site.title), magenta(osource));
// resolve options with argv input
const options = {
...defaults,
...argv,
cacheDir: path.resolve(process.env?.NETLIFY === 'true' ? '/opt/build/cache' : siteConfig.cacheDir, '@lando', 'mvb'),
tmpDir: path.resolve(os.tmpdir(), nanoid()),
};
debug('multiversion build from %o using resolved build options: %O', srcDir, options);
// executor
const runtimeX = options.runtime === 'bun' ? 'bunx' : 'npx';
const pkgInstaller = options.runtime === 'bun' ? ['bun', ['install', '--frozen-localfile']] : ['npm', ['clean-install']];
debug('multiversion build runtime %o with %O', options.runtime, {runtimeX, pkgInstaller});
// determine gitdir
const gitDir = path.resolve(traverseUp(['.git'], osource).find(dir => fs.existsSync(dir)), '..');
debug('determined git-dir: %o', gitDir);
// do the initial setup
fs.removeSync(options.tmpDir, {force: true, maxRetries: 10, recursive: true});
fs.mkdirSync(options.tmpDir, {recursive: true});
// create execers for source and tmp opts
const oexec = createExec({cwd: process.cwd(), debug});
const exec = createExec({cwd: options.tmpDir, debug});
// start it up
log('setting up mvb build environment using %s...', magenta(gitDir));
// lets make sure the source repo at least has all the tag information it needs
const updateArgs = ['fetch', options.ag === false ? 'origin' : '--all', '--tags', '--no-filter', '--force'];
// if shallow then add to update refs
if (getStdOut('git rev-parse --is-shallow-repository', {trim: true}) === 'true') updateArgs.push('--unshallow');
// fetch
await oexec('git', updateArgs);
// and then copy the repo in tmpdir so we can operate on it
fs.copySync(gitDir, options.tmpDir);
// checkout
await exec('git', ['checkout', getBranch(), '--force']);
// reset
await exec('git', ['reset', 'HEAD', '--hard']);
// pull
await exec('git', ['pull', 'origin', getBranch()]);
// also get
if (options.ag !== false) await exec('git', ['merge', options.ag, '-X', 'theirs']);
// get extended version information
const {extended} = await getTags(options.tmpDir, options);
debug('determined versions to build: %o', extended);
// if we cant find the base build then reset it to dev
if (extended.find(version => version.alias === options.build) === undefined) {
debug('could not find a ref for %o, resetting to %o', options.build, 'dev');
options.build = 'dev';
}
// set the base build
extended.unshift(extended.find(version => version.alias === options.build));
debug('determined main/root build is %o %o', options.build, extended[0]);
// now loop through extended and construct the build metadata
const builds = extended.map((version, index) => {
if (index === 0) {
version.base = options.base;
version.outDir = options.outDir;
} else {
version.base = path.resolve(`/${options.base}/${options.versionBase}/${version.alias ?? version.version}`) + '/';
version.outDir = path.join(options.outDir, options.versionBase, version.alias ?? version.version);
}
// if caching then also suggest a cache location
if (options.cache && version.base !== '/') {
version.cacheKey = path.join(options.base, options.versionBase, version.version, version.base);
version.cachePath = path.join(options.cacheDir, crypto.createHash('sha256').update(version.cacheKey).digest('hex'));
}
// return
return {...version, srcDir};
});
// get the dev build so we can set downstream envvars
const devBuild = builds.find(build => build.alias === 'dev');
debug('determined dev build %o', devBuild);
// report
log('%s build at %s using alias %s, ref %s', magenta('primary'), magenta(options.base), magenta(builds[0]?.alias), magenta(builds[0]?.ref));
log('%s %s builds at %s', magenta(builds.length - 1), magenta('versioned'), magenta(`${options.base}${options.versionBase}`.replace(/\/{2,}/g, '/')));
log('%s builds queued up!', magenta(builds.length));
log('');
// and now build them all
for (const build of builds) {
// separate out our stuff
const {alias, cachePath, ref, semantic, srcDir, version, ...config} = build;
// if we have cache then lets just copy it over
if (cachePath && fs.existsSync(cachePath)) {
log('restoring version %s from %s at %s...', magenta(alias ?? version), magenta('cache'), magenta(cachePath));
fs.removeSync(path.resolve(config.outDir), {force: true, maxRetries: 10, recursive: true});
fs.mkdirSync(config.outDir, {recursive: true});
fs.copySync(cachePath, path.resolve(config.outDir));
continue;
}
// if we get here we need to actually do a build
debug('building %o version %o with config %o', srcDir, `${alias ?? version}@${ref}`, config);
log('building version %s, ref %s, from %s to %s...', magenta(alias ?? version), magenta(ref), magenta(srcDir), magenta(`${config.outDir}/`));
// reset HEAD HARD
await exec('git', ['reset', 'HEAD', '--hard']);
// checkout new ref
await exec('git', ['checkout', ref]);
// reinstall
await exec(...pkgInstaller);
// update package.json if needed
const pjsonPath = path.join(options.tmpDir, 'package.json');
const pjson = JSON.parse(fs.readFileSync(pjsonPath, {encoding: 'utf8'}));
if (pjson.version !== semantic) {
// update version
pjson.version = semantic;
// rewrite
fs.writeFileSync(pjsonPath, JSON.stringify(pjson, null, 2));
// log
debug('updated %o version to %o', pjsonPath, semantic);
}
// build the version
try {
await exec(
runtimeX,
['vitepress', 'build', srcDir, '--outDir', config.outDir, '--base', config.base],
{env: {
VPL_MVB_BASE: site.base,
VPL_MVB_BUILD: 1,
VPL_MVB_DEV_VERSION: devBuild?.version ?? 'dev',
VPL_MVB_BRANCH: getBranch(gitDir),
VPL_MVB_SOURCE: process.cwd(),
}},
);
} catch (error) {
error.message = red(`Build failed for version ${version} with error: ${error.message}`);
error.build = build;
throw error;
}
// clean original
fs.removeSync(path.resolve(config.outDir), {force: true, maxRetries: 10, recursive: true});
// copy tmp to original
fs.copySync(path.join(options.tmpDir, config.outDir), path.resolve(config.outDir));
// save cache if its on
if (cachePath && options.cache) {
debug('saving version %s to %s at %s...', version, 'cache', cachePath);
fs.copySync(path.resolve(config.outDir), cachePath);
}
}
// FIN
log('');
log('%s %s builds at %s!', green('completed'), magenta(builds.length), magenta(siteConfig.outDir));

View File

@@ -0,0 +1,14 @@
import Debug from 'debug';
import {default as createContentLoader} from '../utils/create-content-loader.js';
const config = globalThis.VITEPRESS_CONFIG?.site?.themeConfig?.collections ?? {};
const debug = Debug('@lando/collections.data.js'); // eslint-disable-line
const siteConfig = globalThis.VITEPRESS_CONFIG;
const patterns = Object.entries(config)
.map(([type, config]) => config.patterns)
.flat(Number.POSITIVE_INFINITY);
debug('loading collections data with patterns config %o', patterns);
export default createContentLoader(patterns, {siteConfig}, {debug});

View File

@@ -0,0 +1,5 @@
// DO NOT MODIFY THIS FILE!!!
// if you do it or you will break things
// it is automatically transformed by vite to statically include our layout imports
export default function(app) {
};

View File

@@ -0,0 +1,17 @@
import Debug from 'debug';
import {default as getTags} from '../utils/get-tags.js';
const debug = Debug('@lando/tags.data.js'); // eslint-disable-line
const siteConfig = globalThis.VITEPRESS_CONFIG;
export default {
async load() {
const config = siteConfig?.userConfig?.themeConfig?.multiVersionBuild ?? {};
const root = siteConfig?.userConfig?.gitRoot;
const tags = await getTags(root, config, {debug: debug.extend('get-tags')});
debug('loading tag data %o', tags);
return tags;
},
};

View File

@@ -0,0 +1,17 @@
import Debug from 'debug';
import {default as getContributors} from '../utils/get-contributors.js';
const debug = Debug('@lando/team.data.js'); // eslint-disable-line
const siteConfig = globalThis.VITEPRESS_CONFIG;
export default {
async load() {
const contributors = siteConfig?.userConfig?.themeConfig?.contributors ?? false;
const root = siteConfig?.userConfig?.gitRoot;
const team = contributors !== false ? await getContributors(root, contributors, {debug: debug.extend('get-contribs'), paths: []}) : [];
debug('loading team data %o', team);
return team;
},
};

View File

@@ -0,0 +1,67 @@
import sortBy from 'lodash-es/sortBy.js';
import uniq from 'lodash-es/uniq.js';
import {computed, reactive} from 'vue';
import {useData, useRoute} from 'vitepress';
import {data as collections} from './collections.data.js';
export default function useCollection(type = undefined) {
const route = useRoute();
const path = route.path;
const {site, theme} = useData();
const themeTags = theme.value?.tags ?? {};
function findCurrentIndex() {
const result = pages.findIndex(p => `${site.value?.base ?? ''}${p.url}`.replace(/\/+/g, '/') === route.path);
if (result === -1) console.error(`content missing: ${route.path}`);
return result;
}
// filter pages if needed
const pages = type === undefined ? collections : collections.filter(page => page.type === type);
// current
const page = computed(() => pages[findCurrentIndex()]);
// prev page or loop back to end unless the end is me
const prevPage = computed(() => {
const prev = pages[findCurrentIndex() - 1] ?? pages[pages.length - 1];
return prev?.id !== page?.value?.id ? prev : undefined;
});
// next page or loop back to beginning unless the beginning is me
const nextPage = computed(() => {
const next = pages[findCurrentIndex() + 1] ?? pages[0];
return next?.id !== page?.value?.id ? next : undefined;
});
// these are meant to replace the core next|prev nav links
const prevnext = computed(() => ({
prev: prevPage.value ? {text: prevPage.value.title, link: prevPage.value.url} : undefined,
next: nextPage.value ? {text: nextPage.value.title, link: nextPage.value.url} : undefined,
}));
// get the tagz as well
const sortedTags = sortBy(uniq(pages.map(page => page.tags).flat(Infinity)));
const tags = reactive(Object.fromEntries(sortedTags.map(tag => ([tag, {
selected: false,
...themeTags[tag] ?? {},
}]))));
// helper func to see if a set of tag filtered pages has items or not, useful for showing collection sections
const hasItems = (items = [], tags = {}) => {
const tagList = Object.entries(tags).filter(pair => pair[1].selected === true).map(pair => pair[0]);
const filteredItems = items.filter(item => tagList.every(tag => item.tags.indexOf(tag) !== -1));
return filteredItems.length > 0;
};
return {
hasItems,
pages,
page,
nextPage,
prevnext,
prevPage,
path,
tags,
};
}

View File

@@ -0,0 +1,32 @@
import {data as tags} from './tags.data.js';
import {useData} from 'vitepress';
export default function useTags() {
// get version path data
const {theme} = useData();
// if no mvb then just return tags
if (!theme.value.multiVersionBuild) return tags;
// otherwise lets augment it with links and shit!
const {base} = theme.value.multiVersionBuild;
// generate links we can pass into VPLVersionLink
const links = tags.versions
.map(version => ({
text: version,
href: `/${base}/${version}/`.replace(/\/{2,}/g, '/'),
prerelease: /^v?\d+\.\d+\.\d+-\S+$/.test(version),
stable: tags?.aliases?.stable === version,
edge: tags?.aliases?.edge === version,
}));
// also generate alias linkes
const aliasLinks = {
dev: `/${base}/dev/`.replace(/\/{2,}/g, '/'),
edge: `/${base}/edge/`.replace(/\/{2,}/g, '/'),
stable: `/${base}/stable/`.replace(/\/{2,}/g, '/'),
};
return {...tags, links, aliasLinks};
}

View File

@@ -0,0 +1,5 @@
import {data as team} from './team.data.js';
export default function useTeam() {
return team;
}

View File

@@ -0,0 +1,138 @@
<template>
<div
v-if="showAlert && props.content !== ''"
:class="`alert-banner alert-${props.type}`"
>
<div class="alert-content">
<button
v-if="props.closeable"
aria-label="Close"
class="alert-dismiss-button"
@click="dismissAlert"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6 18 18 6M6 6l12 12"
/>
</svg>
</button>
<div
class="content"
v-html="props.content"
/>
</div>
</div>
</template>
<script setup>
import {onMounted, onUnmounted, ref, watch} from 'vue';
const props = defineProps({
content: {
type: String,
default: '',
required: true,
},
type: {
type: String,
default: 'tip',
},
closeable: {
type: Boolean,
default: true,
},
});
const isAlertMode = ref(props.content !== '');
const showAlert = ref(isAlertMode.value);
const update = (value = isAlertMode.value) => {
const htmlEl = window.document.querySelector('html');
htmlEl.classList.toggle('alert', value);
showAlert.value = value;
};
const dismissAlert = () => update(false);
onMounted(() => watch(isAlertMode, update, {immediate: true}));
onUnmounted(() => update(false));
</script>
<style lang="scss" scoped>
:root {
--vpl-alert-height: 40px;
}
.alert-banner {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--vpl-alert-height);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
.alert-content {
display: flex;
justify-content: center;
align-items: center;
gap: 1px;
.content {
font-size: 0.95rem;
font-weight: 500;
}
}
.alert-dismiss-button {
font-size: 1rem;
cursor: pointer;
background-color: transparent;
border: 0;
font-weight: 700;
width: 13px;
}
&.alert-brand {
background-color: var(--vp-c-brand-soft-hex);
color: var(--vp-c-brand-hard);
}
&.alert-danger {
background-color: var(--vp-c-red-soft-hex);
color: var(--vp-c-red-hard);
}
&.alert-tip {
background-color: var(--vp-c-indigo-soft-hex);
color: var(--vp-c-indigo-hard);
}
&.alert-info {
background-color: var(--vp-c-gray-soft-hex);
color: var(--vp-c-gray-hard);
}
&.alert-success {
background-color: var(--vp-c-green-soft-hex);
color: var(--vp-c-green-hard);
}
&.alert-warning {
background-color: var(--vp-c-yellow-soft-hex);
color: var(--vp-c-yellow-hard);
}
}
</style>

View File

@@ -0,0 +1,86 @@
<template>
<div id="docsearch" />
</template>
<script setup>
import docsearch from '@docsearch/js';
import {useData, useRoute, useRouter} from 'vitepress';
import {nextTick, onMounted, watch} from 'vue';
const props = defineProps({
algolia: {
type: Object,
default: () => ({}),
},
});
const router = useRouter();
const route = useRoute();
const {site, localeIndex, lang} = useData();
onMounted(update);
watch(localeIndex, update);
async function update() {
await nextTick();
const options = {
...props.algolia,
...props.algolia.locales?.[localeIndex.value],
};
const rawFacetFilters = options.searchParameters?.facetFilters ?? [];
const facetFilters = [
...(Array.isArray(rawFacetFilters)
? rawFacetFilters
: [rawFacetFilters]
).filter(f => !f.startsWith('lang:')),
`lang:${lang.value}`,
];
initialize({
...options,
searchParameters: {
...options.searchParameters,
facetFilters,
},
});
}
function initialize(userOptions = {}) {
const options = Object.assign({}, userOptions, {
container: '#docsearch',
navigator: {
navigate({itemUrl}) {
const {pathname: hitPathname} = new URL(window.location.origin + itemUrl);
// router doesn't handle same-page navigation so we use the native
// browser location API for anchor navigation
if (route.path === hitPathname) window.location.assign(window.location.origin + itemUrl);
else router.go(itemUrl);
},
},
transformItems(items) {
return items.map(item => Object.assign({}, item, {url: getRelativePath(item.url)}));
},
hitComponent({hit, children}) {
return {
__v: null,
type: 'a',
ref: undefined,
constructor: undefined,
target: '_blank',
key: undefined,
props: {href: hit.url, children, target: '_self'},
};
},
});
docsearch(options);
}
function getRelativePath(url) {
const {pathname, hash} = new URL(url, location.origin);
return pathname.replace(/\.html$/, site.value.cleanUrls ? '' : '.html') + hash;
}
</script>

View File

@@ -0,0 +1,59 @@
<template>
<Page>
<template #doc-top>
<img src="https://i.pinimg.com/originals/4c/d9/ce/4cd9ce636c6d5f23688f0fda99cd81cf.gif">
</template>
<template #doc-before>
<img src="https://i.pinimg.com/originals/4c/d9/ce/4cd9ce636c6d5f23688f0fda99cd81cf.gif">
</template>
<template #doc-after>
<img src="https://i.pinimg.com/originals/4c/d9/ce/4cd9ce636c6d5f23688f0fda99cd81cf.gif">
</template>
<template #doc-bottom>
<img src="https://i.pinimg.com/originals/4c/d9/ce/4cd9ce636c6d5f23688f0fda99cd81cf.gif">
</template>
<template #aside-top>
<img src="https://i.pinimg.com/originals/4c/d9/ce/4cd9ce636c6d5f23688f0fda99cd81cf.gif">
</template>
<template #aside-outline-before>
<img src="https://i.pinimg.com/originals/4c/d9/ce/4cd9ce636c6d5f23688f0fda99cd81cf.gif">
</template>
<template #aside-outline-after>
<img src="https://i.pinimg.com/originals/4c/d9/ce/4cd9ce636c6d5f23688f0fda99cd81cf.gif">
</template>
<template #aside-ads-before>
<img src="https://i.pinimg.com/originals/4c/d9/ce/4cd9ce636c6d5f23688f0fda99cd81cf.gif">
</template>
<template #aside-ads-after>
<img src="https://i.pinimg.com/originals/4c/d9/ce/4cd9ce636c6d5f23688f0fda99cd81cf.gif">
</template>
<template #aside-bottom>
<img src="https://i.pinimg.com/originals/4c/d9/ce/4cd9ce636c6d5f23688f0fda99cd81cf.gif">
</template>
<div class="cat-container">
<Content />
</div>
</Page>
</template>
<script setup>
import Page from 'vitepress/dist/client/theme-default/components/VPDoc.vue';
</script>
<style lang="scss" scoped>
.contributors {
float: left;
max-width: 70%;
overflow: hidden;
max-height: 70px;
}
.contributors-flex {
height: 65px;
display: flex;
flex-direction: column;
flex-wrap: wrap;
justify-content: flex-end;
}
</style>

View File

@@ -0,0 +1,96 @@
<template>
<div class="collection-header">
<div class="collection-type">
<Icon
v-if="collection !== false"
:icon="icon"
:link="iconLink"
:title="collection"
/>
</div>
<div
v-if="authors.length > 0"
class="collection-avatars"
>
<div class="label">
By
<Link
v-for="(author, index) in authors"
:key="author.name"
:href="author.link"
no-icon
>
<span class="underline">{{ author.name }}</span><span class="separator">{{ getSeparator(index, authors.length) }}</span>
</Link>
</div>
<Author
v-for="author in authors"
:key="author.name"
size="icon"
:member="author"
/>
</div>
</div>
</template>
<script setup>
import {computed} from 'vue';
import {useData} from 'vitepress';
import Author from './VPLTeamMembersItem.vue';
import Icon from './VPLCollectionIcon.vue';
import Link from './VPLLink.vue';
const {frontmatter, page} = useData();
const authors = computed(() => frontmatter.value?.authors ?? []);
const collection = computed(() => frontmatter.value?.collection ?? false);
const icon = computed(() => page.value?.collection?.icon ?? false);
const iconLink = computed(() => page.value?.collection?.iconLink ?? false);
const getSeparator = (index, end = 0) => {
return index + 1 === end ? '' : ', ';
};
</script>
<style lang="scss" scoped>
.collection-header {
margin-bottom: 24px;
font-size: .75em;
align-items: flex-start;
z-index: 1;
position: relative;
display: flex;
justify-content: space-between;
a {
font-weight: 500;
color: color-mix(in srgb, var(--vp-c-brand-1) 90%, white);
.underline {
text-underline-offset: 2px;
text-decoration: underline;
}
.separator {
color: var(--vp-c-text-3);
text-decoration: none;
}
}
.collection-type {
display: flex;
gap: 4px;
align-items: center;
color: var(--vp-c-text-2);
}
.collection-avatars {
display: flex;
justify-content: flex-end;
.label {
margin-right: 14px;
}
.VPTeamMembersItem.icon {
overflow: visible;
margin-left: -14px;
}
}
}
</style>

View File

@@ -0,0 +1,59 @@
<template>
<div class="collection-icon">
<Link
:href="link"
rel="noopener"
>
<span
class="icon"
v-html="icon"
/>{{ title }}
</Link>
</div>
</template>
<script setup>
import Link from './VPLLink.vue';
const {title, icon, link} = defineProps({
icon: {
type: String,
default: () => {
return '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" /></svg>';
},
},
link: {
type: String,
default: undefined,
},
title: {
type: String,
default: 'doc',
},
});
</script>
<style lang="scss" scoped>
.collection-icon {
gap: 2px;
display: flex;
justify-content: flex-end;
align-items: center;
text-align: center;
font-weight: 500;
text-transform: capitalize;
color: var(--vp-c-text-3);
a.VPLink.link {
display: flex;
align-items: center;
gap: 2px;
}
&:hover {
color: var(--vp-c-brand-1);
}
.icon {
width: 16px;
}
}
</style>

View File

@@ -0,0 +1,188 @@
<template>
<article
class="collection-article-card"
:class="`${page.type} ${size}`"
>
<Icon
v-if="icon !== false"
:icon="icon"
:link="iconLink"
:title="page.type"
/>
<Link :href="page.url">
<h2 class="title">
{{ page.title }}
</h2>
</Link>
<div
class="summary"
>
{{ page.summary }}
</div>
<div class="attribution">
<div class="authors">
<div
v-if="page.authors && page.authors.length > 0"
class="avatars"
>
<Author
v-for="author in page.authors"
:key="author.name"
size="icon"
:member="author"
/>
</div>
<Link
v-for="(author, index) in page.authors"
:key="author.name"
class="names"
:href="author.link"
no-icon
>
<span class="underline">{{ author.name }}</span><span class="separator">{{ getSeparator(index, page.authors.length) }}&nbsp;</span>
</Link>
</div>
<time
v-if="more === 'date'"
class="date"
:datetime="page.datetime"
>
{{ hdate }}
</time>
<Link
v-else
:href="page.url"
class="read-more"
>
<time :datetime="page.datetime" />
Read More ->
</Link>
</div>
</article>
</template>
<script setup>
import {computed} from 'vue';
import {useData} from 'vitepress';
import Link from './VPLLink.vue';
import Author from './VPLTeamMembersItem.vue';
import Icon from './VPLCollectionIcon.vue';
const {more, page, size} = defineProps({
page: {
type: Object,
required: true,
},
size: {
type: String,
default: 'medium',
},
more: {
type: String,
default: 'readmore',
},
});
const {theme} = useData();
const collection = computed(() => page?.collection ?? false);
const icon = computed(() => theme.value?.collections[collection.value]?.icon ?? false);
const iconLink = computed(() => theme.value?.collections[collection.value]?.iconLink ?? false);
const hdate = computed(() => {
return new Date(page.date ?? page.timestamp).toLocaleDateString(undefined, {
year: 'numeric',
month: 'long',
day: 'numeric',
});
});
const getSeparator = (index, end = 0) => {
return index + 1 === end ? '' : ',';
};
</script>
<style lang="scss" scoped>
.collection-article-card {
background-color: var(--vp-c-bg-soft);
border-radius: var(--vpl-c-border-radius);
border: 1px solid var(--vp-c-bg-soft);
padding: 24px 24px 20px 24px;
display: flex;
flex-direction: column;
flex-grow: 1;
h2 {
color: var(--vp-c-brand-1);
margin-top: -18px;
line-height: 24px;
font-size: 20px;
font-weight: 700;
max-width: 80%;
}
.collection-icon {
font-weight: 500;
font-size: 10px;
position: relative;
top: -20px;
right: -16px;
color: var(--vp-c-text-3);
.icon {
width: 10px;
}
}
.summary {
padding-top: 1em;
padding-bottom: 2em;
line-height: 24px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-text-2);
flex-grow: 1;
}
.attribution {
display: flex;
justify-content: space-between;
margin-top: 10px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-text-3);
.authors {
display: flex;
.avatars {
display: flex;
justify-content: flex-end;
.VPTeamMembersItem.icon {
overflow: visible;
&:not(:first-child) {
margin-left: -14px;
}
}
}
}
.date {
color: var(--vp-c-text-3);
}
.read-more {
color: var(--vp-c-brand-3);
}
}
}
@media (max-width: 767px) {
.collection-article-card {
width: auto;
}
}
@media (max-width: 420px) {
.attribution .authors .names {
display: none;
}
}
</style>

View File

@@ -0,0 +1,66 @@
<template>
<div
v-if="tags.length > 0"
class="aside-tags-wrapper"
>
<span class="ad-header">Tags</span>
<div class="aside-tags">
<Link
v-for="tag in tags"
:key="tag.key"
:no-icon="true"
:href="tag.href"
target="_self"
>
<Tag :text="tag.name" />
</Link>
</div>
</div>
</template>
<script setup>
import {computed} from 'vue';
import {useData} from 'vitepress';
import encodeTag from '../utils/encode-tag.js';
import Link from './VPLLink.vue';
import Tag from './VPLCollectionTag.vue';
const {frontmatter, theme} = useData();
const ptags = frontmatter?.value?.tags ?? [];
const tagLinkPattern = theme?.value?.tagLink;
const tags = computed(() => ptags.map(tag => {
// get tag details
const details = theme?.value?.tags?.[tag];
// set the link data
const data = {key: tag, name: tag, href: details?.link};
// if href is unset and we have a tagLinkPattern then use that
if (!data.href && tagLinkPattern && tagLinkPattern.includes(':tag-id')) data.href = tagLinkPattern.replace(':tag-id', encodeTag(tag));
// if href is unset and we have a tagLinkPattern then use that
if (!data.href && tagLinkPattern && tagLinkPattern.includes(':tag')) data.href = tagLinkPattern.replace(':tag', tag);
// just use the tagLink pattern
if (!data.href && tagLinkPattern) data.href = tagLinkPattern;
// return
return data;
}).filter(tag => tag.href !== undefined));
</script>
<style scoped>
.aside-tags-wrapper .ad-header {
margin: 0;
}
.aside-tags {
display: flex;
flex-direction: row;
flex-wrap: wrap;
padding: 12px 0;
gap: 5px;
.tag {
font-size: 10px;
}
}
</style>

View File

@@ -0,0 +1,137 @@
<template>
<div>
<div
:key="key"
class="collection-articles"
>
<div
v-for="(page, index) in pagination()"
:key="page.key"
:class="{'collection-article': true, grower: getGrower(index)}"
>
<Article
:page="page"
:size="props.size"
:more="props.more"
class="collection-page-article"
/>
</div>
</div>
<VPButton
v-if="pages.length > amount"
size="medium"
text="load more"
theme="alt"
class="load-more-button"
@click="adder"
>
load more
</VPButton>
</div>
</template>
<script setup>
import {defineAsyncComponent, onMounted, ref, watch} from 'vue';
import {VPButton} from 'vitepress/theme';
import Item from './VPLCollectionItem.vue';
const Article = defineAsyncComponent({
loader: async () => Item,
});
const props = defineProps({
items: {
type: Array,
required: true,
},
more: {
type: String,
default: 'readmore',
},
pager: {
type: Number,
default: 10,
},
size: {
type: String,
default: 'medium',
},
tags: {
type: Object,
default: () => ({}),
},
});
// Hardcoded pager value for now
const amount = ref(props.pager);
const key = ref(0);
// normalize data and sort
let pages = props.items
.map(item => Object.assign(item, {show: true, timestamp: item.date ? item.date : item.timestamp}))
.sort((a, b) => a.timestamp < b.timestamp ? 1 : -1);
const adder = () => amount.value += props.pager;
const getGrower = i => pagination()[i + 1] === undefined && (i + 1) % 2 !== 0;
const pagination = () => pages.slice(0, amount.value);
const filter = () => {
const tagList = Object.entries(props.tags).filter(pair => pair[1].selected === true).map(pair => pair[0]);
if (tagList.length === 0) return props.items;
return props.items.filter(item => Array.isArray(item.tags) && tagList.every(tag => item.tags.includes(tag)));
};
// recompute filter when tags change
watch(props.tags, () => {
pages = filter();
key.value++;
});
onMounted(() => {
pages = filter();
key.value++;
});
</script>
<style scoped>
.collection-articles {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
.collection-article {
max-width: 50%;
display: flex;
flex-grow: 1;
padding: 6px;
&.grower {
max-width: 100%;
}
}
}
.load-more-button {
margin: 24px 6px;
padding: 24px;
}
@media (max-width: 767px) {
.collection-articles {
.collection-article {
max-width: 100%;
}
}
}
@media (max-width: 420px) {
.collection-articles {
.collection-article {
padding: 6px 0;
}
}
}
</style>

View File

@@ -0,0 +1,72 @@
<template>
<div class="collection-page">
<slot />
</div>
</template>
<style scoped>
.collection-page {
padding: 0 16px 96px;
position: relative;
margin: 0 auto;
width: 100%;
}
@media (min-width: 768px) {
.collection-page {
padding-bottom: 128px;
}
}
:slotted(.collection-page-section + .collection-page-section),
:slotted(.collection-page-article + .collection-page-section) {
margin-top: 64px;
}
:slotted(.collection-page-article + .collection-page-article) {
margin-top: 24px;
}
@media (min-width: 768px) {
:slotted(.collection-page-title + .collection-page-section) {
margin-top: 16px;
}
:slotted(.collection-page-section + .collection-page-section),
:slotted(.collection-page-article + .collection-page-section) {
margin-top: 96px;
}
}
:slotted(.collection-page-article) {
padding: 0 24px;
}
@media (min-width: 768px) {
.collection-page {
padding: 0 32px 128px;
}
:slotted(.collection-page-article) {
padding: 0 48px;
}
}
@media (min-width: 960px) {
.collection-page {
padding: 0 64px 128px;
}
:slotted(.collection-page-article) {
padding: 0 64px;
}
}
@media (min-width: 1280px) {
.collection-page {
order: 1;
min-width: 640px;
max-width: 1200px;
margin: auto;
}
}
</style>

View File

@@ -0,0 +1,86 @@
<template>
<section class="collection-page-section">
<div class="title">
<div class="title-line" />
<h2
v-if="$slots.title"
class="title-text"
>
<slot name="title" />
</h2>
</div>
<p
v-if="$slots.lead"
class="lead"
>
<slot name="lead" />
</p>
<div
v-if="$slots.items"
class="items"
>
<slot name="items" />
</div>
</section>
</template>
<style scoped>
.collection-page-section {
padding: 0 32px;
}
@media (min-width: 768px) {
.collection-page-section {
padding: 0 48px;
}
}
@media (min-width: 960px) {
.collection-page-section {
padding: 0 64px;
}
}
.title {
position: relative;
margin: 0 auto;
max-width: 1152px;
text-align: center;
color: var(--vp-c-text-2);
}
.title-line {
position: absolute;
top: 16px;
left: 0;
width: 100%;
height: 1px;
background-color: var(--vp-c-divider);
}
.title-text {
position: relative;
display: inline-block;
padding: 0 24px;
letter-spacing: 0;
line-height: 32px;
font-size: 20px;
font-weight: 500;
background-color: var(--vp-c-bg);
}
.lead {
margin: 0 auto;
max-width: 480px;
padding-top: 12px;
text-align: center;
line-height: 24px;
font-size: 16px;
font-weight: 500;
color: var(--vp-c-text-2);
}
.items {
padding-top: 40px;
}
</style>

View File

@@ -0,0 +1,52 @@
<template>
<div class="collection-page-tags">
<Tag
v-for="(tag, name) in tags"
:key="name"
:type="tag.selected ? 'selected' : 'info'"
:text="name"
v-bind="tag"
@click="toggle(name)"
/>
</div>
</template>
<script setup>
import {onMounted} from 'vue';
import {useRoute} from 'vitepress';
import encodeTag from '../utils/encode-tag.js';
import Tag from './VPLCollectionTag.vue';
const tags = defineModel();
const toggle = tag => {
tags.value[tag].selected = !tags.value[tag].selected;
};
onMounted(() => {
const route = useRoute();
const params = route.tags ?? [];
for (const [tag] of Object.entries(tags.value)) {
tags.value[tag].selected = params.includes(tag) || params.includes(encodeTag(tag));
}
});
</script>
<style scoped>
.collection-page-tags {
display: flex;
flex-wrap: wrap;
gap: 5px;
justify-content: center;
align-items: center;
padding: 12px 0;
}
@media (max-width: 960px) {
.collection-page-tags {
padding: 12px 24px;
}
}
</style>

View File

@@ -0,0 +1,69 @@
<template>
<div class="collection-page-title">
<h1
v-if="$slots.title"
class="title"
>
<slot name="title" />
</h1>
<p
v-if="$slots.lead"
class="lead"
>
<slot name="lead" />
</p>
</div>
</template>
<style scoped>
.collection-page-title {
padding: 48px 32px;
text-align: center;
}
@media (min-width: 768px) {
.collection-page-title {
padding: 64px 48px 48px;
}
}
@media (min-width: 960px) {
.collection-page-title {
padding: 80px 64px 48px;
}
}
.title {
letter-spacing: 0;
line-height: 44px;
font-size: 36px;
font-weight: 500;
}
@media (min-width: 768px) {
.title {
letter-spacing: -0.5px;
line-height: 56px;
font-size: 48px;
}
}
.lead {
margin: 0 auto;
max-width: 512px;
padding-top: 12px;
line-height: 24px;
font-size: 16px;
font-weight: 500;
color: var(--vp-c-text-2);
}
@media (min-width: 768px) {
.lead {
max-width: 592px;
letter-spacing: 0.15px;
line-height: 28px;
font-size: 20px;
}
}
</style>

View File

@@ -0,0 +1,78 @@
<template>
<Badge
:class="`tag ${props.tagClass}`"
:style="props.type !== 'selected' ? styles : {}"
:type="props.type"
>
<span
v-if="icon"
class="icon"
v-html="icon"
/>
{{ props.text }}
</Badge>
</template>
<script setup>
import {computed} from 'vue';
import {useData} from 'vitepress';
const {theme} = useData();
const {tags} = theme.value;
const props = defineProps({
color: {
type: String,
default: 'none',
},
icon: {
type: String,
default: undefined,
},
styles: {
type: Object,
default: () => ({}),
},
tagClass: {
type: String,
default: '',
},
text: {
type: String,
required: true,
},
type: {
type: String,
default: 'info',
},
});
const details = Object.assign({color: props.color, styles: props.styles}, tags[props.text] ?? {});
const styles = computed(() => Object.assign({
'background-color': details.color,
'border-color': details.color,
}, details.styles));
const icon = computed(() => props.icon ?? details.icon ?? false);
</script>
<style scoped>
.tag {
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: space-between;
gap: 4px;
font-size: 14px;
.icon {
width: 16px;
}
}
.VPBadge.selected {
border-color: var(--vpl-badge-selected-border);
color: var(--vpl-badge-selected-text);
background-color: var(--vpl-badge-selected-bg);
}
</style>

View File

@@ -0,0 +1,290 @@
<template>
<footer
v-if="showFooter"
class="VPDocFooter"
>
<slot name="doc-footer-before" />
<div class="footer-box">
<div
v-if="hasBackLink"
class="back-link"
>
<Link :href="backLink.link">
{{ backLink.text ?? '<- Back' }}
</Link>
</div>
<div
v-if="hasContributors"
class="contributors"
>
<div class="contributors-flex">
<Contributor
v-for="contributor in contributors"
:key="contributor.key"
size="icon"
:member="contributor"
/>
</div>
</div>
<div class="empty" />
<div
v-if="hasEditLink || hasLastUpdated"
class="edit-info"
>
<div
v-if="hasEditLink"
class="edit-link"
>
<Link
class="edit-link-button"
:href="editLink?.url ?? editLink"
:no-icon="true"
>
<VPIconEdit
class="edit-link-icon"
aria-label="edit icon"
/>
{{ editLink?.text ?? theme?.value?.editLink?.text ?? 'Edit this page' }}
</Link>
</div>
<div
v-if="hasLastUpdated"
class="last-updated"
>
<DocFooterLastUpdated />
</div>
</div>
</div>
<nav
v-if="control.prev?.link || control.next?.link"
class="prev-next"
>
<div class="pager">
<Link
v-if="control.prev?.link"
class="pager-link prev"
:href="control.prev.link"
>
<span
class="desc"
v-html="theme.docFooter?.prev || 'Previous page'"
/>
<span
class="title"
v-html="control.prev.text"
/>
</Link>
</div>
<div class="pager">
<Link
v-if="control.next?.link"
class="pager-link next"
:href="control.next.link"
>
<span
class="desc"
v-html="theme.docFooter?.next || 'Next page'"
/>
<span
class="title"
v-html="control.next.text"
/>
</Link>
</div>
</nav>
</footer>
</template>
<script setup>
import {computed} from 'vue';
import {useData} from 'vitepress';
import {useEditLink} from 'vitepress/dist/client/theme-default/composables/edit-link';
import {usePrevNext} from 'vitepress/dist/client/theme-default/composables/prev-next';
import useCollection from '../client/use-collection.js';
import VPIconEdit from 'vitepress/dist/client/theme-default/components/icons/VPIconEdit.vue';
import Contributor from './VPLTeamMembersItem.vue';
import DocFooterLastUpdated from './VPLDocFooterLastUpdated.vue';
import Link from './VPLLink.vue';
const useBackLink = () => {
// if its a string then assume its the link
if (typeof frontmatter.value?.backLink === 'string') {
return computed(() => ({link: frontmatter.value.backLink}));
}
return computed(() => frontmatter.value.backLink);
};
const {theme, page, frontmatter} = useData();
const collection = computed(() => frontmatter.value?.collection ?? false);
const {prevnext} = useCollection(collection.value);
const oprevnext = usePrevNext();
const cprevnext = computed(() => {
const links = frontmatter.value?.collection ? prevnext : oprevnext;
return links.value;
});
const control = computed(() => ({
prev: frontmatter.value?.prev ? oprevnext.value.prev : cprevnext.value.prev,
next: frontmatter.value?.next ? oprevnext.value.next : cprevnext.value.next,
}));
const backLink = useBackLink();
const contributors = computed(() => frontmatter.value.contributors ?? page.value.contributors);
const editLink = frontmatter.value?.editLink ? computed(() => frontmatter.value?.editLink) : useEditLink();
const hasBackLink = computed(() => {
return frontmatter.value?.backLink?.link;
});
const hasContributors = computed(() => {
return contributors.value && contributors.value.length > 0;
});
const hasEditLink = computed(() => {
return theme.value.editLink && frontmatter.value.editLink !== false;
});
const hasLastUpdated = computed(() => {
return page.value.lastUpdated && frontmatter.value.lastUpdated !== false;
});
const showFooter = computed(() => {
return hasEditLink.value || hasLastUpdated.value || control.value.prev || control.value.next;
});
</script>
<style scoped>
.VPDocFooter {
margin-top: 64px;
}
.back-link {
display: flex;
align-items: flex-end;
border: 0;
line-height: 32px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-brand-1);
transition: color 0.25s;
}
.contributors {
max-width: 420px;
overflow: hidden;
max-height: 70px;
}
.contributors-flex {
height: 65px;
display: flex;
flex-direction: column;
flex-wrap: wrap;
justify-content: flex-end;
}
.desc {
display: block;
line-height: 20px;
font-size: 12px;
font-weight: 500;
color: var(--vp-c-text-2);
}
.edit-link-button {
display: flex;
align-items: center;
border: 0;
line-height: 32px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-brand-1);
transition: color 0.25s;
}
.edit-link-button:hover {
color: var(--vp-c-brand-2);
}
.edit-link-icon {
margin-right: 8px;
width: 14px;
height: 14px;
fill: currentColor;
}
.footer-box {
display: flex;
justify-content: space-between;
align-items: stretch;
}
.prev-next {
border-top: 1px solid var(--vp-c-divider);
padding-top: 24px;
display: grid;
grid-row-gap: 8px;
}
.pager-link {
display: block;
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 11px 16px 13px;
width: 100%;
height: 100%;
transition: border-color 0.25s;
}
.pager-link:hover {
border-color: var(--vp-c-brand-1);
}
.pager-link.next {
margin-left: auto;
text-align: right;
}
.title {
display: block;
line-height: 20px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-brand-1);
transition: color 0.25s;
}
@media (max-width: 767px) {
.contributors {
max-width: 300px;
}
}
@media (max-width: 640px) {
.contributors {
display: none;
}
}
@media (min-width: 640px) {
.edit-info {
display: flex;
justify-content: space-between;
align-items: center;
}
}
@media (min-width: 640px) {
.prev-next {
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 16px;
}
}
</style>

View File

@@ -0,0 +1,58 @@
<template>
<ClientOnly>
<p class="VPLastUpdated">
{{ theme.lastUpdated?.text || theme.lastUpdatedText || 'Last updated' }}
<time :datetime="isoDatetime">{{ datetime }}</time>
</p>
</ClientOnly>
</template>
<script setup>
import {ref, computed, watchEffect, onMounted} from 'vue';
import {useData} from 'vitepress';
import {format as timeago} from 'timeago.js';
const {theme, page, frontmatter, lang} = useData();
// handle time
const date = computed(() => new Date(frontmatter.value.lastUpdated ?? page.value.lastUpdated));
const isoDatetime = computed(() => date.value.toISOString());
const datetime = ref('');
// set time on mounted hook to avoid hydration mismatch due to
// potential differences in timezones of the server and clients
onMounted(() => {
watchEffect(() => {
// allow for timeago style
// @TODO: timeago has localization support but only en_US and zh_CN by default if we add support for this we need
// to do this in the component so the localization is relative to the users browser and not the build server
if (theme.value.lastUpdated?.formatOptions?.dateStyle === 'timeago') {
datetime.value = timeago(date.value.toLocaleDateString());
// otherwis the usual
} else {
datetime.value = new Intl.DateTimeFormat(
theme.value.lastUpdated?.formatOptions?.forceLocale ? lang.value : undefined,
theme.value.lastUpdated?.formatOptions ?? {dateStyle: 'short', timeStyle: 'short'},
).format(date.value);
}
});
});
</script>
<style scoped>
.VPLastUpdated {
line-height: 24px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-text-2);
}
@media (min-width: 640px) {
.VPLastUpdated {
line-height: 32px;
font-size: 14px;
font-weight: 500;
}
}
</style>

View File

@@ -0,0 +1,60 @@
<template>
<Page>
<template #doc-top>
<img src="https://i.pinimg.com/originals/4c/d9/ce/4cd9ce636c6d5f23688f0fda99cd81cf.gif">
</template>
<template #doc-before>
<img src="https://i.pinimg.com/originals/4c/d9/ce/4cd9ce636c6d5f23688f0fda99cd81cf.gif">
</template>
<template #doc-after>
<img src="https://i.pinimg.com/originals/4c/d9/ce/4cd9ce636c6d5f23688f0fda99cd81cf.gif">
</template>
<template #doc-bottom>
<img src="https://i.pinimg.com/originals/4c/d9/ce/4cd9ce636c6d5f23688f0fda99cd81cf.gif">
</template>
<template #aside-top>
aside-top
</template>
<template #aside-outline-before>
aside-outline-before
</template>
<template #aside-outline-after>
aside-outline-after
</template>
<template #aside-ads-before>
aside-ads-before
</template>
<template #aside-ads-after>
aside-ads-after
</template>
<template #aside-bottom>
aside-bottom
</template>
<div class="cat-container">
CATVIBES
<Content />
</div>
</Page>
</template>
<script setup>
import Page from 'vitepress/dist/client/theme-default/components/VPDoc.vue';
</script>
<style lang="scss" scoped>
.contributors {
float: left;
max-width: 70%;
overflow: hidden;
max-height: 70px;
}
.contributors-flex {
height: 65px;
display: flex;
flex-direction: column;
flex-wrap: wrap;
justify-content: flex-end;
}
</style>

View File

@@ -0,0 +1,108 @@
<template>
<div
v-if="hasJobs"
class="jobs"
>
<span
v-if="props.title"
class="ad-header"
>
{{ props.title }}
</span>
<div
v-for="(job, index) in jobs"
:key="index"
class="job"
>
<a
:href="job.link"
target="_blank"
rel="noopener noreferrer"
>
<div class="job-image">
<img
:src="job.logo"
:alt="job.company"
>
</div>
<div class="job-info">
<div class="job-title">{{ job.title }}</div>
<div class="job-aux">
{{ job.company }} - {{ job.aux }}
</div>
</div>
</a>
</div>
</div>
</template>
<script setup>
import {computed} from 'vue';
import {useData} from 'vitepress';
const props = defineProps({
title: {
type: [String, Boolean],
default: 'Jobs',
},
});
const {theme, frontmatter} = useData();
const jobs = frontmatter.value.jobs ?? theme.value.jobs ?? [];
// Compute whether we end up with any jobs or not
const hasJobs = computed(() => jobs !== false && jobs.length > 0);
</script>
<style lang="scss" scoped>
.job {
background-color: var(--vp-carbon-ads-bg-color);
padding: 10px;
border-radius: var(--vpl-c-border-radius);
font-weight: 400;
margin-bottom: 10px;
a {
text-decoration: none;
display: flex;
align-items: center;
color: var(--vp-c-text-1);
font-weight: 700;
}
.job-image {
width: 34px;
margin-right: 5px;
text-decoration: none;
img {
max-height: 24px;
max-width: 24px;
}
}
.job-info {
width: 100%;
.job-title {
font-size: 14px;
}
.job-aux {
font-size: 10px;
letter-spacing: .3px;
color: var(--vp-c-brand-1);
font-weight: 400;
}
}
}
@media (max-width: 1500px) {
.rightbar {
.jobs {
display: none;
}
}
}
.read-mode {
.jobs {
display: none;
}
}
</style>

View File

@@ -0,0 +1,97 @@
<template>
<Layout :class="headerClass">
<template #layout-top>
<Alert
v-if="alert"
:key="alertKey"
:content="alert.content"
:closeable="alert.closeable"
:type="alert.type"
/>
</template>
<template #sidebar-nav-after>
<div
v-if="sidebarEnder !== false"
class="sidebar-end"
>
<VPSideBarItem
:depth="0"
:item="sidebarEnder"
/>
</div>
</template>
<template #doc-before>
<div
v-if="header !== ''"
class="collection-header"
>
<PostHeader v-if="header === 'post'" />
<CollectionHeader v-else />
</div>
</template>
<template #aside-ads-before>
<Tags :key="tagsKey" />
<Jobs :key="jobsKey" />
<Sponsors :key="sponsorsKey" />
</template>
<template #doc-footer-before>
<Tags
v-if="header === 'post'"
:key="tagsKey"
/>
<div
v-if="mailchimp"
class="newsletter-wrapper"
>
<MailChimp v-bind="mailchimp" />
</div>
</template>
</Layout>
</template>
<script setup>
import {useData} from 'vitepress';
import {computed, ref, watch} from 'vue';
import DefaultTheme from 'vitepress/theme';
import VPSideBarItem from 'vitepress/dist/client/theme-default/components/VPSidebarItem.vue';
import Alert from './VPLAlert.vue';
import CollectionHeader from './VPLCollectionHeader.vue';
import MailChimp from './VPLMailChimp.vue';
import PostHeader from './VPLPostHeader.vue';
import Tags from './VPLCollectionItemTags.vue';
const {Layout} = DefaultTheme;
let alertKey = ref(0);
let jobsKey = ref(0);
let sponsorsKey = ref(0);
let tagsKey = ref(0);
const {frontmatter, page, theme} = useData();
const alert = computed(() => frontmatter.value.alert ?? theme.value.alert ?? false);
const header = computed(() => frontmatter.value.collection || '');
const headerClass = computed(() => frontmatter.value.collection ? `collection-${frontmatter.value.collection}` : '');
const mailchimp = computed(() => frontmatter.value?.mailchimp?.action ? frontmatter.value.mailchimp : false);
const sidebarEnder = computed(() => theme.value.sidebarEnder ?? false);
watch(() => page.value.relativePath, () => {
alertKey = page.value.relativePath;
jobsKey = page.value.relativePath;
sponsorsKey = page.value.relativePath;
tagsKey = page.value.relativePath;
});
</script>
<style lang="scss" scoped>
.newsletter-wrapper {
border-top: 1px solid var(--vp-c-divider);
padding: 16px 0 ;
}
</style>

View File

@@ -0,0 +1,73 @@
<template>
<component
:is="tag"
class="VPLink"
:class="{
link: props.href,
'lando': true,
'vp-external-link-icon': isExternal,
'no-icon': noIcon
}"
:href="getLink(props.href)"
:target="target ?? (isExternal ? '_blank' : isFauxInternal ? '_self' : undefined)"
:rel="relation ?? (isExternal ? 'noreferrer' : undefined)"
>
<slot />
</component>
</template>
<script setup>
import {useData} from 'vitepress';
import {computed} from 'vue';
import {normalizeLink} from 'vitepress/dist/client/theme-default/support/utils.js';
import {default as checkIsFauxInternal} from '../utils/is-faux-internal';
import {default as normalizeMvb} from '../utils/normalize-mvblink';
import {default as normalizeRoot} from '../utils/normalize-rootlink';
const EXTERNAL_URL_RE = /^(?:[a-z]+:|\/\/)/i;
const {theme, site} = useData();
const {internalDomains} = theme.value;
const props = defineProps({
tag: {
type: String,
default: undefined,
},
href: {
type: String,
default: undefined,
},
noIcon: {
type: Boolean,
default: false,
},
target: {
type: String,
default: undefined,
},
rel: {
type: String,
default: undefined,
},
});
const relation = computed(() => {
if (props.rel === 'mvb') return 'alternate';
else if (props.rel === 'root' || props.rel === 'none') return undefined;
return props.rel;
});
const tag = computed(() => props.tag ?? (props.href ? 'a' : 'span'));
const target = computed(() => props.target ?? (props.rel === 'mvb' || props.rel === 'root' ? '_self' : undefined));
const isFauxInternal = computed(() => props.href && checkIsFauxInternal(props.href, internalDomains));
const isExternal = computed(() => !isFauxInternal.value && props.href && EXTERNAL_URL_RE.test(props.href));
const getLink = href => {
if (props.rel === 'mvb' && href) return normalizeMvb(href, site.value);
else if (props.rel === 'root' && href) return normalizeRoot(href, site.value);
return href ? normalizeLink(href) : undefined;
};
</script>

View File

@@ -0,0 +1,175 @@
<template>
<div class="newsletter post-subscribe">
<div class="newsletter__wrap">
<div class="newsletter__title">
<h3>{{ props.title }}</h3>
</div>
<div class="newsletter__content">
{{ props.byline }}
</div>
<div id="mc_embed_signup">
<form
id="mc-embedded-subscribe-form"
:action="props.action"
method="post"
name="mc-embedded-subscribe-form"
class="validate subscribe-form"
target="_blank"
novalidate
>
<input
id="mce-EMAIL"
v-model="email"
type="email"
placeholder="Email address"
name="EMAIL"
class="subscribe-input"
>
<div
id="mce-responses"
class="clear"
>
<div
id="mce-error-response"
class="response"
style="display:none"
/>
<div
id="mce-success-response"
class="response"
style="display:none"
/>
</div>
<div
style="position: absolute; left: -5000px;"
aria-hidden="true"
>
<input
type="text"
name="b_59874b4d6910fa65e724a4648_613837077f"
tabindex="-1"
value=""
>
</div>
<VPButton
size="big"
:text="props.button"
>
<input
id="mc-embedded-subscribe"
:class="{ disabled: !email }"
:disabled="!email"
type="submit"
name="subscribe"
value=""
>
</VPButton>
</form>
</div>
</div>
</div>
</template>
<script setup>
import {ref} from 'vue';
import {VPButton} from 'vitepress/theme';
const props = defineProps({
action: {
required: true,
type: String,
},
title: {
type: String,
default: 'Subscribe to the newsletter!',
},
byline: {
type: String,
default: null,
},
button: {
type: String,
default: 'Subscribe',
},
});
const email = ref(null);
</script>
<style lang="scss" scoped>
.newsletter {
text-align: center;
color: var(--vp-c-text-1);
margin: 16px 0;
}
.newsletter__wrap {
padding: 32px 32px;
border-radius: var(--vpl-c-border-radius);
box-sizing: border-box;
width: auto;
font-size: 14px;
input[type=email], input[type=text], textarea {
border: 0 solid #f8f8f8;
box-sizing: border-box;
height: 50px;
width: 100%;
padding: 1em;
&:focus {
outline: 1px solid var(--vp-c-brand-soft);
outline-offset: 2px;
}
}
}
.newsletter__title {
h3 {
margin: 0;
color: var(--vp-custom-block-brand-title);
font-weight: 600;
font-size: 18px;
text-transform: uppercase;
}
}
.newsletter__content {
margin: 16px 0;
line-height: 28px;
}
.post-subscribe {
.subscribe {
width: 100%;
padding: 0;
}
.subscribe-input {
background-color: var(--vp-c-bg);
font-size: inherit;
border: 1px solid var(--vp-c-bg-alt);
width: 100%;
padding: 0.6rem 1.2rem;
box-sizing: border-box;
border-radius: var(--vpl-c-border-radius);
outline: none;
height: auto;
margin: 1em 0;
}
.newsletter__wrap {
background-color: var(--vp-c-brand-soft);
}
@media (max-width: 767px) {
.post-subscribe .subscribe-form {
display: block;
}
}
}
@media (max-width: 420px) {
.newsletter {
padding-left: 0;
padding-right: 0;
border-radius: 0;
width: 100%;
}
.newsletter__wrap {
margin: 0.85rem -1.5rem;
border-radius: 0;
}
}
</style>

View File

@@ -0,0 +1,116 @@
<template>
<div :class="`VPMenuGroup ${getItemColumnsClass(props.columns)}`">
<p
v-if="props.text"
class="title"
>
{{ props.text }}
</p>
<div :class="{'VPMenuGroup-flex-wrapper': props.columns > 1}">
<template v-for="item in props.items">
<MenuLink
v-if="'link' in item"
:key="item.href"
:item="item"
/>
</template>
</div>
</div>
</template>
<script setup>
import MenuLink from './VPLMenuLink.vue';
const props = defineProps({
columns: {
type: Number,
default: 1,
},
items: {
type: Array,
default: () => ([]),
},
text: {
type: String,
default: undefined,
},
});
const getItemColumnsClass = columns => {
switch (columns) {
case 1:
return 'VPMenuGroup-columns-full';
case 2:
return 'VPMenuGroup-columns-half';
case 3:
return 'VPMenuGroup-columns-third';
case 4:
return 'VPMenuGroup-columns-quarter';
default:
return 'VPMenuGroup-colums-third';
};
};
</script>
<style scoped>
.VPMenuGroup {
margin: 12px -12px 0;
border-top: 1px solid var(--vp-c-divider);
padding: 12px 12px 0;
}
.VPMenuGroup .title {
color: var(--vp-c-text-3);
width: 100%;
}
.VPMenuGroup .VPMenuGroup-flex-wrapper {
width: 500px;
display: flex;
flex-wrap: wrap;
}
.VPMenuGroup-columns-full .VPMenuLink {
width: unset;
min-width: 100%;
}
.VPMenuGroup-columns-half .VPMenuLink {
min-width: 49%;
max-width: 50%;
}
.VPMenuGroup-columns-third .VPMenuLink {
min-width: 32%;
max-width: 33%;
}
.VPMenuGroup-columns-quarter .VPMenuLink {
min-width: 24%;
max-width: 25%;
}
.VPMenuGroup:first-child {
margin-top: 0;
border-top: 0;
padding-top: 0;
}
.VPMenuGroup + .VPMenuGroup {
margin-top: 12px;
border-top: 1px solid var(--vp-c-divider);
}
.title {
display: block;
padding: 0 12px;
line-height: 32px;
font-size: 14px;
font-weight: 600;
color: var(--vp-c-text-2);
white-space: nowrap;
transition: color 0.25s;
}
</style>

View File

@@ -0,0 +1,87 @@
<template>
<div class="VPMenuLink">
<Link
:class="{ active: isActive(page.relativePath, item.activeMatch || item.link, !!item.activeMatch) }"
:href="item.link"
:target="item.target"
:rel="item.rel"
>
{{ item.text }}
<Badge
v-if="hasAlert(item.alert) && isActiveAlert(item.alert)"
v-bind="getAlert(item.alert)"
/>
</Link>
</div>
</template>
<script setup>
import {toRefs} from 'vue';
import {useData} from 'vitepress';
import isActive from '../utils/is-active.js';
import Link from './VPLLink.vue';
const props = defineProps({
item: {
type: [Array, Object],
default: () => ([]),
},
});
const {item} = toRefs(props);
const {page} = useData();
const getAlert = alert => {
if (typeof alert === 'string') alert = {text: alert};
return {type: 'info', expires: 2000000000000, ...alert};
};
const hasAlert = alert => {
return (
alert
&& alert !== null
&& alert !== undefined
&& (typeof alert === 'string' || typeof alert == 'object')
);
};
const isActiveAlert = alert => {
const {expires} = getAlert(alert);
return new Date().getTime() < expires;
};
</script>
<style scoped>
.VPMenuGroup + .VPMenuLink {
margin: 12px -12px 0;
border-top: 1px solid var(--vp-c-divider);
padding: 12px 12px 0;
}
.VPMenuLink .VPBadge {
margin-top: 8px;
}
.link {
display: block;
border-radius: 6px;
padding: 0 12px;
line-height: 32px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-text-1);
white-space: nowrap;
transition: background-color 0.25s, color 0.25s;
}
.link:hover {
color: var(--vp-c-brand-1);
background-color: var(--vp-c-default-soft);
}
.link.active {
color: var(--vp-c-brand-1);
}
</style>

View File

@@ -0,0 +1,123 @@
<template>
<div class="maybe">
<span
v-if="treeHasNewAlerts"
class="alert-circle"
/>
<VPFlyout
:class="styles"
:button="item.text"
:items="item.items"
/>
</div>
</template>
<script setup>
import {computed, toRefs} from 'vue';
import {useData} from 'vitepress';
import isActive from '../utils/is-active.js';
import VPFlyout from 'vitepress/dist/client/theme-default/components/VPFlyout.vue';
const {page} = useData();
const props = defineProps({
item: {
type: [Array, Object],
default: () => ([]),
},
});
const {item} = toRefs(props);
const getAlert = alert => {
if (typeof alert === 'string') alert = {text: alert};
return {type: 'success', expires: 2000000000000, ...alert};
};
const isChildActive = navItem => {
if ('link' in navItem) {
return isActive(
page.value.relativePath,
props.item.activeMatch,
!!props.item.activeMatch,
);
} else {
return navItem.items.some(isChildActive);
}
};
const flattenTree = (data, collect = []) => {
// break up children and items
const {items, ...item} = data;
// collec the item
collect.push(item);
// if we have children we need to recurse and add
if (items && items.length > 0) {
items.map(child => {
collect.push(flattenTree(child));
});
};
// faltten and return
return collect.flat(Infinity);
};
const getClasses = item => {
const list = item.value.classes ?? item.value.class;
// if list is nully then
if (!list || list === null) return [];
// return
return Array.isArray(list) ? list : [list];
};
const hasAlert = item => {
const {alert} = item;
return (
alert
&& alert !== null
&& alert !== undefined
&& (typeof alert === 'string' || typeof alert == 'object')
);
};
const treeHasNewAlerts = computed(() => {
const items = flattenTree(item.value);
const activeAlerts = items
.filter(item => hasAlert(item))
.map(item => getAlert(item.alert))
.filter(alert => alert.type === 'new')
.filter(alert => alert && alert.expires > new Date().getTime());
return activeAlerts.length > 0;
});
const styles = computed(() => {
// get active status
const active = isActive(page.relativePath, item.activeMatch, !!item.activeMatch) || isChildActive(props.item);
// build class list
const classes = {active, VPNavBarMenuGroup: true, test: treeHasNewAlerts};
// handle custom classes
for (const style of getClasses(item)) classes[style] = true;
return classes;
});
</script>
<style scoped>
.maybe {
display: flex;
align-items: center;
}
.alert-circle {
height: 8px;
width: 8px;
background-color: var(--vp-c-brand-1);
border-radius: 50%;
margin-right: -7px;
margin-left: 8px;
}
</style>

View File

@@ -0,0 +1,119 @@
<template>
<div class="post-header">
<Icon
v-if="collection !== false"
:icon="icon"
:link="iconLink"
:title="collection"
/>
<span v-if="authors.length > 0">by</span>
<div
v-if="authors.length > 0"
class="post-avatars"
>
<Author
v-for="author in authors"
:key="author.name"
size="icon"
:member="author"
/>
</div>
<Link
v-for="(author, index) in authors"
:key="author.name"
:href="author.link"
no-icon
>
<span class="underline">{{ author.name }}</span><span class="separator">{{ getSeparator(index, authors.length) }}</span>
</Link>
<span v-if="hlocation">from</span>
<span
v-if="hlocation"
class="location"
>
{{ hlocation }}
</span>
on
<time
class="date"
:datetime="datetime"
>
{{ hdate }}
</time>
</div>
</template>
<script setup>
import {computed} from 'vue';
import {useData} from 'vitepress';
import Author from './VPLTeamMembersItem.vue';
import Icon from './VPLCollectionIcon.vue';
import Link from './VPLLink.vue';
const {frontmatter, page} = useData();
const authors = computed(() => frontmatter.value?.authors ?? false);
const collection = computed(() => frontmatter.value?.collection ?? false);
const datetime = computed(() => page.value?.datetime ?? false);
const icon = computed(() => page.value?.collection?.icon ?? false);
const iconLink = computed(() => page.value?.collection?.iconLink ?? false);
const getSeparator = (index, end = 0) => {
return index + 1 === end ? '' : ', ';
};
const hdate = computed(() => {
return new Date(frontmatter.value?.date ?? page.value?.lastUpdated ?? page.value?.timestamp).toLocaleDateString(undefined, {
year: 'numeric',
month: 'long',
day: 'numeric',
});
});
const hlocation = computed(() => {
return frontmatter.value?.location ?? authors?.[0]?.location ?? false;
});
</script>
<style lang="scss" scoped>
.post-header {
align-items: flex-start;
z-index: 1;
display: flex;
gap: 4px;
font-size: .75em;
margin-bottom: 24px;
a {
font-weight: 500;
color: color-mix(in srgb, var(--vp-c-brand-1) 90%, white);
.underline {
text-underline-offset: 2px;
text-decoration: underline;
}
.separator {
color: var(--vp-c-text-3);
text-decoration: none;
}
}
.location, .date {
font-weight: 700;
color: var(--vp-c-text-2);
}
.post-avatars {
display: flex;
justify-content: flex-end;
.VPTeamMembersItem.icon {
overflow: visible;
&:not(:first-child) {
margin-left: -14px;
}
}
}
}
</style>

View File

@@ -0,0 +1,213 @@
<template>
<div
v-if="hasSponsors"
class="sponsors"
>
<span
v-if="props.title"
class="ad-header"
>
{{ props.title }}
</span>
<div class="sponsors-wrapper">
<div
v-for="(sponsor, index) in sponsorList"
:key="index"
:class="sponsor.classes"
>
<div class="sponsor-inner">
<a
:href="sponsor.url"
target="_blank"
>
<div class="sponsor-image"><img
:src="sponsor.logo"
:alt="sponsor.name"
></div>
</a>
</div>
</div>
</div>
<div
v-if="props.text || props.link"
class="sponsor-footer"
>
<a
:href="props.link"
target="_blank"
>
<div class="sponsor sponsor-full">
<span class="sponsor-link">
{{ props.text }}
</span>
</div>
</a>
</div>
</div>
</template>
<script setup>
import yaml from 'js-yaml';
import {computed, onMounted, ref} from 'vue';
import {useData} from 'vitepress';
const extname = path => {
const file = path.split('/')[path.split('/').length - 1];
const fileparts = file.split('.');
return fileparts.length > 1 ? `.${fileparts[fileparts.length - 1]}` : undefined;
};
const {theme, frontmatter} = useData();
const sponsors = frontmatter.value.sponsors ?? theme.value.sponsors ?? [];
const props = defineProps({
text: {
type: [String, Boolean],
default: () => {
const {theme, frontmatter} = useData();
const sponsors = frontmatter.value.sponsors ?? theme.value.sponsors ?? [];
return sponsors.text ?? 'your logo?';
},
},
link: {
type: [String, Boolean],
default: () => {
const {theme, frontmatter} = useData();
const sponsors = frontmatter.value.sponsors ?? theme.value.sponsors ?? [];
return sponsors.link;
},
},
title: {
type: [String, Boolean],
default: 'SPONSORS',
},
});
// Set sponsor data to some reactive thing
const data = ref(sponsors.data ?? []);
// if data is a string/needs to be fetched then do that here
onMounted(async () => {
// if data is already an array then we good
if (Array.isArray(data.value)) return;
// otherwise it SHOULD be a url string
try {
const url = new URL(data.value);
const response = await fetch(url.href);
// allow special file extension handling
switch (extname(url.pathname)) {
case '.yaml':
case '.yml':
data.value = yaml.load(await response.text());
break;
default:
data.value = await response.json();
break;
};
} catch (error) {
console.error(`could not fetch and parse data from ${data.value}`);
console.error(error);
}
});
// Compute sponsor list
const sponsorList = computed(() => {
if (Array.isArray(data.value)) {
return data.value.map(sponsor => ({...sponsor, classes: `sponsor sponsor-${sponsor.type}`}));
} else {
return [];
}
});
// Compute whether we end up with any sponsors or not
const hasSponsors = computed(() => sponsors !== false && sponsors && sponsors.data && sponsors.data.length > 0);
</script>
<style lang="scss" scoped>
.sponsors-wrapper {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
}
.sponsor {
height: 50px;
width: 33%;
display: flex;
align-items: center;
justify-content: space-around;
margin-top: 2px;
cursor: pointer;
border-radius: var(--vpl-c-border-radius);
.sponsor-inner {
background-color: var(--vp-carbon-ads-bg-color);
width: 100%;
height: 100%;
margin-left: 1px;
margin-right: 1px;
display: flex;
align-items: center;
justify-content: space-around;
border-radius: var(--vpl-c-border-radius);
}
&.sponsor-half {
width: 50%;
}
&.sponsor-full {
width: 100%;
margin-bottom: 10px;
}
.sponsor-image {
display: flex;
justify-content: space-around;
align-items: center;
padding: 5px;
img {
max-height: 40px;
max-width: 80%;
}
}
}
.sponsor-footer {
margin-top: 10px;
.sponsor {
display: flex;
justify-content: space-around;
align-items: center;
width: auto;
background-color: var(--vp-carbon-ads-bg-color);
width: 100%;
height: 50px;
margin-left: 1px;
margin-right: 1px;
display: flex;
align-items: center;
justify-content: space-around;
.sponsor-link {
color: var(--vp-c-text-3);
display: block;
font-weight: 700;
font-size: 11px;
text-transform: uppercase;
letter-spacing: .4px;
}
}
}
@media (max-width: 1500px) {
.rightbar {
.sponsors {
display: none;
}
}
}
.read-mode {
.sponsors {
display: none;
}
}
</style>

View File

@@ -0,0 +1,225 @@
<template>
<VPTeamMembers
:size="size"
:members="members"
/>
</template>
<script setup>
import useTeam from '../client/use-team.js';
import {VPTeamMembers} from 'vitepress/theme';
const {members, size} = defineProps({
size: {
type: String,
default: 'medium',
},
members: {
type: Array,
default: () => {
return useTeam() ?? [];
},
},
});
</script>
<style scoped>
.VPTeamMembersItem {
display: flex;
flex-direction: column;
gap: 2px;
border-radius: 12px;
width: 100%;
height: 100%;
overflow: hidden;
}
.VPTeamMembersItem.icon {
display: flex;
flex-direction: column;
gap: 2px;
border-radius: 12px;
width: 30px;
height: 30px;
overflow: hidden;
}
.VPTeamMembersItem.icon .profile {
padding: 0px;
background-color: transparent;
}
.VPTeamMembersItem.icon .data {
display: none;
}
.VPTeamMembersItem.icon .avatar {
width: 24px;
height: 24px;
box-shadow: none;
}
.VPTeamMembersItem.small .profile {
padding: 32px;
}
.VPTeamMembersItem.small .data {
padding-top: 20px;
}
.VPTeamMembersItem.small .avatar {
width: 64px;
height: 64px;
}
.VPTeamMembersItem.small .name {
line-height: 24px;
font-size: 16px;
}
.VPTeamMembersItem.small .affiliation {
padding-top: 4px;
line-height: 20px;
font-size: 14px;
}
.VPTeamMembersItem.small .desc {
padding-top: 12px;
line-height: 20px;
font-size: 14px;
}
.VPTeamMembersItem.small .links {
margin: 0 -16px -20px;
padding: 10px 0 0;
}
.VPTeamMembersItem.medium .profile {
padding: 48px 32px;
}
.VPTeamMembersItem.medium .data {
padding-top: 24px;
text-align: center;
}
.VPTeamMembersItem.medium .avatar {
width: 96px;
height: 96px;
}
.VPTeamMembersItem.medium .name {
letter-spacing: 0.15px;
line-height: 28px;
font-size: 20px;
}
.VPTeamMembersItem.medium .affiliation {
padding-top: 4px;
font-size: 16px;
}
.VPTeamMembersItem.medium .desc {
padding-top: 16px;
max-width: 288px;
font-size: 16px;
}
.VPTeamMembersItem.medium .links {
margin: 0 -16px -12px;
padding: 16px 12px 0;
}
.profile {
flex-grow: 1;
background-color: var(--vp-c-bg-soft);
}
.data {
text-align: center;
}
.avatar {
position: relative;
flex-shrink: 0;
margin: 0 auto;
border-radius: 50%;
box-shadow: var(--vp-shadow-3);
}
.avatar-img {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
border-radius: 50%;
object-fit: cover;
}
.name {
margin: 0;
font-weight: 600;
}
.affiliation {
margin: 0;
font-weight: 500;
color: var(--vp-c-text-2);
}
.org.link {
color: var(--vp-c-text-2);
transition: color 0.25s;
}
.org.link:hover {
color: var(--vp-c-brand-1);
}
.desc {
margin: 0 auto;
}
.desc :deep(a) {
font-weight: 500;
color: var(--vp-c-brand-1);
text-decoration-style: dotted;
transition: color 0.25s;
}
.links {
display: flex;
justify-content: center;
height: 56px;
}
.sp-link {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
padding: 16px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-sponsor);
background-color: var(--vp-c-bg-soft);
transition: color 0.25s, background-color 0.25s;
}
.sp .sp-link.link:hover,
.sp .sp-link.link:focus {
outline: none;
color: var(--vp-c-white);
background-color: var(--vp-c-sponsor);
}
.sp-icon {
margin-right: 8px;
width: 16px;
height: 16px;
fill: currentColor;
}
</style>

View File

@@ -0,0 +1,392 @@
<template>
<article
class="VPTeamMembersItem"
:class="[size, maintainerClass]"
>
<div class="profile">
<div
v-if="(member.commits || member.maintainer) && size !== 'icon'"
class="top-hat"
>
<div class="maintainer-role">
{{ member.maintainer ? 'Maintainer' : '' }}
</div>
<div class="commits">
{{ member.commits ? member.commits : '' }}
</div>
</div>
<figure class="avatar">
<Link
:href="getLink(member)"
no-icon
>
<img
class="avatar-img"
:src="avatar"
:alt="`Picture of ${member.name}`"
:title="getAvatarTitle(member)"
>
</Link>
</figure>
<div class="data">
<div class="name">
{{ member.name }}
</div>
<p
v-if="member.title || member.org"
class="affiliation"
>
<span
v-if="member.title"
class="title"
>
{{ member.title }}
</span>
<span
v-if="member.title && member.org"
class="at"
>
@
</span>
<Link
v-if="member.org"
class="org"
:class="{ link: member.orgLink }"
:href="member.orgLink"
no-icon
>
{{ member.org }}
</Link>
</p>
<p
v-if="member.desc"
class="desc"
v-html="member.desc"
/>
<div
v-if="member.links"
class="links"
>
<VPSocialLinks :links="member.links" />
</div>
</div>
</div>
<div
v-if="member.sponsor"
class="sp"
>
<Link
class="sp-link"
:href="member.sponsor"
no-icon
>
<VPIconHeart class="sp-icon" /> Sponsor
</Link>
</div>
</article>
</template>
<script setup>
import {computed} from 'vue';
import VPIconHeart from 'vitepress/dist/client/theme-default/components/icons/VPIconHeart.vue';
import VPSocialLinks from 'vitepress/dist/client/theme-default/components/VPSocialLinks.vue';
import Link from './VPLLink.vue';
const {member, size} = defineProps({
size: {
type: String,
default: 'medium',
},
member: {
type: Object,
default: () => ({}),
},
});
// compute avatar url with correct size
const avatar = computed(() => {
const src = member.avatar ?? member.pic;
switch (size) {
case 'icon':
return `${src}?size=24`;
case 'small':
return `${src}?size=64`;
case 'medium':
return `${src}?size=120`;
case 'large':
return `${src}?size=256`;
default:
return src;
};
});
const maintainerClass = computed(() => member.maintainer ? 'maintainer' : '');
const getLink = member => {
if (member.link) return member.link;
else if (Array.isArray(member?.links) && member.links[0]) return member.links[0].link;
else if (member.email) return `mailto:${member.email}`;
};
const getAvatarTitle = member => {
let avatarTitle = `${member.name}`;
if (member.email) avatarTitle += ` <${member.email}>`;
if (member.commits) avatarTitle += ` - ${Number.parseInt(member.commits, 10)} commits`;
return avatarTitle;
};
</script>
<style scoped>
.VPTeamMembersItem {
display: flex;
flex-direction: column;
gap: 2px;
border-radius: 12px;
width: 100%;
height: 100%;
overflow: hidden;
}
.VPTeamMembersItem.icon {
display: flex;
flex-direction: column;
gap: 2px;
border-radius: 12px;
width: 30px;
height: 30px;
overflow: hidden;
}
.VPTeamMembersItem.icon .profile {
padding: 0px;
background-color: transparent;
}
.VPTeamMembersItem.icon .top-hat {
display: none;
}
.VPTeamMembersItem.icon .maintainer-role {
display: none;
}
.VPTeamMembersItem.icon .commits {
display: none;
}
.VPTeamMembersItem.icon .data {
display: none;
}
.VPTeamMembersItem.icon .avatar {
width: 24px;
height: 24px;
box-shadow: none;
}
.VPTeamMembersItem.icon .sp {
display: none;
}
.VPTeamMembersItem.small .profile {
padding: 32px;
}
.VPTeamMembersItem.small .data {
padding-top: 20px;
}
.VPTeamMembersItem.small .avatar {
width: 64px;
height: 64px;
}
.VPTeamMembersItem.small .name {
line-height: 24px;
font-size: 16px;
}
.VPTeamMembersItem.small .affiliation {
padding-top: 4px;
line-height: 20px;
font-size: 12px;
}
.VPTeamMembersItem.small .desc {
padding-top: 12px;
line-height: 20px;
font-size: 14px;
display: none;
}
.VPTeamMembersItem.small .links {
margin: 0 -16px -20px;
padding: 10px 0 0;
}
.VPTeamMembersItem.medium .profile {
padding: 48px 32px;
}
.VPTeamMembersItem.medium .data {
padding-top: 24px;
text-align: center;
}
.VPTeamMembersItem.medium .avatar {
width: 96px;
height: 96px;
}
.VPTeamMembersItem.medium .name {
letter-spacing: 0.15px;
line-height: 28px;
font-size: 20px;
}
.VPTeamMembersItem.medium .affiliation {
padding-top: 4px;
font-size: 14px;
}
.VPTeamMembersItem.medium .desc {
padding-top: 16px;
max-width: 288px;
font-size: 16px;
}
.VPTeamMembersItem.medium .links {
margin: 0 -16px -12px;
padding: 16px 12px 0;
}
.profile {
flex-grow: 1;
background-color: var(--vpl-c-bg-contributor);
}
.maintainer .profile {
background-color: var(--vpl-c-bg-maintainer);
}
.maintainer .sp-link {
background-color: var(--vpl-c-bg-maintainer);
}
.data {
text-align: center;
}
.avatar {
position: relative;
flex-shrink: 0;
margin: 0 auto;
border-radius: 50%;
box-shadow: var(--vp-shadow-3);
}
.avatar-img {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
border-radius: 50%;
object-fit: cover;
}
.name {
margin: 0;
font-weight: 600;
}
.affiliation {
margin: 0;
text-transform: uppercase;
font-weight: 700;
color: var(--vp-c-text-3);
}
.at {
color: var(--vp-c-text-2);
}
.top-hat {
position: relative;
top: -30px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.commits {
color: var(--vp-c-text-3);
position: relative;
right: -25px;
font-size: 10px
}
.maintainer-role {
color: var(--vp-c-text-3);
position: relative;
font-size: 10px;
left: -25px;
text-transform: uppercase;
font-weight: 700;
}
.org.link {
color: var(--vp-c-text-3);
transition: color 0.25s;
}
.org.link:hover {
color: var(--vp-c-brand-1);
}
.desc {
margin: 0 auto;
}
.desc :deep(a) {
font-weight: 500;
color: var(--vp-c-brand-1);
text-decoration-style: dotted;
transition: color 0.25s;
}
.links {
display: flex;
justify-content: center;
height: 56px;
}
.sp-link {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
padding: 16px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-sponsor);
background-color: var(--vp-c-bg-soft);
transition: color 0.25s, background-color 0.25s;
}
.sp .sp-link.link:hover,
.sp .sp-link.link:focus {
outline: none;
color: var(--vp-c-white);
background-color: var(--vp-c-sponsor);
}
.sp-icon {
margin-right: 8px;
width: 16px;
height: 16px;
fill: currentColor;
}
</style>

View File

@@ -0,0 +1,100 @@
<template>
<a
class="VPLink"
:class="{
'prerelease': props.prerelease,
link: props.version ?? props.text,
'vp-external-link-icon': props.target === '_blank',
'no-icon': props.noIcon
}"
:href="getLink(props.version ?? props.text)"
:target="props.target"
>
{{ props.version ?? props.text }}
<Badge
v-if="props.stable"
type="success"
text="STABLE"
vertical="middle"
/>
<Badge
v-if="props.edge"
type="warning"
text="EDGE"
vertical="middle"
/>
<Badge
v-if="props.dev"
type="tip"
text="DEV"
vertical="middle"
/>
</a>
</template>
<script setup>
import {default as normalizeMvb} from '../utils/normalize-mvblink';
import {useData} from 'vitepress';
const {site} = useData();
const props = defineProps({
dev: {
type: Boolean,
default: false,
},
edge: {
type: Boolean,
default: false,
},
noIcon: {
type: Boolean,
default: false,
},
prerelease: {
type: Boolean,
default: false,
},
stable: {
type: Boolean,
default: false,
},
target: {
type: String,
default: '_blank',
},
version: {
type: String,
default: undefined,
},
// DEPRECATED but kept for backwards compat
text: {
type: String,
default: undefined,
},
});
const getLink = version => {
if (props.dev === true) return normalizeMvb('/dev/', site.value);
return normalizeMvb(`/${version}/`, site.value);
};
</script>
<style lang="scss">
.version-link {
a {
color: var(--vp-c-green-3);
&:hover {
color: var(--vp-c-green-3);
}
}
a.prerelease {
color: var(--vp-c-yellow-3);
&:hover {
color: var(--vp-c-yellow-3);
}
}
}
</style>

View File

@@ -0,0 +1,57 @@
<template>
<div
v-if="url"
class="video-responsive"
>
<iframe
width="100%"
height="400"
:src="url"
frameborder="0"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
/>
</div>
</template>
<script setup>
import {computed} from 'vue';
const props = defineProps({
id: {
type: String,
required: true,
},
});
const url = computed(() => `https://www.youtube.com/embed/${props.id}`);
</script>
<style lang="scss">
.video-responsive {
margin-top: 1em;
overflow: hidden;
padding-bottom: 56.25%;
position: relative;
height: 0;
iframe {
left: 0;
top: 0;
height: 100%;
width: 100%;
position: absolute;
}
}
@media (max-width: 767px) {
.video-responsive {
padding-left: 0;
padding-right: 0;
border-radius: 0;
width: auto;
margin: 0.85rem -1.5rem;
border-radius: 0;
}
}
</style>

View File

@@ -0,0 +1,238 @@
// mods
import {existsSync} from 'node:fs';
import {dirname, resolve} from 'node:path';
import {fileURLToPath} from 'node:url';
import isEmpty from 'lodash-es/isEmpty.js';
import merge from 'lodash-es/merge.js';
import Debug from 'debug';
import {defineConfigWithTheme} from 'vitepress';
// utils
import {default as createContainer} from './utils/create-container.js';
import {default as getBaseUrl} from './utils/get-base-url.js';
import {default as getContributors} from './utils/get-contributors.js';
import {default as getGaHeaders} from './utils/get-ga-headers.js';
import {default as getHubspotHeaders} from './utils/get-hubspot-headers.js';
import {default as getTags} from './utils/get-tags.js';
import {default as normalizeMVB} from './utils/normalize-mvb.js';
import {default as parseLayouts} from './utils/parse-layouts.js';
import {default as traverseUp} from './utils/traverse-up.js';
// node/plugins
import {default as addContributors} from './node/add-contributors.js';
import {default as addLayoutsPlugin} from './vite/add-layout-components-plugin.js';
import {default as addMetadata} from './node/add-metadata.js';
import {default as augmentAuthors} from './node/augment-authors.js';
import {default as buildCollections} from './node/build-collections.js';
import {default as normalizeFrontmatter} from './node/normalize-frontmatter.js';
import {default as normalizeLegacyFrontmatter} from './node/normalize-legacy-frontmatter.js';
import {default as parseCollections} from './node/parse-collections.js';
import {default as generateFeeds} from './node/generate-feeds.js';
import {default as generateRobotsTxt} from './node/generate-robots.js';
import {default as linkOverridePlugin} from './markdown/link-override-plugin.js';
import {tabsMarkdownPlugin} from 'vitepress-plugin-tabs';
import {default as tabsMarkdownOverridePlugin} from './markdown/tabs-override-plugin.js';
import {default as urlLoader} from './vite/url-loader.js';
// vitepress patches
import {default as patchVPMenuColumnsPlugin} from './vite/patch-vp-menu-columns-plugin.js';
import {default as patchVPUseSidebarControl} from './vite/patch-vp-use-sidebar-control.js';
// configsets
import {default as baseConfig} from './config/defaults.js';
import {default as lando3BaseConfig} from './config/landov3.js';
import {default as lando4BaseConfig} from './config/landov4.js';
export async function defineConfig(userConfig = {}, defaults = {}) {
const debug = Debug('@lando/vpltheme'); // eslint-disable-line
// theme root
userConfig.themeRoot = dirname(fileURLToPath(import.meta.url));
// prefer landov4 if defaults not set
if (isEmpty(userConfig.defaults) && userConfig.landoDocs === 4) {
debug('no user defaults set, using lando v4 defaults');
defaults = lando4BaseConfig(userConfig);
// ditto but for lando v3
} else if (isEmpty(userConfig.defaults) && userConfig.landoDocs === 3) {
debug('no user defaults set, using lando v3 defaults');
defaults = lando3BaseConfig(userConfig);
// Same as above but for legacy things
} else if (isEmpty(userConfig.defaults) && (userConfig.landoDocs || userConfig.lando)) {
debug('no user defaults set, using lando v3 defaults');
defaults = lando3BaseConfig(userConfig);
// Otherwise if we are empty then just set to defaults
} else if (isEmpty(userConfig.defaults)) {
debug('no user defaults set, using theme defaults');
defaults = baseConfig(userConfig);
}
// merge config sources
const config = merge({}, defaults, userConfig);
// log
debug('incoming vitepress configuration %O', config);
// normalize mvb stuff as needed
if (config?.themeConfig.multiVersionBuild) {
config.themeConfig.multiVersionBuild = normalizeMVB({...config.themeConfig.multiVersionBuild, siteBase: config.base});
debug('normalized mvb config to %o', config.themeConfig.multiVersionBuild);
}
// get git root if its not defined
if (!config.gitRoot) {
const gitDir = traverseUp(['.git'], resolve(config.themeRoot, '..')).find(dir => existsSync(dir));
config.gitRoot = gitDir ? resolve(gitDir, '..') : config.themeRoot;
debug('automatically set gitRoot to %o', config.gitRoot);
}
// If we want to show the shared navbar then lets add it to the begining of the navbar
if (Array.isArray(config?.themeConfig?.sharedNav)) {
config.themeConfig.nav = config.themeConfig.sharedNav.concat(config.themeConfig.nav);
debug('prepended shared navbar to user specified navbar with %o', config.themeConfig.sharedNav);
}
// explode
const {buildEnd, markdown, themeConfig, transformPageData, sitemap, vite} = config;
// normalize id
if (typeof themeConfig.internalDomain === 'string') themeConfig.internalDomain = [themeConfig.internalDomain];
if (typeof themeConfig.internalDomains === 'string') themeConfig.internalDomains = [themeConfig.internalDomains];
themeConfig.internalDomains = [...themeConfig.internalDomain, ...themeConfig.internalDomains];
// normalize contribs
if (themeConfig.contributors === true) themeConfig.contributors = baseConfig.themeConfig.contributors;
// normalize layouts
if (Object.keys(themeConfig.layouts).length > 0) themeConfig.layouts = parseLayouts(themeConfig.layouts);
// normalize sitemap
if (!sitemap.hostname && themeConfig?.autometa?.canonicalUrl) sitemap.hostname = themeConfig.autometa.canonicalUrl;
// attempt to set a baseurl
if (!config.baseUrl) config.baseUrl = getBaseUrl() ?? config.basethemeConfig?.autometa?.canonicalUrl ?? sitemap.hostname;
// extract
const {containers, contributors, ga, hubspot, internalDomains, layouts} = themeConfig;
// debug here so it doesnt print like 10 times
for (const [name, opts] of Object.entries(containers)) {
debug('added custom markdown container %o with config %o', name, opts);
}
// vite aliases
vite.resolve.alias.push(...[
{find: /^.*\/VPAlgoliaSearchBox\.vue$/, replacement: fileURLToPath(new URL('./components/VPLAlgoliaSearchBox.vue', import.meta.url))},
{find: /^.*\/VPDocFooter\.vue$/, replacement: fileURLToPath(new URL('./components/VPLDocFooter.vue', import.meta.url))},
{find: /^.*\/VPLink\.vue$/, replacement: fileURLToPath(new URL('./components/VPLLink.vue', import.meta.url))},
{find: /^.*\/VPMenuGroup\.vue$/, replacement: fileURLToPath(new URL('./components/VPLMenuGroup.vue', import.meta.url))},
{find: /^.*\/VPNavBarMenuGroup\.vue$/, replacement: fileURLToPath(new URL('./components/VPLNavBarMenuGroup.vue', import.meta.url))},
{find: /^.*\/VPTeamMembersItem\.vue$/, replacement: fileURLToPath(new URL('./components/VPLTeamMembersItem.vue', import.meta.url))},
]);
// plguins
vite.plugins.push(...[
addLayoutsPlugin(layouts, {debug: debug.extend('vite-plugin')}),
patchVPMenuColumnsPlugin({debug: debug.extend('vitepress-patcher')}),
patchVPUseSidebarControl({debug: debug.extend('vitepress-patcher')}),
urlLoader({debug: debug.extend('url-loader')}),
]);
// deps
vite.optimizeDeps.exclude.push('fsevents', '@lando/vitepress-theme-default-plus');
// ssr
vite.ssr.noExternal.push('@lando/vitepress-theme-default-plus');
// debug
debug('added vite resolver config %O', vite.resolve);
debug('added vite plugins %O', vite.plugins);
debug('added vite optimizeDeps config %O', vite.optimizeDeps);
debug('added vite ssr config %O', vite.ssr);
// markdown plugins
markdown.config = md => {
// add custom markdown containers, including tabs
for (const [name, opts] of Object.entries(containers)) {
md.use(...createContainer(name, opts, md));
}
// add tabs plugin
md.use(tabsMarkdownPlugin);
// override the tabs container so we can inject styling
md.use(tabsMarkdownOverridePlugin, {debug: debug.extend('markdown-plugin')});
// override the link plugin so it can handle internal domains
md.use(linkOverridePlugin, {
target: '_blank',
rel: 'noreferrer',
...markdown.externalLinks,
},
config.base,
internalDomains,
debug.extend('markdown-plugin'),
);
};
// add google analytics
if (ga !== false && ga.id) {
config.head.push(...getGaHeaders(ga.id));
debug('added google analytics/gtm tracking with %o', ga);
}
// add hubspot
if (hubspot !== false && hubspot.id) {
config.head.push(...getHubspotHeaders(hubspot.id));
debug('added hubspot tracking with %o', hubspot);
}
// get full team info
const copts = {debug: debug.extend('get-contribs'), paths: []};
const team = contributors !== false ? await getContributors(config.gitRoot, contributors, copts) : [];
debug('discovered full team info %o', team);
// get full version alias information but let this fail
try {
const tags = await getTags(config.gitRoot, undefined, {debug: debug.extend('get-tags')});
themeConfig.versions = tags.aliases ?? {};
debug('discovered version aliases %o', config.versions);
} catch (error) {
debug('unable to get version alias information with error %o', error);
}
// build robots.txt and rssfeed
config.buildEnd = async siteConfig => {
// generate robots txt
await generateRobotsTxt(siteConfig, {debug: debug.extend('generate-robots')});
// generate rss feeds
await generateFeeds(siteConfig, {debug: debug.extend('generate-feeds')});
// run any user specified transformPageData if its a function
if (buildEnd && typeof buildEnd === 'function') {
await buildEnd(siteConfig, {debug: debug.extend('user-build-end')});
}
};
// augment pages with additional data
config.transformPageData = async (pageData, {siteConfig}) => {
// make sure siteConfig.collections exists and is populated
await buildCollections(siteConfig, {debug: debug.extend('build-collections')});
// normalize legacy frontmatter
await normalizeLegacyFrontmatter(pageData, {siteConfig, debug: debug.extend('page-data')});
// normalize frontmatter
await normalizeFrontmatter(pageData, {siteConfig, debug: debug.extend('page-data')});
// add contributor information
await addContributors(pageData, {siteConfig, debug: debug.extend('page-data')});
// add metadata information
await addMetadata(pageData, {siteConfig, debug: debug.extend('page-data')});
// parse collections
await parseCollections(pageData, {siteConfig, debug: debug.extend('page-data')});
// normalize authors
await augmentAuthors(pageData, {team, debug: debug.extend('page-data')});
// run any user specified transformPageData if its a function
if (transformPageData && typeof transformPageData === 'function') {
await transformPageData(pageData, {siteConfig, debug: debug.extend('user-transform-page-data')});
}
};
return defineConfigWithTheme(config);
}

View File

@@ -0,0 +1,118 @@
import {default as getBaseUrl} from '@lando/vitepress-theme-default-plus/get-base-url';
export default function({base}) {
return {
base: base ?? '/',
baseUrl: getBaseUrl(),
collections: {},
feeds: false,
lang: 'en-US',
markdown: {},
robots: {
allowAll: true,
policy: [],
policies: [],
},
sitemap: {
lastmodDateOnly: false,
transformItems: items => {
for (const item of items) {
item.url = `${base ?? '/'}${item.url}`;
item.priority = 0.5;
item.changefreq = 'daily';
}
return items;
},
},
themeConfig: {
alert: false,
autometa: false,
carbonAds: undefined,
collections: {
post: {
frontmatter: {
collection: 'post',
contributors: false,
backLink: {
text: '<- Back to blog',
link: '/blog',
},
aside: false,
sidebar: false,
prev: false,
next: false,
editLink: false,
},
icon: '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 0 1 .865-.501 48.172 48.172 0 0 0 3.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0 0 12 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018Z"/></svg>',
iconLink: '/blog',
patterns: ['blog/**/*.md'],
},
guide: {
frontmatter: {
collection: 'guide',
},
icon: '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" d="M4.26 10.147a60.438 60.438 0 0 0-.491 6.347A48.62 48.62 0 0 1 12 20.904a48.62 48.62 0 0 1 8.232-4.41 60.46 60.46 0 0 0-.491-6.347m-15.482 0a50.636 50.636 0 0 0-2.658-.813A59.906 59.906 0 0 1 12 3.493a59.903 59.903 0 0 1 10.399 5.84c-.896.248-1.783.52-2.658.814m-15.482 0A50.717 50.717 0 0 1 12 13.489a50.702 50.702 0 0 1 7.74-3.342M6.75 15a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm0 0v-3.675A55.378 55.378 0 0 1 12 8.443m-7.007 11.55A5.981 5.981 0 0 0 6.75 15.75v-1.5" /></svg>',
iconLink: '/guides',
patterns: ['guides/**/*.md'],
},
},
containers: {
'brand': {defaultTitle: 'BRAND'},
'box': {},
'box-blue': {},
'box-brand': {},
'box-green': {},
'box-red': {},
'box-yellow': {},
'caption': {},
'card': {},
'center': {},
'half': {},
'highlight': {},
'left': {},
'right': {},
'success': {defaultTitle: 'SUCCESS'},
'third': {},
'thumbnail': {},
},
contributors: {
merge: 'name',
debotify: true,
exclude: [],
include: [],
},
internalDomain: [],
internalDomains: [],
ga: false,
hubspot: false,
jobs: false,
lastUpdated: {
text: 'Updated',
formatOptions: {
dateStyle: 'timeago',
},
},
layouts: {},
multiVersionBuild: false,
nav: [],
sidebar: {},
sidebarEnder: false,
sponsors: false,
tags: {},
tagLink: undefined,
team: [],
},
vite: {
css: {
preprocessorOptions: {
sass: {api: 'modern-compiler'},
scss: {api: 'modern-compiler'},
},
},
optimizeDeps: {exclude: []},
plugins: [],
resolve: {alias: []},
ssr: {noExternal: []},
},
};
};

View File

@@ -0,0 +1,387 @@
import {default as isDevRelease} from '@lando/vitepress-theme-default-plus/is-dev-release';
import {default as getBaseUrl} from '@lando/vitepress-theme-default-plus/get-base-url';
import uniq from 'lodash-es/uniq.js';
export default function({
base,
landoPlugin,
themeConfig,
version,
baseUrl = getBaseUrl() ?? 'https://docs.lando.dev',
navrel = 'root',
} = {}) {
// if this is a lando plugin then reset the baseUrl
if (landoPlugin) baseUrl = getBaseUrl(landoPlugin);
// reset the base if its undefined
if (!base) base = landoPlugin ? `/plugins/${landoPlugin}/` : '/';
// reset baseUrl with dat base
baseUrl = `${baseUrl}${base}`;
// backwards compat with LANDO_MVB_VERSION
if (!process?.env?.VPL_MVB_VERSION && process?.env?.LANDO_MVB_VERSION) {
process.env.VPL_MVB_VERSION = process.env.LANDO_MVB_VERSION;
}
// allow version to imported from ENV which is nice for one-off dev builds
version = process?.env?.VPL_MVB_VERSION ? process.env.VPL_MVB_VERSION : `v${version}`;
// construct the rest
const text = ['core'].includes(landoPlugin) ? version : `${landoPlugin}@${version}`;
const repo = landoPlugin ? `https://github.com/lando/${landoPlugin}` : 'https://github.com/lando';
// if no sidebar ender and we have plugin/version then do it
if (!themeConfig.sidebarEnder && themeConfig.sidebarEnder !== false && landoPlugin && version) {
themeConfig.sidebarEnder = {
text,
collapsed: true,
items: [
{
text: 'Other Doc Versions',
items: [
{rel: 'mvb', text: 'stable', target: '_blank', link: '/stable/'},
{rel: 'mvb', text: 'edge', target: '_blank', link: '/edge/'},
{rel: 'mvb', text: '<strong>see all versions</strong>', link: '/'},
],
},
{text: 'Other Releases', link: `${repo}/releases`},
],
};
// add release notes
if (themeConfig.sidebarEnder && !isDevRelease(version)) {
themeConfig.sidebarEnder.items.splice(1, 0, {
text: 'Release Notes',
link: `${repo}/releases/tag/${version}`,
});
}
}
// combine internals
themeConfig.internalDomains = themeConfig.internalDomains ?? [];
// add the usual domains
themeConfig.internalDomains.push(
'http://localhost',
'https://localhost',
'http://docs.lando.dev',
'https://docs.lando.dev',
getBaseUrl(landoPlugin),
);
// if plugin then add netlify stuff
if (landoPlugin) {
themeConfig.internalDomains.push(`^https:\/\/lando-${landoPlugin}\.netlify\.app(\/.*)?$`);
themeConfig.internalDomains.push(`^https:\/\/[a-zA-Z0-9-]+--lando-${landoPlugin}\.netlify\.app(\/.*)?$`);
}
return {
base,
collections: {},
feed: {
patterns: ['*.md', '*/**/*.md'],
},
lang: 'en-US',
markdown: {},
robots: {
host: baseUrl,
sitemap: `${baseUrl}sitemap.xml`,
disallowAll: false,
allowAll: false,
policy: [],
policies: [{
userAgent: '*',
disallow: ['/v/'],
allow: '/',
}],
},
sitemap: {
hostname: getBaseUrl(landoPlugin) ?? 'https://docs.lando.dev/',
lastmodDateOnly: false,
transformItems: items => {
for (const item of items) {
item.url = `${base}${item.url}`;
item.priority = 0.5;
item.changefreq = 'daily';
}
return items;
},
},
themeConfig: {
alert: false,
autometa: {
canonicalUrl: getBaseUrl(landoPlugin) ?? 'https://docs.lando.dev/',
image: 'https://docs.lando.dev/images/icon.png',
x: '@devwithlando',
},
carbonAds: {
code: 'CE7DCKJU',
placement: 'landodev',
},
collections: {
post: {
frontmatter: {
collection: 'post',
contributors: false,
backLink: {
text: '<- Back to blog',
link: '/blog',
},
aside: false,
sidebar: false,
prev: false,
next: false,
editLink: false,
},
icon: '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 0 1 .865-.501 48.172 48.172 0 0 0 3.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0 0 12 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018Z"/></svg>',
iconLink: '/blog',
patterns: ['blog/**/*.md'],
},
guide: {
frontmatter: {
collection: 'guide',
},
icon: '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" d="M4.26 10.147a60.438 60.438 0 0 0-.491 6.347A48.62 48.62 0 0 1 12 20.904a48.62 48.62 0 0 1 8.232-4.41 60.46 60.46 0 0 0-.491-6.347m-15.482 0a50.636 50.636 0 0 0-2.658-.813A59.906 59.906 0 0 1 12 3.493a59.903 59.903 0 0 1 10.399 5.84c-.896.248-1.783.52-2.658.814m-15.482 0A50.717 50.717 0 0 1 12 13.489a50.702 50.702 0 0 1 7.74-3.342M6.75 15a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm0 0v-3.675A55.378 55.378 0 0 1 12 8.443m-7.007 11.55A5.981 5.981 0 0 0 6.75 15.75v-1.5" /></svg>',
iconLink: '/guides',
patterns: ['guides/**/*.md'],
},
},
containers: {
'brand': {defaultTitle: 'BRAND'},
'box': {},
'box-blue': {},
'box-brand': {},
'box-green': {},
'box-red': {},
'box-yellow': {},
'caption': {},
'card': {},
'center': {},
'half': {},
'highlight': {},
'left': {},
'right': {},
'success': {defaultTitle: 'SUCCESS'},
'third': {},
'thumbnail': {},
},
contributors: {
merge: 'name',
debotify: true,
include: [
{
name: 'Mike Pirog',
email: 'mike@lando.dev',
title: 'Co-founder',
org: 'lando.dev',
orgLink: 'https://lando.dev',
links: [
{icon: 'github', link: 'https://github.com/pirog'},
{icon: 'twitter', link: 'https://twitter.com/pirogcommamike'},
],
sponsor: 'https://lando.dev/sponsor',
maintainer: true,
mergeOnly: true,
},
{
name: 'John Ouelett',
email: 'john@thinktandem.io',
title: 'Robot From Future',
mergeOnly: true,
},
{
avatar: 'https://avatars.githubusercontent.com/u/1153738',
name: 'Alec Reynolds',
email: 'alec+git@lando.dev',
title: 'Co-founder',
org: 'lando.dev',
orgLink: 'https://lando.dev',
desc: 'A chill dude',
links: [
{icon: 'github', link: 'https://github.com/reynoldsalec'},
{icon: 'twitter', link: 'https://twitter.com/reynoldsalec'},
],
sponsor: 'https://lando.dev/sponsor',
maintainer: true,
mergeOnly: true,
},
],
},
editLink: {
pattern: `${repo}/edit/main/docs/:path`,
},
internalDomain: [],
internalDomains: uniq(themeConfig.internalDomains),
ga: {id: 'G-ZSK3T9FTQ9'},
hubspot: {id: '6478338'},
jobs: [
{
title: 'Lando Developer',
logo: 'https://docs.lando.dev/images/icon.svg',
link: 'https://docs.google.com/forms/d/e/1FAIpQLSc2vkesq59BblKo8ZX-R1hKTrHphh1kmsg4FgWV1WH5BKEjHQ/viewform',
company: 'Lando Alliance',
aux: 'DC, Remote',
},
],
lastUpdated: {
text: 'Updated',
formatOptions: {
dateStyle: 'timeago',
},
},
layouts: {},
logo: {src: '/images/icon.svg', width: 24, height: 24},
multiVersionBuild: {
base: '/v/',
build: 'stable',
cache: true,
match: 'v[0-9].*',
satisfies: '>=1.0.0',
ag: 'edge',
},
nav: [],
sidebar: {},
sidebarEnder: themeConfig.sidebarEnder ?? false,
search: {
provider: 'algolia',
options: {
appId: '9S3BH0SKWT',
apiKey: 'dbdd36d83bdc347eb485aa13f381527c',
indexName: 'lando',
},
},
sharedNav: sharedNav(navrel),
socialLinks: [
{
icon: 'github',
link: repo,
},
{
icon: 'x',
link: 'https://x.com/@devwithlando',
},
{
icon: 'youtube',
link: 'https://www.youtube.com/channel/UCl_QBNuGJNoo7yH-n18K7Kg',
},
{
icon: {
svg: '<svg class="shake" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="red" d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>',
},
link: 'https://lando.dev/sponsor',
},
],
sponsors: {
text: 'your logo?',
link: 'https://lando.dev/sponsor',
data: 'https://raw.githubusercontent.com/lando/lando/main/patriots.yaml',
all: 'https://raw.githubusercontent.com/lando/lando/main/sponsors.yaml',
},
tags: {},
tagLink: undefined,
team: [],
},
vite: {
css: {
preprocessorOptions: {
sass: {api: 'modern-compiler'},
scss: {api: 'modern-compiler'},
},
},
optimizeDeps: {exclude: []},
plugins: [],
resolve: {alias: []},
ssr: {noExternal: []},
},
};
};
function sharedNav(rel = navrel) {
return [
{
text: 'Getting Started',
link: `/getting-started/`,
activeMatch: '/contrib|/getting-started|/guides|/help|/install|/lando-101|/security|/support|/team|/troubleshooting',
rel,
},
{
text: 'CLI',
link: `/cli/`,
activeMatch: '/cli',
rel,
},
{
text: 'Config',
activeMatch: '/config/|/landofile',
items: [
{
text: 'Landofile',
columns: 3,
items: [
{text: 'Basics', link: `/landofile/index.html`, rel},
{text: 'Services', link: `/landofile/services.html`, rel},
{text: 'Recipes', link: `/landofile/recipes.html`, rel},
{text: 'Tooling', link: `/landofile/tooling.html`, rel},
{text: 'Proxy', link: `/landofile/proxy.html`, rel},
{text: 'Events', link: `/landofile/events.html`, rel},
],
},
{
text: 'Global Config',
columns: 3,
items: [
{text: 'Global', link: `/config/global.html`, rel},
{text: 'Environment', link: `/config/env.html`, rel},
{text: 'Experimental', link: `/config/experimental.html`, rel},
{text: 'Healthcheck', link: `/config/healthcheck.html`, rel},
{text: 'Orchestrator', link: `/config/orchestrator.html`, rel},
{text: 'Networking', link: `/config/networking.html`, rel},
{text: 'Performance', link: `/config/performance.html`, rel},
{text: 'Plugins', link: `/config/plugins.html`, rel},
{text: 'Releases', link: `/config/releases.html`, rel},
{text: 'Scanner', link: `/config/scanner.html`, rel},
{text: 'Security', link: `/config/security.html`, rel},
{text: 'SSH', link: `/config/ssh.html`, rel},
{text: 'Shared Files', link: `/config/files.html`, rel},
],
},
],
},
{
text: 'Services',
activeMatch: '/services',
items: [
{
text: 'API 3',
columns: 2,
items: [
{text: 'Lando', link: `/services/lando-3.html`, rel},
],
},
{
text: 'API 4',
columns: 2,
items: [
{
text: 'L-337 Service',
link: `/services/l337.html`,
rel,
alert: {
text: 'BETA!',
type: 'new',
},
},
],
},
],
},
{
text: 'Plugins',
activeMatch: '/plugins',
link: `/plugins/`,
rel,
},
];
};

View File

@@ -0,0 +1,337 @@
import {default as isDevRelease} from '@lando/vitepress-theme-default-plus/is-dev-release';
import {default as getBaseUrl} from '@lando/vitepress-theme-default-plus/get-base-url';
import uniq from 'lodash-es/uniq.js';
export default function({
base,
landoPlugin,
themeConfig,
version,
baseUrl = getBaseUrl() ?? 'https://docs.lando.dev',
navrel = 'root',
} = {}) {
// if this is a lando plugin then reset the baseUrl
if (landoPlugin) baseUrl = getBaseUrl(landoPlugin);
// reset the base if its undefined
if (!base) base = landoPlugin ? `/plugins/${landoPlugin}/` : '/';
// reset baseUrl with dat base
baseUrl = `${baseUrl}${base}`;
// backwards compat with LANDO_MVB_VERSION
if (!process?.env?.VPL_MVB_VERSION && process?.env?.LANDO_MVB_VERSION) {
process.env.VPL_MVB_VERSION = process.env.LANDO_MVB_VERSION;
}
// allow version to imported from ENV which is nice for one-off dev builds
version = process?.env?.VPL_MVB_VERSION ? process.env.VPL_MVB_VERSION : `v${version}`;
// construct the rest
const text = ['core'].includes(landoPlugin) ? version : `${landoPlugin}@${version}`;
const repo = landoPlugin ? `https://github.com/lando/${landoPlugin}` : 'https://github.com/lando';
// if no sidebar ender and we have plugin/version then do it
if (!themeConfig.sidebarEnder && themeConfig.sidebarEnder !== false && landoPlugin && version) {
themeConfig.sidebarEnder = {
text,
collapsed: true,
items: [
{
text: 'Other Doc Versions',
items: [
{rel: 'mvb', text: 'stable', target: '_blank', link: '/stable/'},
{rel: 'mvb', text: 'edge', target: '_blank', link: '/edge/'},
{rel: 'mvb', text: '<strong>see all versions</strong>', link: '/'},
],
},
{text: 'Other Releases', link: `${repo}/releases`},
],
};
// add release notes
if (themeConfig.sidebarEnder && !isDevRelease(version)) {
themeConfig.sidebarEnder.items.splice(1, 0, {
text: 'Release Notes',
link: `${repo}/releases/tag/${version}`,
});
}
}
// combine internals
themeConfig.internalDomains = themeConfig.internalDomains ?? [];
// add the usual domains
themeConfig.internalDomains.push(
'http://localhost',
'https://localhost',
'http://docs.lando.dev',
'https://docs.lando.dev',
getBaseUrl(landoPlugin),
);
// if plugin then add netlify stuff
if (landoPlugin) {
themeConfig.internalDomains.push(`^https:\/\/lando-${landoPlugin}\.netlify\.app(\/.*)?$`);
themeConfig.internalDomains.push(`^https:\/\/[a-zA-Z0-9-]+--lando-${landoPlugin}\.netlify\.app(\/.*)?$`);
}
return {
base,
collections: {},
feed: {
patterns: ['*.md', '*/**/*.md'],
},
lang: 'en-US',
markdown: {},
robots: {
host: baseUrl,
sitemap: `${baseUrl}sitemap.xml`,
disallowAll: false,
allowAll: false,
policy: [],
policies: [{
userAgent: '*',
disallow: ['/v/'],
allow: '/',
}],
},
sitemap: {
hostname: getBaseUrl(landoPlugin) ?? 'https://docs.lando.dev/',
lastmodDateOnly: false,
transformItems: items => {
for (const item of items) {
item.url = `${base}${item.url}`;
item.priority = 0.5;
item.changefreq = 'daily';
}
return items;
},
},
themeConfig: {
alert: false,
autometa: {
canonicalUrl: getBaseUrl(landoPlugin) ?? 'https://docs.lando.dev/',
image: 'https://docs.lando.dev/images/icon.png',
x: '@devwithlando',
},
carbonAds: {
code: 'CE7DCKJU',
placement: 'landodev',
},
collections: {
post: {
frontmatter: {
collection: 'post',
contributors: false,
backLink: {
text: '<- Back to blog',
link: '/blog',
},
aside: false,
sidebar: false,
prev: false,
next: false,
editLink: false,
},
icon: '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 0 1 .865-.501 48.172 48.172 0 0 0 3.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0 0 12 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018Z"/></svg>',
iconLink: '/blog',
patterns: ['blog/**/*.md'],
},
guide: {
frontmatter: {
collection: 'guide',
},
icon: '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" d="M4.26 10.147a60.438 60.438 0 0 0-.491 6.347A48.62 48.62 0 0 1 12 20.904a48.62 48.62 0 0 1 8.232-4.41 60.46 60.46 0 0 0-.491-6.347m-15.482 0a50.636 50.636 0 0 0-2.658-.813A59.906 59.906 0 0 1 12 3.493a59.903 59.903 0 0 1 10.399 5.84c-.896.248-1.783.52-2.658.814m-15.482 0A50.717 50.717 0 0 1 12 13.489a50.702 50.702 0 0 1 7.74-3.342M6.75 15a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm0 0v-3.675A55.378 55.378 0 0 1 12 8.443m-7.007 11.55A5.981 5.981 0 0 0 6.75 15.75v-1.5" /></svg>',
iconLink: '/guides',
patterns: ['guides/**/*.md'],
},
},
containers: {
'brand': {defaultTitle: 'BRAND'},
'box': {},
'box-blue': {},
'box-brand': {},
'box-green': {},
'box-red': {},
'box-yellow': {},
'caption': {},
'card': {},
'center': {},
'half': {},
'highlight': {},
'left': {},
'right': {},
'success': {defaultTitle: 'SUCCESS'},
'third': {},
'thumbnail': {},
},
contributors: {
merge: 'name',
debotify: true,
include: [
{
name: 'Mike Pirog',
email: 'mike@lando.dev',
title: 'Co-founder',
org: 'lando.dev',
orgLink: 'https://lando.dev',
links: [
{icon: 'github', link: 'https://github.com/pirog'},
{icon: 'twitter', link: 'https://twitter.com/pirogcommamike'},
],
sponsor: 'https://lando.dev/sponsor',
maintainer: true,
mergeOnly: true,
},
{
name: 'John Ouelett',
email: 'john@thinktandem.io',
title: 'Robot From Future',
mergeOnly: true,
},
{
avatar: 'https://avatars.githubusercontent.com/u/1153738',
name: 'Alec Reynolds',
email: 'alec+git@lando.dev',
title: 'Co-founder',
org: 'lando.dev',
orgLink: 'https://lando.dev',
desc: 'A chill dude',
links: [
{icon: 'github', link: 'https://github.com/reynoldsalec'},
{icon: 'twitter', link: 'https://twitter.com/reynoldsalec'},
],
sponsor: 'https://lando.dev/sponsor',
maintainer: true,
mergeOnly: true,
},
],
},
editLink: {
pattern: `${repo}/edit/main/docs/:path`,
},
internalDomain: [],
internalDomains: uniq(themeConfig.internalDomains),
ga: {id: 'G-ZSK3T9FTQ9'},
hubspot: {id: '6478338'},
jobs: [
{
title: 'Lando Developer',
logo: 'https://docs.lando.dev/images/icon.svg',
link: 'https://docs.google.com/forms/d/e/1FAIpQLSc2vkesq59BblKo8ZX-R1hKTrHphh1kmsg4FgWV1WH5BKEjHQ/viewform',
company: 'Lando Alliance',
aux: 'DC, Remote',
},
],
lastUpdated: {
text: 'Updated',
formatOptions: {
dateStyle: 'timeago',
},
},
layouts: {},
logo: {src: '/images/icon.svg', width: 24, height: 24},
multiVersionBuild: {
base: '/v/',
build: 'stable',
cache: true,
match: 'v[0-9].*',
satisfies: '>=1.0.0',
ag: 'edge',
},
nav: [],
sidebar: {},
sidebarEnder: themeConfig.sidebarEnder ?? false,
search: {
provider: 'algolia',
options: {
appId: '9S3BH0SKWT',
apiKey: 'd3db589efd595b115848fc6a654d3263',
indexName: 'lando',
},
},
sharedNav: sharedNav(navrel),
socialLinks: [
{
icon: 'github',
link: repo,
},
{
icon: 'x',
link: 'https://x.com/@devwithlando',
},
{
icon: 'youtube',
link: 'https://www.youtube.com/channel/UCl_QBNuGJNoo7yH-n18K7Kg',
},
{
icon: {
svg: '<svg class="shake" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="red" d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>',
},
link: 'https://lando.dev/sponsor',
},
],
sponsors: {
text: 'your logo?',
link: 'https://lando.dev/sponsor',
data: 'https://raw.githubusercontent.com/lando/lando/main/patriots.yaml',
all: 'https://raw.githubusercontent.com/lando/lando/main/sponsors.yaml',
},
tags: {},
tagLink: undefined,
team: [],
},
vite: {
css: {
preprocessorOptions: {
sass: {api: 'modern-compiler'},
scss: {api: 'modern-compiler'},
},
},
optimizeDeps: {exclude: []},
plugins: [],
resolve: {alias: []},
ssr: {noExternal: []},
},
};
};
function sharedNav(rel = navrel) {
return [
{
text: 'Core',
items: [
{
text: 'Landofile',
columns: 4,
items: [
{
text: 'Services',
link: `/landofile/services.html`,
rel,
},
],
},
{
text: 'Configuration',
columns: 3,
items: [],
},
{
text: 'Plugins',
columns: 3,
items: [],
},
{
text: 'Services',
columns: 2,
items: [
{text: 'L-337', link: `/services/l337.html`, rel},
],
},
],
},
];
};

View File

@@ -0,0 +1,114 @@
import Debug from 'debug';
import {URL} from 'url';
import {default as isFauxInternal} from '../utils/is-faux-internal.js';
const indexRE = /(^|.*\/)index.md(#?.*)$/i;
const EXTERNAL_URL_RE = /^(?:[a-z]+:|\/\/)/i;
function isExternal(path) {
return EXTERNAL_URL_RE.test(path);
}
function normalizeHref(hrefAttr, env) {
let url = hrefAttr[1];
const indexMatch = url.match(indexRE);
if (indexMatch) {
const [, path, hash] = indexMatch;
url = path + hash;
} else {
let cleanUrl = url.replace(/[?#].*$/, '');
// transform foo.md -> foo[.html]
if (cleanUrl.endsWith('.md')) {
cleanUrl = cleanUrl.replace(/\.md$/, env.cleanUrls ? '' : '.html');
}
// transform ./foo -> ./foo[.html]
if (
!env.cleanUrls &&
!cleanUrl.endsWith('.html') &&
!cleanUrl.endsWith('/')
) {
cleanUrl += '.html';
}
const parsed = new URL(url, 'http://a.com');
url = cleanUrl + parsed.search + parsed.hash;
}
// ensure leading . for relative paths
if (!url.startsWith('/') && !/^\.\//.test(url)) {
url = './' + url;
}
// export it for existence check
pushLink(url.replace(/\.html$/, ''), env);
// markdown-it encodes the uri
hrefAttr[1] = decodeURI(url);
}
function pushLink(link, env) {
const links = env.links || (env.links = []);
links.push(link);
}
export default function(md, externalAttrs, base, domains, debug = Debug('@lando/markdown-plugin')) { // eslint-disable-line
md.renderer.rules.link_open = (
tokens,
idx,
options,
env,
self,
) => {
const token = tokens[idx];
const hrefIndex = token.attrIndex('href');
if (hrefIndex >= 0) {
const hrefAttr = token.attrs[hrefIndex];
const url = hrefAttr[1];
if (isExternal(url)) {
// set external attributes unless faux internal
if (!isFauxInternal(url, domains)) {
Object.entries(externalAttrs).forEach(([key, val]) => {
token.attrSet(key, val);
});
// we need to for _self for fauxinternals
} else {
token.attrSet('target', '_self');
}
// catch localhost links as dead link
if (url.replace(EXTERNAL_URL_RE, '').startsWith('//localhost:')) {
pushLink(url, env);
}
hrefAttr[1] = url;
} else {
if (
// internal anchor links
!url.startsWith('#') &&
// mail/custom protocol links
new URL(url, 'http://a.com').protocol.startsWith('http') &&
// links to files (other than html/md)
!/\.(?!html|md)\w+($|\?)/i.test(url)
) {
normalizeHref(hrefAttr, env);
} else if (url.startsWith('#')) {
hrefAttr[1] = decodeURI(hrefAttr[1]);
}
// append base to internal (non-relative) urls
if (hrefAttr[1].startsWith('/')) {
hrefAttr[1] = `${base}${hrefAttr[1]}`.replace(/\/+/g, '/');
}
}
// encode vite-specific replace strings in case they appear in URLs
// this also excludes them from build-time replacements (which injects
// <wbr/> and will break URLs)
hrefAttr[1] = hrefAttr[1]
.replace(/\bimport\.meta/g, 'import%2Emeta')
.replace(/\bprocess\.env/g, 'process%2Eenv');
}
return self.renderToken(tokens, idx, options);
};
debug('added custom markdown %o rule with config %o', 'link_open', {base, domains});
};

View File

@@ -0,0 +1,27 @@
import container from 'markdown-it-container';
import Debug from 'debug';
const parseTabsParams = input => {
const match = input.match(/key:(\S+)/);
return {shareStateKey: match?.[1]};
};
export default function(md, {debug = Debug('@lando/markdown-plugin')}) { // eslint-disable-line
md.use(container, 'tabs', {
render(tokens, index, _options, env) {
const token = tokens[index];
const style = token.info.trim().slice(4).trim();
if (token.nesting === 1) {
const params = parseTabsParams(token.info);
const shareStateKeyProp = params.shareStateKey
? `sharedStateKey="${md.utils.escapeHtml(params.shareStateKey)}"`
: '';
return `<PluginTabs class="${style}" ${shareStateKeyProp}>\n`;
} else {
return `</PluginTabs>\n`;
}
},
});
debug('override custom markdown container %o with styling support', 'vitepress-plugin-tabs');
};

View File

@@ -0,0 +1,38 @@
[build]
base = "./"
publish = "docs/.vitepress/dist/"
command = "npx mvb docs"
[context.deploy-preview]
command = "npm run build"
# https://github.com/munter/netlify-plugin-checklinks#readme
[[context.deploy-preview.plugins]]
package = "netlify-plugin-checklinks"
[context.deploy-preview.plugins.inputs]
todoPatterns = [ "load", "CHANGELOG.html", "x.com", "twitter.com", "/v/" ]
skipPatterns = [ ".rss", ".gif", ".jpg", "--vitepress-theme-default-plus.netlify.app" ]
checkExternal = true
# Sets our asset optimization
[build.processing.css]
bundle = true
minify = true
[build.processing.js]
bundle = true
minify = true
[build.processing.html]
pretty_urls = false
[build.processing.images]
compress = true
# Caches our images for 1 year
[[headers]]
for = "/images/*"
[headers.values]
Cache-Control = "public, max-age=31536000"
# https://github.com/netlify/netlify-plugin-lighthouse#readme
[[plugins]]
package = "@netlify/plugin-lighthouse"
[plugins.inputs.audits]
output_path = "reports/lighthouse.html"

View File

@@ -0,0 +1,30 @@
import Debug from 'debug';
import {default as resolveGitPaths} from '../utils/resolve-git-paths.js';
import {default as getContributors} from '../utils/get-contributors.js';
export default async function(pageData, {
debug = Debug('@lando/add-contributors'), // eslint-disable-line
siteConfig,
} = {}) {
debug = debug.extend(`${pageData.relativePath}`);
// get path
const {frontmatter, relativePath} = pageData;
// compute git things
const gitDir = siteConfig?.userConfig?.gitRoot;
const gitPaths = resolveGitPaths(relativePath, siteConfig.srcDir.replace(`${gitDir}/`, ''), frontmatter['git-include']);
// get contributors
const contributors = frontmatter.contributors || siteConfig?.site?.themeConfig?.contributors || false;
// add contributors unless turned off
if (contributors !== false) {
try {
pageData.contributors = await getContributors(gitDir, contributors, {debug, paths: gitPaths});
debug('set contributors %o', pageData.contributors);
} catch (error) {
debug('could not get contributor information, considering this non-fatal but you should investigate and resolve');
console.error(error);
}
}
};

View File

@@ -0,0 +1,65 @@
import {resolve} from 'node:path';
import Debug from 'debug';
export default async function(pageData, {
debug = Debug('@lando/add-metadata'), // eslint-disable-line
siteConfig,
} = {}) {
debug = debug.extend(`${pageData.relativePath}`);
// get stuff
const {frontmatter, lastUpdated, relativePath} = pageData;
const {site} = siteConfig;
// make sure header info is at least an empty array
if (!Array.isArray(frontmatter.head)) frontmatter.head = [];
// retrieve metadata
const autometa = siteConfig?.site?.themeConfig?.autometa ?? false;
if (autometa !== false) {
const {canonicalUrl, image, twitter, x} = autometa;
const title = frontmatter.title ?? pageData.title ?? site.title;
const description = frontmatter.description ?? frontmatter.summary ?? site.description;
const i = frontmatter.image ?? image ?? site?.logo?.src;
const xandle = x ?? twitter;
const published = new Date(Number.isNaN(lastUpdated) || !lastUpdated ? Date.now() : lastUpdated);
// generics
frontmatter.head.push(
['meta', {name: 'twitter:card', content: 'summary'}],
['meta', {name: 'twitter:title', content: title}],
['meta', {name: 'twitter:description', content: description}],
['meta', {name: 'twitter:image', content: i}],
['meta', {name: 'twitter:image:alt', content: title}],
['meta', {property: 'og:type', content: 'article'}],
['meta', {property: 'og:title', content: title}],
['meta', {property: 'og:description', content: description}],
['meta', {property: 'og:site_name', content: site.title}],
['meta', {name: 'og:image', content: i}],
['meta', {name: 'og:image:alt', content: title}],
['meta', {property: 'article:published_time', content: published}],
['meta', {itemprop: 'name', content: title}],
['meta', {itemprop: 'description', content: description}],
);
debug('set metadata %o', {title, description, i, published});
// twitter/x
if (xandle) {
frontmatter.head.push(['meta', {name: 'twitter:site', content: xandle}]);
debug('set xandle to %o', xandle);
}
// canonical stuff
if (canonicalUrl) {
const pathname = relativePath.replace(/(^|\/)index\.md$/, '$1').replace(/\.md$/, site.cleanUrls ? '' : '.html');
const url = new URL(resolve(site.base, pathname), canonicalUrl);
const {href} = url;
frontmatter.head.unshift(
['meta', {name: 'twitter:url', content: href}],
['meta', {property: 'og:url', content: href}],
['link', {rel: 'canonical', href: href}],
);
debug('set canonical url to %o', href);
}
}
};

View File

@@ -0,0 +1,29 @@
import Debug from 'debug';
const getContributor = (id, contributors = []) => contributors.find(contributor => contributor.email === id)
?? contributors.find(contributor => contributor.name === id);
const getLink = author => {
if (author.link) return author.link;
else if (Array.isArray(author?.links) && author.links[0]) return author.links[0].link;
else if (author.email) return `mailto:${author.email}`;
};
export default async function(pageData, {
team,
debug = Debug('@lando/augment-authors'), // eslint-disable-line
} = {}) {
debug = debug.extend(`${pageData.relativePath}`);
const {frontmatter} = pageData;
// normalize and augment author info
if (Array.isArray(frontmatter.authors)) {
frontmatter.authors = frontmatter.authors
.map(author => typeof author === 'string' ? getContributor(author, team) : author)
.filter(author => author && author !== false && author !== null)
.map(author => ({...author, link: getLink(author)}));
}
// log
debug('augmented author information to %o', {authors: frontmatter.authors});
};

View File

@@ -0,0 +1,20 @@
import fg from 'fast-glob';
import Debug from 'debug';
export default async function(siteConfig, {debug = Debug('@lando/build-collections')} = {}) { // eslint-disable-line
// ensure siteConfig.collections is at least an empty object
if (!siteConfig.collections || typeof siteConfig.collections !== 'object') siteConfig.collections = {};
// before we start lets make sure we have a list of paths for each collection
// we do it like this to minimize running fastglob a bunch of times
for (const [collection, config] of Object.entries(siteConfig?.site?.themeConfig?.collections ?? {})) {
if (!Array.isArray(siteConfig.collections[collection])) {
siteConfig.collections[collection] = fg.globSync(config.patterns ?? [], {
dot: true,
cwd: siteConfig.srcDir,
onlyFiles: true,
});
debug('built collection %o with page listing %o', collection, siteConfig.collections[collection]);
}
}
};

View File

@@ -0,0 +1,94 @@
import {writeFileSync} from 'node:fs';
import {join} from 'node:path';
import {default as createContentLoader} from '../utils/create-content-loader.js';
import Debug from 'debug';
import {Feed} from 'feed';
const normalizeUrl = url => url.replace(/([^:]\/)\/+/g, '$1');
// helper to normalize things
const normalizeConfig = (config = {}, feed = 'feed') => {
// pluralize
if (!config.patterns && config.pattern) {
config.patterns = config.pattern;
delete config.pattern;
}
// arrayize
if (typeof config.patterns === 'string') config.patterns = [config.patterns];
// set filename if it isnt set
if (!config.file) config.file = `/${feed}.rss`;
// add the feed name just for the hell of it?
config.name = feed;
return config;
};
const sort = items => items.sort((a, b) => a.timestamp < b.timestamp ? 1 : -1);
export default async function(siteConfig, {debug = Debug('@lando/generate-feeds')} = {}) { // eslint-disable-line
const {userConfig, site, outDir} = siteConfig;
// get config from priority sources
const config = userConfig.feeds
|| userConfig?.themeConfig?.feeds
|| userConfig.feed
|| userConfig?.themeConfig?.feed;
// if feeds is false or undefined then just end it all right here
if (!config) return;
// if feeds has only one top level feed then bump it down
const feeds = (config.patterns || config.pattern) ? {feed: config} : config;
// loop through and generate feedzzz
await Promise.all(Object.entries(feeds).map(async ([name, config]) => {
config = normalizeConfig(config, name);
const {href} = new URL(site.base ?? '/', config.baseUrl ?? userConfig.baseUrl);
const feed = new Feed({
title: config.title ?? site.title ?? '',
description: config.description ?? config.description ?? '',
id: normalizeUrl(config.id ?? href),
link: normalizeUrl(config.link ?? href),
language: config.language ?? 'en',
image: config.image ?? '',
favicon: normalizeUrl(config.favicon ?? `${href}/favicon.ico`),
copyright: config.copyright ?? '',
});
// get items
const items = await createContentLoader(config.patterns, {excerpt: true, render: true, siteConfig}, {debug}).load();
// and loop theough them to add
for (const item of sort(items)) {
const {authors, timestamp, excerpt, html, summary, title, url} = item;
// initial payload
const data = {
title,
id: normalizeUrl(`${href}${url}`),
link: normalizeUrl(`${href}${url}`),
description: excerpt !== '' ? excerpt : summary,
content: html,
date: new Date(timestamp),
};
// add authors if we have them
if (authors && Array.isArray(authors)) data.authors = authors.map(({name, link}) => ({name, link}));
// SHE ALWAYS NEEDS TO FEED
feed.addItem(data);
}
// write feed
const dest = join(outDir, config.file);
writeFileSync(dest, feed.rss2());
debug('generated rss feed %o', dest);
}));
}

View File

@@ -0,0 +1,63 @@
import {writeFileSync} from 'node:fs';
import {resolve} from 'node:path';
import merge from 'lodash-es/merge.js';
import robotstxt from 'generate-robotstxt';
import Debug from 'debug';
const defaults = {
allowAll: false,
disallowAll: false,
policy: [],
policies: [],
file: 'robots.txt',
};
const disallowAllPolicy = {
userAgent: '*',
disallow: '/',
};
const allowAllPolicy = {
userAgent: '*',
disallow: '',
};
export default async function({userConfig, outDir}, {debug = Debug('@lando/generate-robots')} = {}) { // eslint-disable-line
// get robots config or defaults
const robots = merge({}, defaults, userConfig.robots || userConfig?.themeConfig?.robots);
// munge policies together
robots.policy = [...robots.policy, ...robots.policies].filter(policy => policy);
// if no polices and disallowAll=false then assume allowAll
if (Array.isArray(robots.policy) && robots.policy.length === 0 && !robots.disallowAll) robots.allowAll = true;
// robot vibes
const {allowAll, disallowAll, host, sitemap} = robots;
// if disallow all then thats all we need really
if (disallowAll) robots.policy = [disallowAllPolicy];
// ditto for allow all
else if (allowAll) robots.policy = [allowAllPolicy];
// build generate options
const options = {policy: robots.policy};
// add host
if (host) options.host = host;
// add sitemap
if (sitemap) options.sitemap = sitemap;
debug('resolved robots.txt config from %o to %o', robots, options);
// write file
try {
const dest = resolve(outDir, robots.file);
const content = await robotstxt(options);
writeFileSync(dest, content);
debug('generated %o with content \n%O', dest, content);
} catch (error) {
throw error;
}
};

View File

@@ -0,0 +1,63 @@
import {existsSync, lstatSync} from 'node:fs';
import {resolve} from 'node:path';
import sortBy from 'lodash-es/sortBy.js';
import uniq from 'lodash-es/uniq.js';
import Debug from 'debug';
import {default as getTimestamp} from '../utils/get-timestamp.js';
export default async function(pageData, {
siteConfig,
debug = Debug('@lando/normalize-frontmatter'), // eslint-disable-line
} = {}) {
debug = debug.extend(`${pageData.relativePath}`);
const {frontmatter, relativePath} = pageData;
// use lastUpdated for date if its there
if (!frontmatter.date && Number.isInteger(pageData.lastUpdated)) frontmatter.date = pageData.lastUpdated;
// if we still dont have a date then we need to discover with git
if (!frontmatter.date
&& existsSync(resolve(siteConfig.srcDir, relativePath))
&& lstatSync(resolve(siteConfig.srcDir, relativePath)).isFile()
) {
frontmatter.date = await getTimestamp(resolve(siteConfig.srcDir, relativePath), {debug});
}
// standardize some date info
const date = new Date(frontmatter.date);
pageData.timestamp = date.getTime();
pageData.datetime = date.toJSON();
// prefer authors over author
if (frontmatter.authors === undefined && frontmatter.author !== undefined) {
pageData.frontmatter.authors = frontmatter.author;
delete pageData.frontmatter.author;
}
// prefer authors be an array
if (pageData.frontmatter.authors && !Array.isArray(pageData.frontmatter.authors)) {
pageData.frontmatter.authors = [pageData.frontmatter.authors];
}
// do a final check to make sure authors is at least an empty array
if (!pageData.frontmatter.authors) pageData.frontmatter.authors = [];
// consolidate it all into an array at frontmatter.tags
if (!frontmatter.tags) pageData.frontmatter.tags = [];
if (frontmatter.tags && typeof frontmatter.tags === 'string') pageData.frontmatter.tags = [pageData.frontmatter.tags];
if (frontmatter.tag && typeof frontmatter.tag === 'string') pageData.frontmatter.tags.push(pageData.frontmatter.tag);
if (Array.isArray(pageData.frontmatter.tag)) {
pageData.frontmatter.tags = pageData.frontmatter.tags.concat(pageData.frontmatter.tag);
}
delete pageData.frontmatter.tag;
// make sure tags are unique
if (Array.isArray(pageData.frontmatter.tags)) pageData.frontmatter.tags = sortBy(uniq((pageData.frontmatter.tags)));
// log
debug('normalized date information to %o', {date: frontmatter.date, timestamp: pageData.timestamp, datetime: pageData.datetime});
debug('normalized author information to %o', {authors: pageData.frontmatter.authors});
debug('normalized tags information to %o', {tags: pageData.frontmatter.tags});
};

View File

@@ -0,0 +1,29 @@
import Debug from 'debug';
export default async function(pageData, {
siteConfig,
debug = Debug('@lando/normalize-legacy-frontmatter'), // eslint-disable-line
} = {}) {
debug = debug.extend(`${pageData.relativePath}`);
const {frontmatter} = pageData;
// map and remove legacy vuepress2 theme blog setting
if (!frontmatter.collection && frontmatter.blog === true) {
pageData.frontmatter.collection = 'post';
delete pageData.frontmatter.blog;
debug('mapped frontmatter.blog to frontmatter.collection === post');
// ditto for guide setting
} else if (!frontmatter.collection && frontmatter.guide === true) {
pageData.frontmatter.collection = 'guide';
delete pageData.frontmatter.guide;
debug('mapped frontmatter.guide to frontmatter.collection === guide');
}
// ditto for updated
if (!frontmatter.date && frontmatter?.updated?.timestamp) {
pageData.frontmatter.date = frontmatter.updated.timestamp;
delete pageData.frontmatter.updated.timestamp;
debug('mapped frontmatter.updated.timestamp to frontmatter.date');
}
};

View File

@@ -0,0 +1,38 @@
import merge from 'lodash-es/merge.js';
import Debug from 'debug';
export default async function(pageData, {
siteConfig,
debug = Debug('@lando/parse-collections'), // eslint-disable-line
} = {}) {
debug = debug.extend(`${pageData.relativePath}`);
// get stuff
const {site} = siteConfig;
const {themeConfig} = site;
const {contributors, frontmatter, relativePath} = pageData;
// loop through collections so we can assign default values
await Promise.all(Object.entries(themeConfig?.collections ?? {}).map(async ([collection, config]) => {
// get collection pages so we can do the match
const pages = siteConfig?.collections[collection] ?? [];
// if this is a match then do the collection stuff we need to do
if (pages.includes(relativePath) || frontmatter.collection === collection) {
// if no author is set then we should be able to set it with contrib info
if ((frontmatter.authors === undefined
|| (Array.isArray(frontmatter.authors) && frontmatter.authors.length === 0))
&& Array.isArray(contributors)) {
frontmatter.authors = contributors;
debug('set authors %o using contributors information', frontmatter.authors.map(author => author.name));
}
// merge over defaults and save config and call it a day
pageData.frontmatter = merge({}, config.frontmatter, frontmatter);
pageData.collection = config;
// log
debug('rebased on collection %o defaults %O', collection, config.frontmatter);
}
}));
};

View File

@@ -0,0 +1 @@
../esbuild/bin/esbuild

View File

@@ -0,0 +1 @@
../nanoid/bin/nanoid.js

View File

@@ -0,0 +1 @@
../vite/bin/vite.js

View File

@@ -0,0 +1 @@
../vitepress/bin/vitepress.js

View File

@@ -0,0 +1,17 @@
# @algolia/autocomplete-core
The [`autocomplete-core`](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-core/createAutocomplete) package is the foundation of Autocomplete. It exposes primitives to build an autocomplete experience.
You likely dont need to use this package directly unless youre building a [renderer](https://www.algolia.com/doc/ui-libraries/autocomplete/guides/creating-a-renderer).
## Installation
```sh
yarn add @algolia/autocomplete-core
# or
npm install @algolia/autocomplete-core
```
## Documentation
See [**Documentation**](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-core).

View File

@@ -0,0 +1,2 @@
import { AutocompleteOptions, BaseItem } from './types';
export declare function checkOptions<TItem extends BaseItem>(options: AutocompleteOptions<TItem>): void;

View File

@@ -0,0 +1,4 @@
import { warn } from '@algolia/autocomplete-shared';
export function checkOptions(options) {
process.env.NODE_ENV !== 'production' ? warn(!options.debug, 'The `debug` option is meant for development debugging and should not be used in production.') : void 0;
}

View File

@@ -0,0 +1,8 @@
import { AutocompleteApi, AutocompleteOptions as AutocompleteCoreOptions, BaseItem } from './types';
export interface AutocompleteOptionsWithMetadata<TItem extends BaseItem> extends AutocompleteCoreOptions<TItem> {
/**
* @internal
*/
__autocomplete_metadata?: Record<string, unknown>;
}
export declare function createAutocomplete<TItem extends BaseItem, TEvent = Event, TMouseEvent = MouseEvent, TKeyboardEvent = KeyboardEvent>(options: AutocompleteOptionsWithMetadata<TItem>): AutocompleteApi<TItem, TEvent, TMouseEvent, TKeyboardEvent>;

View File

@@ -0,0 +1,106 @@
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
import { createAlgoliaInsightsPlugin } from '@algolia/autocomplete-plugin-algolia-insights';
import { checkOptions } from './checkOptions';
import { createStore } from './createStore';
import { getAutocompleteSetters } from './getAutocompleteSetters';
import { getDefaultProps } from './getDefaultProps';
import { getPropGetters } from './getPropGetters';
import { getMetadata, injectMetadata } from './metadata';
import { onInput } from './onInput';
import { stateReducer } from './stateReducer';
export function createAutocomplete(options) {
checkOptions(options);
var subscribers = [];
var props = getDefaultProps(options, subscribers);
var store = createStore(stateReducer, props, onStoreStateChange);
var setters = getAutocompleteSetters({
store: store
});
var propGetters = getPropGetters(_objectSpread({
props: props,
refresh: refresh,
store: store,
navigator: props.navigator
}, setters));
function onStoreStateChange(_ref) {
var _state$context, _state$context$algoli;
var prevState = _ref.prevState,
state = _ref.state;
props.onStateChange(_objectSpread({
prevState: prevState,
state: state,
refresh: refresh,
navigator: props.navigator
}, setters));
if (!isAlgoliaInsightsPluginEnabled() && (_state$context = state.context) !== null && _state$context !== void 0 && (_state$context$algoli = _state$context.algoliaInsightsPlugin) !== null && _state$context$algoli !== void 0 && _state$context$algoli.__automaticInsights && props.insights !== false) {
var plugin = createAlgoliaInsightsPlugin({
__autocomplete_clickAnalytics: false
});
props.plugins.push(plugin);
subscribePlugins([plugin]);
}
}
function refresh() {
return onInput(_objectSpread({
event: new Event('input'),
nextState: {
isOpen: store.getState().isOpen
},
props: props,
navigator: props.navigator,
query: store.getState().query,
refresh: refresh,
store: store
}, setters));
}
function subscribePlugins(plugins) {
plugins.forEach(function (plugin) {
var _plugin$subscribe;
return (_plugin$subscribe = plugin.subscribe) === null || _plugin$subscribe === void 0 ? void 0 : _plugin$subscribe.call(plugin, _objectSpread(_objectSpread({}, setters), {}, {
navigator: props.navigator,
refresh: refresh,
onSelect: function onSelect(fn) {
subscribers.push({
onSelect: fn
});
},
onActive: function onActive(fn) {
subscribers.push({
onActive: fn
});
},
onResolve: function onResolve(fn) {
subscribers.push({
onResolve: fn
});
}
}));
});
}
function isAlgoliaInsightsPluginEnabled() {
return props.plugins.some(function (plugin) {
return plugin.name === 'aa.algoliaInsightsPlugin';
});
}
if (props.insights && !isAlgoliaInsightsPluginEnabled()) {
var insightsParams = typeof props.insights === 'boolean' ? {} : props.insights;
props.plugins.push(createAlgoliaInsightsPlugin(insightsParams));
}
subscribePlugins(props.plugins);
injectMetadata({
metadata: getMetadata({
plugins: props.plugins,
options: options
}),
environment: props.environment
});
return _objectSpread(_objectSpread({
refresh: refresh,
navigator: props.navigator
}, propGetters), setters);
}

View File

@@ -0,0 +1,7 @@
import { AutocompleteState, AutocompleteStore, BaseItem, InternalAutocompleteOptions, Reducer } from './types';
declare type OnStoreStateChange<TItem extends BaseItem> = ({ prevState, state, }: {
prevState: AutocompleteState<TItem>;
state: AutocompleteState<TItem>;
}) => void;
export declare function createStore<TItem extends BaseItem>(reducer: Reducer, props: InternalAutocompleteOptions<TItem>, onStoreStateChange: OnStoreStateChange<TItem>): AutocompleteStore<TItem>;
export {};

View File

@@ -0,0 +1,28 @@
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
import { createCancelablePromiseList } from './utils';
export function createStore(reducer, props, onStoreStateChange) {
var state = props.initialState;
return {
getState: function getState() {
return state;
},
dispatch: function dispatch(action, payload) {
var prevState = _objectSpread({}, state);
state = reducer(state, {
type: action,
props: props,
payload: payload
});
onStoreStateChange({
state: state,
prevState: prevState
});
},
pendingRequests: createCancelablePromiseList()
};
}

View File

@@ -0,0 +1,13 @@
import { AutocompleteCollection, AutocompleteStore, BaseItem } from './types';
interface GetAutocompleteSettersOptions<TItem extends BaseItem> {
store: AutocompleteStore<TItem>;
}
export declare function getAutocompleteSetters<TItem extends BaseItem>({ store, }: GetAutocompleteSettersOptions<TItem>): {
setActiveItemId: import("@algolia/autocomplete-shared/dist/esm/core/AutocompleteSetters").StateUpdater<number | null>;
setQuery: import("@algolia/autocomplete-shared/dist/esm/core/AutocompleteSetters").StateUpdater<string>;
setCollections: import("@algolia/autocomplete-shared/dist/esm/core/AutocompleteSetters").StateUpdater<(AutocompleteCollection<TItem> | import("@algolia/autocomplete-shared/dist/esm/core/AutocompleteCollection").AutocompleteCollectionItemsArray<TItem>)[]>;
setIsOpen: import("@algolia/autocomplete-shared/dist/esm/core/AutocompleteSetters").StateUpdater<boolean>;
setStatus: import("@algolia/autocomplete-shared/dist/esm/core/AutocompleteSetters").StateUpdater<"idle" | "loading" | "stalled" | "error">;
setContext: import("@algolia/autocomplete-shared/dist/esm/core/AutocompleteSetters").StateUpdater<import("@algolia/autocomplete-shared/dist/esm/core/AutocompleteContext").AutocompleteContext>;
};
export {};

View File

@@ -0,0 +1,48 @@
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
import { flatten } from '@algolia/autocomplete-shared';
export function getAutocompleteSetters(_ref) {
var store = _ref.store;
var setActiveItemId = function setActiveItemId(value) {
store.dispatch('setActiveItemId', value);
};
var setQuery = function setQuery(value) {
store.dispatch('setQuery', value);
};
var setCollections = function setCollections(rawValue) {
var baseItemId = 0;
var value = rawValue.map(function (collection) {
return _objectSpread(_objectSpread({}, collection), {}, {
// We flatten the stored items to support calling `getAlgoliaResults`
// from the source itself.
items: flatten(collection.items).map(function (item) {
return _objectSpread(_objectSpread({}, item), {}, {
__autocomplete_id: baseItemId++
});
})
});
});
store.dispatch('setCollections', value);
};
var setIsOpen = function setIsOpen(value) {
store.dispatch('setIsOpen', value);
};
var setStatus = function setStatus(value) {
store.dispatch('setStatus', value);
};
var setContext = function setContext(value) {
store.dispatch('setContext', value);
};
return {
setActiveItemId: setActiveItemId,
setQuery: setQuery,
setCollections: setCollections,
setIsOpen: setIsOpen,
setStatus: setStatus,
setContext: setContext
};
}

View File

@@ -0,0 +1,6 @@
import { AutocompleteState, BaseItem } from './types';
interface GetCompletionProps<TItem extends BaseItem> {
state: AutocompleteState<TItem>;
}
export declare function getCompletion<TItem extends BaseItem>({ state, }: GetCompletionProps<TItem>): string | null;
export {};

View File

@@ -0,0 +1,9 @@
import { getActiveItem } from './utils';
export function getCompletion(_ref) {
var _getActiveItem;
var state = _ref.state;
if (state.isOpen === false || state.activeItemId === null) {
return null;
}
return ((_getActiveItem = getActiveItem(state)) === null || _getActiveItem === void 0 ? void 0 : _getActiveItem.itemInputValue) || null;
}

View File

@@ -0,0 +1,2 @@
import { AutocompleteOptions, AutocompleteSubscribers, BaseItem, InternalAutocompleteOptions } from './types';
export declare function getDefaultProps<TItem extends BaseItem>(props: AutocompleteOptions<TItem>, pluginSubscribers: AutocompleteSubscribers<TItem>): InternalAutocompleteOptions<TItem>;

View File

@@ -0,0 +1,130 @@
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
import { getItemsCount, generateAutocompleteId, flatten } from '@algolia/autocomplete-shared';
import { getNormalizedSources } from './utils';
export function getDefaultProps(props, pluginSubscribers) {
var _props$id;
/* eslint-disable no-restricted-globals */
var environment = typeof window !== 'undefined' ? window : {};
/* eslint-enable no-restricted-globals */
var plugins = props.plugins || [];
return _objectSpread(_objectSpread({
debug: false,
openOnFocus: false,
enterKeyHint: undefined,
ignoreCompositionEvents: false,
placeholder: '',
autoFocus: false,
defaultActiveItemId: null,
stallThreshold: 300,
insights: undefined,
environment: environment,
shouldPanelOpen: function shouldPanelOpen(_ref) {
var state = _ref.state;
return getItemsCount(state) > 0;
},
reshape: function reshape(_ref2) {
var sources = _ref2.sources;
return sources;
}
}, props), {}, {
// Since `generateAutocompleteId` triggers a side effect (it increments
// an internal counter), we don't want to execute it if unnecessary.
id: (_props$id = props.id) !== null && _props$id !== void 0 ? _props$id : generateAutocompleteId(),
plugins: plugins,
// The following props need to be deeply defaulted.
initialState: _objectSpread({
activeItemId: null,
query: '',
completion: null,
collections: [],
isOpen: false,
status: 'idle',
context: {}
}, props.initialState),
onStateChange: function onStateChange(params) {
var _props$onStateChange;
(_props$onStateChange = props.onStateChange) === null || _props$onStateChange === void 0 ? void 0 : _props$onStateChange.call(props, params);
plugins.forEach(function (x) {
var _x$onStateChange;
return (_x$onStateChange = x.onStateChange) === null || _x$onStateChange === void 0 ? void 0 : _x$onStateChange.call(x, params);
});
},
onSubmit: function onSubmit(params) {
var _props$onSubmit;
(_props$onSubmit = props.onSubmit) === null || _props$onSubmit === void 0 ? void 0 : _props$onSubmit.call(props, params);
plugins.forEach(function (x) {
var _x$onSubmit;
return (_x$onSubmit = x.onSubmit) === null || _x$onSubmit === void 0 ? void 0 : _x$onSubmit.call(x, params);
});
},
onReset: function onReset(params) {
var _props$onReset;
(_props$onReset = props.onReset) === null || _props$onReset === void 0 ? void 0 : _props$onReset.call(props, params);
plugins.forEach(function (x) {
var _x$onReset;
return (_x$onReset = x.onReset) === null || _x$onReset === void 0 ? void 0 : _x$onReset.call(x, params);
});
},
getSources: function getSources(params) {
return Promise.all([].concat(_toConsumableArray(plugins.map(function (plugin) {
return plugin.getSources;
})), [props.getSources]).filter(Boolean).map(function (getSources) {
return getNormalizedSources(getSources, params);
})).then(function (nested) {
return flatten(nested);
}).then(function (sources) {
return sources.map(function (source) {
return _objectSpread(_objectSpread({}, source), {}, {
onSelect: function onSelect(params) {
source.onSelect(params);
pluginSubscribers.forEach(function (x) {
var _x$onSelect;
return (_x$onSelect = x.onSelect) === null || _x$onSelect === void 0 ? void 0 : _x$onSelect.call(x, params);
});
},
onActive: function onActive(params) {
source.onActive(params);
pluginSubscribers.forEach(function (x) {
var _x$onActive;
return (_x$onActive = x.onActive) === null || _x$onActive === void 0 ? void 0 : _x$onActive.call(x, params);
});
},
onResolve: function onResolve(params) {
source.onResolve(params);
pluginSubscribers.forEach(function (x) {
var _x$onResolve;
return (_x$onResolve = x.onResolve) === null || _x$onResolve === void 0 ? void 0 : _x$onResolve.call(x, params);
});
}
});
});
});
},
navigator: _objectSpread({
navigate: function navigate(_ref3) {
var itemUrl = _ref3.itemUrl;
environment.location.assign(itemUrl);
},
navigateNewTab: function navigateNewTab(_ref4) {
var itemUrl = _ref4.itemUrl;
var windowReference = environment.open(itemUrl, '_blank', 'noopener');
windowReference === null || windowReference === void 0 ? void 0 : windowReference.focus();
},
navigateNewWindow: function navigateNewWindow(_ref5) {
var itemUrl = _ref5.itemUrl;
environment.open(itemUrl, '_blank', 'noopener');
}
}, props.navigator)
});
}

View File

@@ -0,0 +1,16 @@
import { AutocompleteScopeApi, AutocompleteStore, BaseItem, GetEnvironmentProps, GetFormProps, GetInputProps, GetItemProps, GetLabelProps, GetListProps, GetPanelProps, GetRootProps, InternalAutocompleteOptions } from './types';
interface GetPropGettersOptions<TItem extends BaseItem> extends AutocompleteScopeApi<TItem> {
store: AutocompleteStore<TItem>;
props: InternalAutocompleteOptions<TItem>;
}
export declare function getPropGetters<TItem extends BaseItem, TEvent, TMouseEvent, TKeyboardEvent>({ props, refresh, store, ...setters }: GetPropGettersOptions<TItem>): {
getEnvironmentProps: GetEnvironmentProps;
getRootProps: GetRootProps;
getFormProps: GetFormProps<TEvent>;
getLabelProps: GetLabelProps;
getInputProps: GetInputProps<TEvent, TMouseEvent, TKeyboardEvent>;
getPanelProps: GetPanelProps<TMouseEvent>;
getListProps: GetListProps;
getItemProps: GetItemProps<any, TMouseEvent>;
};
export {};

View File

@@ -0,0 +1,335 @@
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
var _excluded = ["props", "refresh", "store"],
_excluded2 = ["inputElement", "formElement", "panelElement"],
_excluded3 = ["inputElement"],
_excluded4 = ["inputElement", "maxLength"],
_excluded5 = ["source"],
_excluded6 = ["item", "source"];
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
import { noop } from '@algolia/autocomplete-shared';
import { onInput } from './onInput';
import { onKeyDown as _onKeyDown } from './onKeyDown';
import { getActiveItem, getAutocompleteElementId, isOrContainsNode, isSamsung, getNativeEvent } from './utils';
export function getPropGetters(_ref) {
var props = _ref.props,
refresh = _ref.refresh,
store = _ref.store,
setters = _objectWithoutProperties(_ref, _excluded);
var getEnvironmentProps = function getEnvironmentProps(providedProps) {
var inputElement = providedProps.inputElement,
formElement = providedProps.formElement,
panelElement = providedProps.panelElement,
rest = _objectWithoutProperties(providedProps, _excluded2);
function onMouseDownOrTouchStart(event) {
// The `onTouchStart`/`onMouseDown` events shouldn't trigger the `blur`
// handler when it's not an interaction with Autocomplete.
// We detect it with the following heuristics:
// - the panel is closed AND there are no pending requests
// (no interaction with the autocomplete, no future state updates)
// - OR the touched target is the input element (should open the panel)
var isAutocompleteInteraction = store.getState().isOpen || !store.pendingRequests.isEmpty();
if (!isAutocompleteInteraction || event.target === inputElement) {
return;
}
// @TODO: support cases where there are multiple Autocomplete instances.
// Right now, a second instance makes this computation return false.
var isTargetWithinAutocomplete = [formElement, panelElement].some(function (contextNode) {
return isOrContainsNode(contextNode, event.target);
});
if (isTargetWithinAutocomplete === false) {
store.dispatch('blur', null);
// If requests are still pending when the user closes the panel, they
// could reopen the panel once they resolve.
// We want to prevent any subsequent query from reopening the panel
// because it would result in an unsolicited UI behavior.
if (!props.debug) {
store.pendingRequests.cancelAll();
}
}
}
return _objectSpread({
// We do not rely on the native `blur` event of the input to close the
// panel, but rather on a custom `touchstart`/`mousedown` event outside
// of the autocomplete elements.
// This ensures we don't mistakenly interpret interactions within the
// autocomplete (but outside of the input) as a signal to close the panel.
// For example, clicking reset button causes an input blur, but if
// `openOnFocus=true`, it shouldn't close the panel.
// On touch devices, scrolling results (`touchmove`) causes an input blur
// but shouldn't close the panel.
onTouchStart: onMouseDownOrTouchStart,
onMouseDown: onMouseDownOrTouchStart,
// When scrolling on touch devices (mobiles, tablets, etc.), we want to
// mimic the native platform behavior where the input is blurred to
// hide the virtual keyboard. This gives more vertical space to
// discover all the suggestions showing up in the panel.
onTouchMove: function onTouchMove(event) {
if (store.getState().isOpen === false || inputElement !== props.environment.document.activeElement || event.target === inputElement) {
return;
}
inputElement.blur();
}
}, rest);
};
var getRootProps = function getRootProps(rest) {
return _objectSpread({
role: 'combobox',
'aria-expanded': store.getState().isOpen,
'aria-haspopup': 'listbox',
'aria-controls': store.getState().isOpen ? store.getState().collections.map(function (_ref2) {
var source = _ref2.source;
return getAutocompleteElementId(props.id, 'list', source);
}).join(' ') : undefined,
'aria-labelledby': getAutocompleteElementId(props.id, 'label')
}, rest);
};
var getFormProps = function getFormProps(providedProps) {
var inputElement = providedProps.inputElement,
rest = _objectWithoutProperties(providedProps, _excluded3);
return _objectSpread({
action: '',
noValidate: true,
role: 'search',
onSubmit: function onSubmit(event) {
var _providedProps$inputE;
event.preventDefault();
props.onSubmit(_objectSpread({
event: event,
refresh: refresh,
state: store.getState()
}, setters));
store.dispatch('submit', null);
(_providedProps$inputE = providedProps.inputElement) === null || _providedProps$inputE === void 0 ? void 0 : _providedProps$inputE.blur();
},
onReset: function onReset(event) {
var _providedProps$inputE2;
event.preventDefault();
props.onReset(_objectSpread({
event: event,
refresh: refresh,
state: store.getState()
}, setters));
store.dispatch('reset', null);
(_providedProps$inputE2 = providedProps.inputElement) === null || _providedProps$inputE2 === void 0 ? void 0 : _providedProps$inputE2.focus();
}
}, rest);
};
var getInputProps = function getInputProps(providedProps) {
var _props$environment$na;
function onFocus(event) {
// We want to trigger a query when `openOnFocus` is true
// because the panel should open with the current query.
if (props.openOnFocus || Boolean(store.getState().query)) {
onInput(_objectSpread({
event: event,
props: props,
query: store.getState().completion || store.getState().query,
refresh: refresh,
store: store
}, setters));
}
store.dispatch('focus', null);
}
var _ref3 = providedProps || {},
inputElement = _ref3.inputElement,
_ref3$maxLength = _ref3.maxLength,
maxLength = _ref3$maxLength === void 0 ? 512 : _ref3$maxLength,
rest = _objectWithoutProperties(_ref3, _excluded4);
var activeItem = getActiveItem(store.getState());
var userAgent = ((_props$environment$na = props.environment.navigator) === null || _props$environment$na === void 0 ? void 0 : _props$environment$na.userAgent) || '';
var shouldFallbackKeyHint = isSamsung(userAgent);
var enterKeyHint = props.enterKeyHint || (activeItem !== null && activeItem !== void 0 && activeItem.itemUrl && !shouldFallbackKeyHint ? 'go' : 'search');
return _objectSpread({
'aria-autocomplete': 'both',
'aria-activedescendant': store.getState().isOpen && store.getState().activeItemId !== null ? getAutocompleteElementId(props.id, "item-".concat(store.getState().activeItemId), activeItem === null || activeItem === void 0 ? void 0 : activeItem.source) : undefined,
'aria-controls': store.getState().isOpen ? store.getState().collections.map(function (_ref4) {
var source = _ref4.source;
return getAutocompleteElementId(props.id, 'list', source);
}).join(' ') : undefined,
'aria-labelledby': getAutocompleteElementId(props.id, 'label'),
value: store.getState().completion || store.getState().query,
id: getAutocompleteElementId(props.id, 'input'),
autoComplete: 'off',
autoCorrect: 'off',
autoCapitalize: 'off',
enterKeyHint: enterKeyHint,
spellCheck: 'false',
autoFocus: props.autoFocus,
placeholder: props.placeholder,
maxLength: maxLength,
type: 'search',
onChange: function onChange(event) {
var value = event.currentTarget.value;
if (props.ignoreCompositionEvents && getNativeEvent(event).isComposing) {
setters.setQuery(value);
return;
}
onInput(_objectSpread({
event: event,
props: props,
query: value.slice(0, maxLength),
refresh: refresh,
store: store
}, setters));
},
onCompositionEnd: function onCompositionEnd(event) {
onInput(_objectSpread({
event: event,
props: props,
query: event.currentTarget.value.slice(0, maxLength),
refresh: refresh,
store: store
}, setters));
},
onKeyDown: function onKeyDown(event) {
if (getNativeEvent(event).isComposing) {
return;
}
_onKeyDown(_objectSpread({
event: event,
props: props,
refresh: refresh,
store: store
}, setters));
},
onFocus: onFocus,
// We don't rely on the `blur` event.
// See explanation in `onTouchStart`/`onMouseDown`.
// @MAJOR See if we need to keep this handler.
onBlur: noop,
onClick: function onClick(event) {
// When the panel is closed and you click on the input while
// the input is focused, the `onFocus` event is not triggered
// (default browser behavior).
// In an autocomplete context, it makes sense to open the panel in this
// case.
// We mimic this event by catching the `onClick` event which
// triggers the `onFocus` for the panel to open.
if (providedProps.inputElement === props.environment.document.activeElement && !store.getState().isOpen) {
onFocus(event);
}
}
}, rest);
};
var getLabelProps = function getLabelProps(rest) {
return _objectSpread({
htmlFor: getAutocompleteElementId(props.id, 'input'),
id: getAutocompleteElementId(props.id, 'label')
}, rest);
};
var getListProps = function getListProps(providedProps) {
var _ref5 = providedProps || {},
source = _ref5.source,
rest = _objectWithoutProperties(_ref5, _excluded5);
return _objectSpread({
role: 'listbox',
'aria-labelledby': getAutocompleteElementId(props.id, 'label'),
id: getAutocompleteElementId(props.id, 'list', source)
}, rest);
};
var getPanelProps = function getPanelProps(rest) {
return _objectSpread({
onMouseDown: function onMouseDown(event) {
// Prevents the `activeElement` from being changed to the panel so
// that the blur event is not triggered, otherwise it closes the
// panel.
event.preventDefault();
},
onMouseLeave: function onMouseLeave() {
store.dispatch('mouseleave', null);
}
}, rest);
};
var getItemProps = function getItemProps(providedProps) {
var item = providedProps.item,
source = providedProps.source,
rest = _objectWithoutProperties(providedProps, _excluded6);
return _objectSpread({
id: getAutocompleteElementId(props.id, "item-".concat(item.__autocomplete_id), source),
role: 'option',
'aria-selected': store.getState().activeItemId === item.__autocomplete_id,
onMouseMove: function onMouseMove(event) {
if (item.__autocomplete_id === store.getState().activeItemId) {
return;
}
store.dispatch('mousemove', item.__autocomplete_id);
var activeItem = getActiveItem(store.getState());
if (store.getState().activeItemId !== null && activeItem) {
var _item = activeItem.item,
itemInputValue = activeItem.itemInputValue,
itemUrl = activeItem.itemUrl,
_source = activeItem.source;
_source.onActive(_objectSpread({
event: event,
item: _item,
itemInputValue: itemInputValue,
itemUrl: itemUrl,
refresh: refresh,
source: _source,
state: store.getState()
}, setters));
}
},
onMouseDown: function onMouseDown(event) {
// Prevents the `activeElement` from being changed to the item so it
// can remain with the current `activeElement`.
event.preventDefault();
},
onClick: function onClick(event) {
var itemInputValue = source.getItemInputValue({
item: item,
state: store.getState()
});
var itemUrl = source.getItemUrl({
item: item,
state: store.getState()
});
// If `getItemUrl` is provided, it means that the suggestion
// is a link, not plain text that aims at updating the query.
// We can therefore skip the state change because it will update
// the `activeItemId`, resulting in a UI flash, especially
// noticeable on mobile.
var runPreCommand = itemUrl ? Promise.resolve() : onInput(_objectSpread({
event: event,
nextState: {
isOpen: false
},
props: props,
query: itemInputValue,
refresh: refresh,
store: store
}, setters));
runPreCommand.then(function () {
source.onSelect(_objectSpread({
event: event,
item: item,
itemInputValue: itemInputValue,
itemUrl: itemUrl,
refresh: refresh,
source: source,
state: store.getState()
}, setters));
});
}
}, rest);
};
return {
getEnvironmentProps: getEnvironmentProps,
getRootProps: getRootProps,
getFormProps: getFormProps,
getLabelProps: getLabelProps,
getInputProps: getInputProps,
getPanelProps: getPanelProps,
getListProps: getListProps,
getItemProps: getItemProps
};
}

View File

@@ -0,0 +1,3 @@
export * from './createAutocomplete';
export * from './getDefaultProps';
export * from './types';

View File

@@ -0,0 +1,3 @@
export * from './createAutocomplete';
export * from './getDefaultProps';
export * from './types';

View File

@@ -0,0 +1,33 @@
import { UserAgent } from '@algolia/autocomplete-shared';
import { AutocompleteEnvironment, AutocompleteOptionsWithMetadata, AutocompletePlugin, BaseItem } from '.';
declare type AutocompleteMetadata = {
plugins: Array<{
name: string | undefined;
options: string[];
}>;
options: Record<string, string[]>;
ua: UserAgent[];
};
declare type GetMetadataParams<TItem extends BaseItem, TData = unknown> = {
plugins: Array<AutocompletePlugin<TItem, TData>>;
options: AutocompleteOptionsWithMetadata<TItem>;
};
export declare function getMetadata<TItem extends BaseItem, TData = unknown>({ plugins, options, }: GetMetadataParams<TItem, TData>): {
plugins: {
name: string | undefined;
options: string[];
}[];
options: {
'autocomplete-core': string[];
};
ua: {
segment: string;
version: string;
}[];
};
declare type InlineMetadataParams = {
metadata: AutocompleteMetadata;
environment: AutocompleteEnvironment;
};
export declare function injectMetadata({ metadata, environment, }: InlineMetadataParams): void;
export {};

View File

@@ -0,0 +1,41 @@
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
import { userAgents } from '@algolia/autocomplete-shared';
export function getMetadata(_ref) {
var _, _options$__autocomple, _options$__autocomple2, _options$__autocomple3;
var plugins = _ref.plugins,
options = _ref.options;
var optionsKey = (_ = (((_options$__autocomple = options.__autocomplete_metadata) === null || _options$__autocomple === void 0 ? void 0 : _options$__autocomple.userAgents) || [])[0]) === null || _ === void 0 ? void 0 : _.segment;
var extraOptions = optionsKey ? _defineProperty({}, optionsKey, Object.keys(((_options$__autocomple2 = options.__autocomplete_metadata) === null || _options$__autocomple2 === void 0 ? void 0 : _options$__autocomple2.options) || {})) : {};
return {
plugins: plugins.map(function (plugin) {
return {
name: plugin.name,
options: Object.keys(plugin.__autocomplete_pluginOptions || [])
};
}),
options: _objectSpread({
'autocomplete-core': Object.keys(options)
}, extraOptions),
ua: userAgents.concat(((_options$__autocomple3 = options.__autocomplete_metadata) === null || _options$__autocomple3 === void 0 ? void 0 : _options$__autocomple3.userAgents) || [])
};
}
export function injectMetadata(_ref3) {
var _environment$navigato, _environment$navigato2;
var metadata = _ref3.metadata,
environment = _ref3.environment;
var isMetadataEnabled = (_environment$navigato = environment.navigator) === null || _environment$navigato === void 0 ? void 0 : (_environment$navigato2 = _environment$navigato.userAgent) === null || _environment$navigato2 === void 0 ? void 0 : _environment$navigato2.includes('Algolia Crawler');
if (isMetadataEnabled) {
var metadataContainer = environment.document.createElement('meta');
var headRef = environment.document.querySelector('head');
metadataContainer.name = 'algolia:metadata';
setTimeout(function () {
metadataContainer.content = JSON.stringify(metadata);
headRef.appendChild(metadataContainer);
}, 0);
}
}

View File

@@ -0,0 +1,18 @@
import { AutocompleteScopeApi, AutocompleteState, AutocompleteStore, BaseItem, InternalAutocompleteOptions } from './types';
import { CancelablePromise } from './utils';
interface OnInputParams<TItem extends BaseItem> extends AutocompleteScopeApi<TItem> {
event: any;
/**
* The next partial state to apply after the function is called.
*
* This is useful when we call `onInput` in a different scenario than an
* actual input. For example, we use `onInput` when we click on an item,
* but we want to close the panel in that case.
*/
nextState?: Partial<AutocompleteState<TItem>>;
props: InternalAutocompleteOptions<TItem>;
query: string;
store: AutocompleteStore<TItem>;
}
export declare function onInput<TItem extends BaseItem>({ event, nextState, props, query, refresh, store, ...setters }: OnInputParams<TItem>): CancelablePromise<void>;
export {};

View File

@@ -0,0 +1,143 @@
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
var _excluded = ["event", "nextState", "props", "query", "refresh", "store"];
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
import { reshape } from './reshape';
import { preResolve, resolve, postResolve } from './resolve';
import { cancelable, createConcurrentSafePromise, getActiveItem } from './utils';
var lastStalledId = null;
var runConcurrentSafePromise = createConcurrentSafePromise();
export function onInput(_ref) {
var event = _ref.event,
_ref$nextState = _ref.nextState,
nextState = _ref$nextState === void 0 ? {} : _ref$nextState,
props = _ref.props,
query = _ref.query,
refresh = _ref.refresh,
store = _ref.store,
setters = _objectWithoutProperties(_ref, _excluded);
if (lastStalledId) {
props.environment.clearTimeout(lastStalledId);
}
var setCollections = setters.setCollections,
setIsOpen = setters.setIsOpen,
setQuery = setters.setQuery,
setActiveItemId = setters.setActiveItemId,
setStatus = setters.setStatus,
setContext = setters.setContext;
setQuery(query);
setActiveItemId(props.defaultActiveItemId);
if (!query && props.openOnFocus === false) {
var _nextState$isOpen;
var collections = store.getState().collections.map(function (collection) {
return _objectSpread(_objectSpread({}, collection), {}, {
items: []
});
});
setStatus('idle');
setCollections(collections);
setIsOpen((_nextState$isOpen = nextState.isOpen) !== null && _nextState$isOpen !== void 0 ? _nextState$isOpen : props.shouldPanelOpen({
state: store.getState()
}));
// We make sure to update the latest resolved value of the tracked
// promises to keep late resolving promises from "cancelling" the state
// updates performed in this code path.
// We chain with a void promise to respect `onInput`'s expected return type.
var _request = cancelable(runConcurrentSafePromise(collections).then(function () {
return Promise.resolve();
}));
return store.pendingRequests.add(_request);
}
setStatus('loading');
lastStalledId = props.environment.setTimeout(function () {
setStatus('stalled');
}, props.stallThreshold);
// We track the entire promise chain triggered by `onInput` before mutating
// the Autocomplete state to make sure that any state manipulation is based on
// fresh data regardless of when promises individually resolve.
// We don't track nested promises and only rely on the full chain resolution,
// meaning we should only ever manipulate the state once this concurrent-safe
// promise is resolved.
var request = cancelable(runConcurrentSafePromise(props.getSources(_objectSpread({
query: query,
refresh: refresh,
state: store.getState()
}, setters)).then(function (sources) {
return Promise.all(sources.map(function (source) {
return Promise.resolve(source.getItems(_objectSpread({
query: query,
refresh: refresh,
state: store.getState()
}, setters))).then(function (itemsOrDescription) {
return preResolve(itemsOrDescription, source.sourceId, store.getState());
});
})).then(resolve).then(function (responses) {
var __automaticInsights = responses.some(function (_ref2) {
var items = _ref2.items;
return isSearchResponseWithAutomaticInsightsFlag(items);
});
// No need to pollute the context if `__automaticInsights=false`
if (__automaticInsights) {
var _store$getState$conte;
setContext({
algoliaInsightsPlugin: _objectSpread(_objectSpread({}, ((_store$getState$conte = store.getState().context) === null || _store$getState$conte === void 0 ? void 0 : _store$getState$conte.algoliaInsightsPlugin) || {}), {}, {
__automaticInsights: __automaticInsights
})
});
}
return postResolve(responses, sources, store);
}).then(function (collections) {
return reshape({
collections: collections,
props: props,
state: store.getState()
});
});
}))).then(function (collections) {
var _nextState$isOpen2;
// Parameters passed to `onInput` could be stale when the following code
// executes, because `onInput` calls may not resolve in order.
// If it becomes a problem we'll need to save the last passed parameters.
// See: https://codesandbox.io/s/agitated-cookies-y290z
setStatus('idle');
setCollections(collections);
var isPanelOpen = props.shouldPanelOpen({
state: store.getState()
});
setIsOpen((_nextState$isOpen2 = nextState.isOpen) !== null && _nextState$isOpen2 !== void 0 ? _nextState$isOpen2 : props.openOnFocus && !query && isPanelOpen || isPanelOpen);
var highlightedItem = getActiveItem(store.getState());
if (store.getState().activeItemId !== null && highlightedItem) {
var item = highlightedItem.item,
itemInputValue = highlightedItem.itemInputValue,
itemUrl = highlightedItem.itemUrl,
source = highlightedItem.source;
source.onActive(_objectSpread({
event: event,
item: item,
itemInputValue: itemInputValue,
itemUrl: itemUrl,
refresh: refresh,
source: source,
state: store.getState()
}, setters));
}
}).finally(function () {
setStatus('idle');
if (lastStalledId) {
props.environment.clearTimeout(lastStalledId);
}
});
return store.pendingRequests.add(request);
}
function isSearchResponseWithAutomaticInsightsFlag(items) {
return !Array.isArray(items) && Boolean(items === null || items === void 0 ? void 0 : items._automaticInsights);
}

View File

@@ -0,0 +1,8 @@
import { AutocompleteScopeApi, AutocompleteStore, BaseItem, InternalAutocompleteOptions } from './types';
interface OnKeyDownOptions<TItem extends BaseItem> extends AutocompleteScopeApi<TItem> {
event: KeyboardEvent;
props: InternalAutocompleteOptions<TItem>;
store: AutocompleteStore<TItem>;
}
export declare function onKeyDown<TItem extends BaseItem>({ event, props, refresh, store, ...setters }: OnKeyDownOptions<TItem>): void;
export {};

View File

@@ -0,0 +1,196 @@
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
var _excluded = ["event", "props", "refresh", "store"];
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
import { onInput } from './onInput';
import { getActiveItem, getAutocompleteElementId } from './utils';
export function onKeyDown(_ref) {
var event = _ref.event,
props = _ref.props,
refresh = _ref.refresh,
store = _ref.store,
setters = _objectWithoutProperties(_ref, _excluded);
if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
// eslint-disable-next-line no-inner-declarations
var triggerScrollIntoView = function triggerScrollIntoView() {
var highlightedItem = getActiveItem(store.getState());
var nodeItem = props.environment.document.getElementById(getAutocompleteElementId(props.id, "item-".concat(store.getState().activeItemId), highlightedItem === null || highlightedItem === void 0 ? void 0 : highlightedItem.source));
if (nodeItem) {
if (nodeItem.scrollIntoViewIfNeeded) {
nodeItem.scrollIntoViewIfNeeded(false);
} else {
nodeItem.scrollIntoView(false);
}
}
}; // eslint-disable-next-line no-inner-declarations
var triggerOnActive = function triggerOnActive() {
var highlightedItem = getActiveItem(store.getState());
if (store.getState().activeItemId !== null && highlightedItem) {
var item = highlightedItem.item,
itemInputValue = highlightedItem.itemInputValue,
itemUrl = highlightedItem.itemUrl,
source = highlightedItem.source;
source.onActive(_objectSpread({
event: event,
item: item,
itemInputValue: itemInputValue,
itemUrl: itemUrl,
refresh: refresh,
source: source,
state: store.getState()
}, setters));
}
}; // Default browser behavior changes the caret placement on ArrowUp and
// ArrowDown.
event.preventDefault();
// When re-opening the panel, we need to split the logic to keep the actions
// synchronized as `onInput` returns a promise.
if (store.getState().isOpen === false && (props.openOnFocus || Boolean(store.getState().query))) {
onInput(_objectSpread({
event: event,
props: props,
query: store.getState().query,
refresh: refresh,
store: store
}, setters)).then(function () {
store.dispatch(event.key, {
nextActiveItemId: props.defaultActiveItemId
});
triggerOnActive();
// Since we rely on the DOM, we need to wait for all the micro tasks to
// finish (which include re-opening the panel) to make sure all the
// elements are available.
setTimeout(triggerScrollIntoView, 0);
});
} else {
store.dispatch(event.key, {});
triggerOnActive();
triggerScrollIntoView();
}
} else if (event.key === 'Escape') {
// This prevents the default browser behavior on `input[type="search"]`
// from removing the query right away because we first want to close the
// panel.
event.preventDefault();
store.dispatch(event.key, null);
// Hitting the `Escape` key signals the end of a user interaction with the
// autocomplete. At this point, we should ignore any requests that are still
// pending and could reopen the panel once they resolve, because that would
// result in an unsolicited UI behavior.
store.pendingRequests.cancelAll();
} else if (event.key === 'Tab') {
store.dispatch('blur', null);
// Hitting the `Tab` key signals the end of a user interaction with the
// autocomplete. At this point, we should ignore any requests that are still
// pending and could reopen the panel once they resolve, because that would
// result in an unsolicited UI behavior.
store.pendingRequests.cancelAll();
} else if (event.key === 'Enter') {
// No active item, so we let the browser handle the native `onSubmit` form
// event.
if (store.getState().activeItemId === null || store.getState().collections.every(function (collection) {
return collection.items.length === 0;
})) {
// If requests are still pending when the panel closes, they could reopen
// the panel once they resolve.
// We want to prevent any subsequent query from reopening the panel
// because it would result in an unsolicited UI behavior.
if (!props.debug) {
store.pendingRequests.cancelAll();
}
return;
}
// This prevents the `onSubmit` event to be sent because an item is
// highlighted.
event.preventDefault();
var _ref2 = getActiveItem(store.getState()),
item = _ref2.item,
itemInputValue = _ref2.itemInputValue,
itemUrl = _ref2.itemUrl,
source = _ref2.source;
if (event.metaKey || event.ctrlKey) {
if (itemUrl !== undefined) {
source.onSelect(_objectSpread({
event: event,
item: item,
itemInputValue: itemInputValue,
itemUrl: itemUrl,
refresh: refresh,
source: source,
state: store.getState()
}, setters));
props.navigator.navigateNewTab({
itemUrl: itemUrl,
item: item,
state: store.getState()
});
}
} else if (event.shiftKey) {
if (itemUrl !== undefined) {
source.onSelect(_objectSpread({
event: event,
item: item,
itemInputValue: itemInputValue,
itemUrl: itemUrl,
refresh: refresh,
source: source,
state: store.getState()
}, setters));
props.navigator.navigateNewWindow({
itemUrl: itemUrl,
item: item,
state: store.getState()
});
}
} else if (event.altKey) {
// Keep native browser behavior
} else {
if (itemUrl !== undefined) {
source.onSelect(_objectSpread({
event: event,
item: item,
itemInputValue: itemInputValue,
itemUrl: itemUrl,
refresh: refresh,
source: source,
state: store.getState()
}, setters));
props.navigator.navigate({
itemUrl: itemUrl,
item: item,
state: store.getState()
});
return;
}
onInput(_objectSpread({
event: event,
nextState: {
isOpen: false
},
props: props,
query: itemInputValue,
refresh: refresh,
store: store
}, setters)).then(function () {
source.onSelect(_objectSpread({
event: event,
item: item,
itemInputValue: itemInputValue,
itemUrl: itemUrl,
refresh: refresh,
source: source,
state: store.getState()
}, setters));
});
}
}
}

View File

@@ -0,0 +1,11 @@
import { AutocompleteCollection, AutocompleteState, BaseItem, InternalAutocompleteOptions } from './types';
declare type ReshapeParams<TItem extends BaseItem> = {
collections: Array<AutocompleteCollection<any>>;
props: InternalAutocompleteOptions<TItem>;
state: AutocompleteState<TItem>;
};
export declare function reshape<TItem extends BaseItem>({ collections, props, state, }: ReshapeParams<TItem>): {
source: import("@algolia/autocomplete-shared/dist/esm/core/AutocompleteReshape").AutocompleteReshapeSource<TItem>;
items: TItem[];
}[];
export {};

View File

@@ -0,0 +1,45 @@
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
import { flatten } from '@algolia/autocomplete-shared';
export function reshape(_ref) {
var collections = _ref.collections,
props = _ref.props,
state = _ref.state;
// Sources are grouped by `sourceId` to conveniently pick them via destructuring.
// Example: `const { recentSearchesPlugin } = sourcesBySourceId`
var originalSourcesBySourceId = collections.reduce(function (acc, collection) {
return _objectSpread(_objectSpread({}, acc), {}, _defineProperty({}, collection.source.sourceId, _objectSpread(_objectSpread({}, collection.source), {}, {
getItems: function getItems() {
// We provide the resolved items from the collection to the `reshape` prop.
return flatten(collection.items);
}
})));
}, {});
var _props$plugins$reduce = props.plugins.reduce(function (acc, plugin) {
if (plugin.reshape) {
return plugin.reshape(acc);
}
return acc;
}, {
sourcesBySourceId: originalSourcesBySourceId,
state: state
}),
sourcesBySourceId = _props$plugins$reduce.sourcesBySourceId;
var reshapeSources = props.reshape({
sourcesBySourceId: sourcesBySourceId,
sources: Object.values(sourcesBySourceId),
state: state
});
// We reconstruct the collections with the items modified by the `reshape` prop.
return flatten(reshapeSources).filter(Boolean).map(function (source) {
return {
source: source,
items: source.getItems()
};
});
}

View File

@@ -0,0 +1,43 @@
import type { ExecuteResponse, RequesterDescription, TransformResponse } from '@algolia/autocomplete-preset-algolia';
import type { SearchResponse } from '@algolia/autocomplete-shared';
import { MultipleQueriesQuery, SearchForFacetValuesResponse } from '@algolia/client-search';
import { AutocompleteState, AutocompleteStore, BaseItem, InternalAutocompleteSource } from './types';
declare type RequestDescriptionPreResolved<TItem extends BaseItem> = Pick<RequesterDescription<TItem>, 'execute' | 'requesterId' | 'searchClient' | 'transformResponse'> & {
requests: Array<{
query: MultipleQueriesQuery;
sourceId: string;
transformResponse: TransformResponse<TItem>;
}>;
};
declare type RequestDescriptionPreResolvedCustom<TItem extends BaseItem> = {
items: TItem[] | TItem[][];
sourceId: string;
transformResponse?: undefined;
};
export declare function preResolve<TItem extends BaseItem>(itemsOrDescription: TItem[] | TItem[][] | RequesterDescription<TItem>, sourceId: string, state: AutocompleteState<TItem>): RequestDescriptionPreResolved<TItem> | RequestDescriptionPreResolvedCustom<TItem>;
export declare function resolve<TItem extends BaseItem>(items: Array<RequestDescriptionPreResolved<TItem> | RequestDescriptionPreResolvedCustom<TItem>>): Promise<(RequestDescriptionPreResolvedCustom<TItem> | {
items: SearchForFacetValuesResponse | SearchResponse<TItem>;
sourceId: string;
transformResponse: TransformResponse<TItem>;
})[]>;
export declare function postResolve<TItem extends BaseItem>(responses: Array<RequestDescriptionPreResolvedCustom<TItem> | ExecuteResponse<TItem>[0]>, sources: Array<InternalAutocompleteSource<TItem>>, store: AutocompleteStore<TItem>): {
source: InternalAutocompleteSource<TItem>;
items: {
label: string;
count: number;
_highlightResult: {
label: {
value: string;
};
};
}[][] | {
label: string;
count: number;
_highlightResult: {
label: {
value: string;
};
};
}[] | import("@algolia/client-search").Hit<TItem>[] | (SearchForFacetValuesResponse | SearchResponse<TItem> | TItem[] | TItem[][])[];
}[];
export {};

View File

@@ -0,0 +1,114 @@
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
import { decycle, flatten, invariant } from '@algolia/autocomplete-shared';
import { mapToAlgoliaResponse } from './utils';
function isDescription(item) {
return Boolean(item.execute);
}
function isRequesterDescription(description) {
return Boolean(description === null || description === void 0 ? void 0 : description.execute);
}
export function preResolve(itemsOrDescription, sourceId, state) {
if (isRequesterDescription(itemsOrDescription)) {
var contextParameters = itemsOrDescription.requesterId === 'algolia' ? Object.assign.apply(Object, [{}].concat(_toConsumableArray(Object.keys(state.context).map(function (key) {
var _state$context$key;
return (_state$context$key = state.context[key]) === null || _state$context$key === void 0 ? void 0 : _state$context$key.__algoliaSearchParameters;
})))) : {};
return _objectSpread(_objectSpread({}, itemsOrDescription), {}, {
requests: itemsOrDescription.queries.map(function (query) {
return {
query: itemsOrDescription.requesterId === 'algolia' ? _objectSpread(_objectSpread({}, query), {}, {
params: _objectSpread(_objectSpread({}, contextParameters), query.params)
}) : query,
sourceId: sourceId,
transformResponse: itemsOrDescription.transformResponse
};
})
});
}
return {
items: itemsOrDescription,
sourceId: sourceId
};
}
export function resolve(items) {
var packed = items.reduce(function (acc, current) {
if (!isDescription(current)) {
acc.push(current);
return acc;
}
var searchClient = current.searchClient,
execute = current.execute,
requesterId = current.requesterId,
requests = current.requests;
var container = acc.find(function (item) {
return isDescription(current) && isDescription(item) && item.searchClient === searchClient && Boolean(requesterId) && item.requesterId === requesterId;
});
if (container) {
var _container$items;
(_container$items = container.items).push.apply(_container$items, _toConsumableArray(requests));
} else {
var request = {
execute: execute,
requesterId: requesterId,
items: requests,
searchClient: searchClient
};
acc.push(request);
}
return acc;
}, []);
var values = packed.map(function (maybeDescription) {
if (!isDescription(maybeDescription)) {
return Promise.resolve(maybeDescription);
}
var _ref = maybeDescription,
execute = _ref.execute,
items = _ref.items,
searchClient = _ref.searchClient;
return execute({
searchClient: searchClient,
requests: items
});
});
return Promise.all(values).then(function (responses) {
return flatten(responses);
});
}
export function postResolve(responses, sources, store) {
return sources.map(function (source) {
var matches = responses.filter(function (response) {
return response.sourceId === source.sourceId;
});
var results = matches.map(function (_ref2) {
var items = _ref2.items;
return items;
});
var transform = matches[0].transformResponse;
var items = transform ? transform(mapToAlgoliaResponse(results)) : results;
source.onResolve({
source: source,
results: results,
items: items,
state: store.getState()
});
invariant(Array.isArray(items), function () {
return "The `getItems` function from source \"".concat(source.sourceId, "\" must return an array of items but returned type ").concat(JSON.stringify(_typeof(items)), ":\n\n").concat(JSON.stringify(decycle(items), null, 2), ".\n\nSee: https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/sources/#param-getitems");
});
invariant(items.every(Boolean), "The `getItems` function from source \"".concat(source.sourceId, "\" must return an array of items but returned ").concat(JSON.stringify(undefined), ".\n\nDid you forget to return items?\n\nSee: https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/sources/#param-getitems"));
return {
source: source,
items: items
};
});
}

View File

@@ -0,0 +1,2 @@
import { Reducer } from './types';
export declare const stateReducer: Reducer;

View File

@@ -0,0 +1,145 @@
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
import { getItemsCount, invariant } from '@algolia/autocomplete-shared';
import { getCompletion } from './getCompletion';
import { getNextActiveItemId } from './utils';
export var stateReducer = function stateReducer(state, action) {
switch (action.type) {
case 'setActiveItemId':
{
return _objectSpread(_objectSpread({}, state), {}, {
activeItemId: action.payload
});
}
case 'setQuery':
{
return _objectSpread(_objectSpread({}, state), {}, {
query: action.payload,
completion: null
});
}
case 'setCollections':
{
return _objectSpread(_objectSpread({}, state), {}, {
collections: action.payload
});
}
case 'setIsOpen':
{
return _objectSpread(_objectSpread({}, state), {}, {
isOpen: action.payload
});
}
case 'setStatus':
{
return _objectSpread(_objectSpread({}, state), {}, {
status: action.payload
});
}
case 'setContext':
{
return _objectSpread(_objectSpread({}, state), {}, {
context: _objectSpread(_objectSpread({}, state.context), action.payload)
});
}
case 'ArrowDown':
{
var nextState = _objectSpread(_objectSpread({}, state), {}, {
activeItemId: action.payload.hasOwnProperty('nextActiveItemId') ? action.payload.nextActiveItemId : getNextActiveItemId(1, state.activeItemId, getItemsCount(state), action.props.defaultActiveItemId)
});
return _objectSpread(_objectSpread({}, nextState), {}, {
completion: getCompletion({
state: nextState
})
});
}
case 'ArrowUp':
{
var _nextState = _objectSpread(_objectSpread({}, state), {}, {
activeItemId: getNextActiveItemId(-1, state.activeItemId, getItemsCount(state), action.props.defaultActiveItemId)
});
return _objectSpread(_objectSpread({}, _nextState), {}, {
completion: getCompletion({
state: _nextState
})
});
}
case 'Escape':
{
if (state.isOpen) {
return _objectSpread(_objectSpread({}, state), {}, {
activeItemId: null,
isOpen: false,
completion: null
});
}
return _objectSpread(_objectSpread({}, state), {}, {
activeItemId: null,
query: '',
status: 'idle',
collections: []
});
}
case 'submit':
{
return _objectSpread(_objectSpread({}, state), {}, {
activeItemId: null,
isOpen: false,
status: 'idle'
});
}
case 'reset':
{
return _objectSpread(_objectSpread({}, state), {}, {
activeItemId:
// Since we open the panel on reset when openOnFocus=true
// we need to restore the highlighted index to the defaultActiveItemId. (DocSearch use-case)
// Since we close the panel when openOnFocus=false
// we lose track of the highlighted index. (Query-suggestions use-case)
action.props.openOnFocus === true ? action.props.defaultActiveItemId : null,
status: 'idle',
completion: null,
query: ''
});
}
case 'focus':
{
return _objectSpread(_objectSpread({}, state), {}, {
activeItemId: action.props.defaultActiveItemId,
isOpen: (action.props.openOnFocus || Boolean(state.query)) && action.props.shouldPanelOpen({
state: state
})
});
}
case 'blur':
{
if (action.props.debug) {
return state;
}
return _objectSpread(_objectSpread({}, state), {}, {
isOpen: false,
activeItemId: null
});
}
case 'mousemove':
{
return _objectSpread(_objectSpread({}, state), {}, {
activeItemId: action.payload
});
}
case 'mouseleave':
{
return _objectSpread(_objectSpread({}, state), {}, {
activeItemId: action.props.defaultActiveItemId
});
}
default:
invariant(false, "The reducer action ".concat(JSON.stringify(action.type), " is not supported."));
return state;
}
};

View File

@@ -0,0 +1,15 @@
import { CancelablePromiseList } from '../utils';
import { BaseItem, InternalAutocompleteOptions, AutocompleteState } from './';
export interface AutocompleteStore<TItem extends BaseItem> {
getState(): AutocompleteState<TItem>;
dispatch(action: ActionType, payload: any): void;
pendingRequests: CancelablePromiseList<void>;
}
export declare type Reducer = <TItem extends BaseItem>(state: AutocompleteState<TItem>, action: Action<TItem, any>) => AutocompleteState<TItem>;
declare type Action<TItem extends BaseItem, TPayload> = {
type: ActionType;
props: InternalAutocompleteOptions<TItem>;
payload: TPayload;
};
export declare type ActionType = 'setActiveItemId' | 'setQuery' | 'setCollections' | 'setIsOpen' | 'setStatus' | 'setContext' | 'ArrowUp' | 'ArrowDown' | 'Escape' | 'Enter' | 'submit' | 'reset' | 'focus' | 'blur' | 'mousemove' | 'mouseleave' | 'click';
export {};

View File

@@ -0,0 +1,7 @@
import { BaseItem, OnActiveParams, OnResolveParams, OnSelectParams } from './';
export declare type AutocompleteSubscriber<TItem extends BaseItem> = {
onSelect(params: OnSelectParams<TItem>): void;
onActive(params: OnActiveParams<TItem>): void;
onResolve(params: OnResolveParams<TItem>): void;
};
export declare type AutocompleteSubscribers<TItem extends BaseItem> = Array<Partial<AutocompleteSubscriber<TItem>>>;

View File

@@ -0,0 +1,22 @@
export * from '@algolia/autocomplete-shared/dist/esm/core';
export * from './AutocompleteStore';
export * from './AutocompleteSubscribers';
import { CreateAlgoliaInsightsPluginParams, AutocompleteInsightsApi as _AutocompleteInsightsApi, AlgoliaInsightsHit as _AlgoliaInsightsHit } from '@algolia/autocomplete-plugin-algolia-insights';
import { AutocompleteOptions as _AutocompleteOptions, InternalAutocompleteOptions as _InternalAutocompleteOptions, BaseItem } from '@algolia/autocomplete-shared/dist/esm/core';
export declare type AutocompleteInsightsApi = _AutocompleteInsightsApi;
export declare type AlgoliaInsightsHit = _AlgoliaInsightsHit;
declare type InsightsOption = {
/**
* Whether to enable the Insights plugin and load the Insights library if it has not been loaded yet.
*
* See [**autocomplete-plugin-algolia-insights**](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-algolia-insights/) for more information.
*
* @default undefined
* @link https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete/#param-insights
*/
insights?: CreateAlgoliaInsightsPluginParams | boolean | undefined;
};
export interface AutocompleteOptions<TItem extends BaseItem> extends _AutocompleteOptions<TItem>, InsightsOption {
}
export interface InternalAutocompleteOptions<TItem extends BaseItem> extends _InternalAutocompleteOptions<TItem>, InsightsOption {
}

Some files were not shown because too many files have changed in this diff Show More