AlgoliaSearchBox.vue 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. <script setup lang="ts">
  2. import '@docsearch/css'
  3. import docsearch from '@docsearch/js'
  4. import { useRoute, useRouter, useData } from 'vitepress'
  5. import { getCurrentInstance, onMounted, watch } from 'vue'
  6. import type { DefaultTheme } from '../config'
  7. import type { DocSearchHit } from '@docsearch/react/dist/esm/types'
  8. const props = defineProps<{
  9. options: DefaultTheme.AlgoliaSearchOptions
  10. multilang?: boolean
  11. }>()
  12. const vm = getCurrentInstance()
  13. const route = useRoute()
  14. const router = useRouter()
  15. watch(
  16. () => props.options,
  17. (value) => {
  18. update(value)
  19. }
  20. )
  21. onMounted(() => {
  22. initialize(props.options)
  23. })
  24. function isSpecialClick(event: MouseEvent) {
  25. return (
  26. event.button === 1 ||
  27. event.altKey ||
  28. event.ctrlKey ||
  29. event.metaKey ||
  30. event.shiftKey
  31. )
  32. }
  33. function getRelativePath(absoluteUrl: string) {
  34. const { pathname, hash } = new URL(absoluteUrl)
  35. return pathname + hash
  36. }
  37. function update(options: any) {
  38. if (vm && vm.vnode.el) {
  39. vm.vnode.el.innerHTML =
  40. '<div class="algolia-search-box" id="docsearch"></div>'
  41. initialize(options)
  42. }
  43. }
  44. const { lang } = useData()
  45. // if the user has multiple locales, the search results should be filtered
  46. // based on the language
  47. const facetFilters: string[] = props.multilang ? ['lang:' + lang.value] : []
  48. if (props.options.searchParameters?.facetFilters) {
  49. facetFilters.push(...props.options.searchParameters.facetFilters)
  50. }
  51. watch(lang, (newLang, oldLang) => {
  52. const index = facetFilters.findIndex((filter) => filter === 'lang:' + oldLang)
  53. if (index > -1) {
  54. facetFilters.splice(index, 1, 'lang:' + newLang)
  55. }
  56. })
  57. function initialize(userOptions: any) {
  58. docsearch(
  59. Object.assign({}, userOptions, {
  60. container: '#docsearch',
  61. searchParameters: Object.assign({}, userOptions.searchParameters, {
  62. // pass a custom lang facetFilter to allow multiple language search
  63. // https://github.com/algolia/docsearch-configs/pull/3942
  64. facetFilters
  65. }),
  66. navigator: {
  67. navigate: ({ itemUrl }: { itemUrl: string }) => {
  68. const { pathname: hitPathname } = new URL(
  69. window.location.origin + itemUrl
  70. )
  71. // Router doesn't handle same-page navigation so we use the native
  72. // browser location API for anchor navigation
  73. if (route.path === hitPathname) {
  74. window.location.assign(window.location.origin + itemUrl)
  75. } else {
  76. router.go(itemUrl)
  77. }
  78. }
  79. },
  80. transformItems: (items: DocSearchHit[]) => {
  81. return items.map((item) => {
  82. return Object.assign({}, item, {
  83. url: getRelativePath(item.url)
  84. })
  85. })
  86. },
  87. hitComponent: ({
  88. hit,
  89. children
  90. }: {
  91. hit: DocSearchHit
  92. children: any
  93. }) => {
  94. const relativeHit = hit.url.startsWith('http')
  95. ? getRelativePath(hit.url as string)
  96. : hit.url
  97. return {
  98. type: 'a',
  99. ref: undefined,
  100. constructor: undefined,
  101. key: undefined,
  102. props: {
  103. href: hit.url,
  104. onClick: (event: MouseEvent) => {
  105. if (isSpecialClick(event)) {
  106. return
  107. }
  108. // we rely on the native link scrolling when user is already on
  109. // the right anchor because Router doesn't support duplicated
  110. // history entries
  111. if (route.path === relativeHit) {
  112. return
  113. }
  114. // if the hits goes to another page, we prevent the native link
  115. // behavior to leverage the Router loading feature
  116. if (route.path !== relativeHit) {
  117. event.preventDefault()
  118. }
  119. router.go(relativeHit)
  120. },
  121. children
  122. },
  123. __v: null
  124. }
  125. }
  126. })
  127. )
  128. }
  129. </script>
  130. <template>
  131. <div class="algolia-search-box" id="docsearch" />
  132. </template>
  133. <style>
  134. .algolia-search-box {
  135. padding-top: 1px;
  136. }
  137. @media (min-width: 720px) {
  138. .algolia-search-box {
  139. padding-left: 8px;
  140. }
  141. }
  142. @media (min-width: 751px) {
  143. .algolia-search-box {
  144. min-width: 176.3px; /* avoid layout shift */
  145. }
  146. .algolia-search-box .DocSearch-Button-Placeholder {
  147. padding-left: 8px;
  148. font-size: 0.9rem;
  149. font-weight: 500;
  150. }
  151. }
  152. .DocSearch {
  153. --docsearch-primary-color: var(--c-brand);
  154. --docsearch-highlight-color: var(--docsearch-primary-color);
  155. --docsearch-searchbox-shadow: inset 0 0 0 2px var(--docsearch-primary-color);
  156. --docsearch-text-color: var(--c-text-light);
  157. --docsearch-muted-color: var(--c-text-lighter);
  158. --docsearch-searchbox-background: #f2f2f2;
  159. }
  160. </style>