@meeovi/layer-search 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +181 -0
  2. package/app/components/README.md +3 -0
  3. package/app/components/atoms/BaseButton.vue +36 -0
  4. package/app/components/atoms/BaseCard.vue +13 -0
  5. package/app/components/atoms/BaseCheckbox.vue +51 -0
  6. package/app/components/atoms/BaseLogo.vue +19 -0
  7. package/app/components/atoms/BaseText.vue +17 -0
  8. package/app/components/atoms/BaseTitle.vue +23 -0
  9. package/app/components/atoms/DiscordIcon.vue +14 -0
  10. package/app/components/atoms/GithubIcon.vue +14 -0
  11. package/app/components/atoms/HalfSolidStarIcon.vue +5 -0
  12. package/app/components/atoms/SelectArrow.vue +5 -0
  13. package/app/components/atoms/SolidStarIcon.vue +5 -0
  14. package/app/components/atoms/StarIcon.vue +5 -0
  15. package/app/components/atoms/TwitterIcon.vue +14 -0
  16. package/app/components/atoms/WebIcon.vue +12 -0
  17. package/app/components/atoms/XIcon.vue +5 -0
  18. package/app/components/features/aiSearch.vue +0 -0
  19. package/app/components/features/allSearch.vue +0 -0
  20. package/app/components/features/autocomplete.vue +0 -0
  21. package/app/components/features/imageSearch.vue +0 -0
  22. package/app/components/features/videoSearch.vue +0 -0
  23. package/app/components/filters/filters.vue +0 -0
  24. package/app/components/molecules/BaseSelect.vue +53 -0
  25. package/app/components/molecules/PageNumber.vue +54 -0
  26. package/app/components/molecules/RangeSlider.vue +37 -0
  27. package/app/components/molecules/SearchInput.vue +32 -0
  28. package/app/components/molecules/SocialLink.vue +42 -0
  29. package/app/components/molecules/StarRating.vue +48 -0
  30. package/app/components/organisms/LoadingIndicator.vue +12 -0
  31. package/app/components/organisms/MeiliSearchBar.vue +15 -0
  32. package/app/components/organisms/MeiliSearchFacetFilter.vue +116 -0
  33. package/app/components/organisms/MeiliSearchLoadingProvider.vue +29 -0
  34. package/app/components/organisms/MeiliSearchPagination.vue +40 -0
  35. package/app/components/organisms/MeiliSearchProvider.vue +51 -0
  36. package/app/components/organisms/MeiliSearchRangeFilter.vue +52 -0
  37. package/app/components/organisms/MeiliSearchRatingFilter.vue +47 -0
  38. package/app/components/organisms/MeiliSearchResults.vue +35 -0
  39. package/app/components/organisms/MeiliSearchSorting.vue +23 -0
  40. package/app/components/organisms/MeiliSearchStats.vue +13 -0
  41. package/app/components/organisms/ProductCard.vue +80 -0
  42. package/app/components/organisms/TheNavbar.vue +71 -0
  43. package/app/components/results/audioSearch.vue +7 -0
  44. package/app/components/results/booksSearch.vue +7 -0
  45. package/app/components/results/financeSearch.vue +93 -0
  46. package/app/components/results/imageSearch.vue +7 -0
  47. package/app/components/results/musicSearch.vue +93 -0
  48. package/app/components/results/newsSearch.vue +93 -0
  49. package/app/components/results/spaceSearch.vue +93 -0
  50. package/app/components/results/spacesSearch.vue +7 -0
  51. package/app/components/results/travelSearch.vue +93 -0
  52. package/app/components/results/videoSearch.vue +7 -0
  53. package/app/components/search.vue +87 -0
  54. package/app/components/templates/HomeTemplate.vue +44 -0
  55. package/app/components/widgets/ClearRefinements.vue +27 -0
  56. package/app/components/widgets/NoResults.vue +125 -0
  57. package/app/components/widgets/PriceSlider.css +58 -0
  58. package/app/composables/adapter/meilisearch.ts +58 -0
  59. package/app/composables/adapter/mock.ts +34 -0
  60. package/app/composables/adapter/opensearch.ts +66 -0
  61. package/app/composables/adapter/types.ts +14 -0
  62. package/app/composables/bridges/instantsearch.ts +20 -0
  63. package/app/composables/bridges/react.ts +40 -0
  64. package/app/composables/bridges/vue.ts +38 -0
  65. package/app/composables/cli.ts +85 -0
  66. package/app/composables/config/schema.ts +16 -0
  67. package/app/composables/config.ts +20 -0
  68. package/app/composables/core/Facets.ts +9 -0
  69. package/app/composables/core/Filters.ts +13 -0
  70. package/app/composables/core/Normalizers.ts +0 -0
  71. package/app/composables/core/Pipeline.ts +20 -0
  72. package/app/composables/core/QueryBuilder.ts +27 -0
  73. package/app/composables/core/SearchContext.ts +54 -0
  74. package/app/composables/core/SearchManager.ts +27 -0
  75. package/app/composables/events.ts +6 -0
  76. package/app/composables/index.ts +9 -0
  77. package/app/composables/module.ts +72 -0
  78. package/app/composables/types/api/global-search.ts +8 -0
  79. package/app/composables/types.d.ts +12 -0
  80. package/app/composables/utils/normalizers.ts +6 -0
  81. package/app/pages/results.vue +85 -0
  82. package/app/plugins/instantsearch.js +35 -0
  83. package/app/plugins/search.js +20 -0
  84. package/nuxt.config.ts +11 -0
  85. package/package.json +43 -0
  86. package/tsconfig.json +14 -0
@@ -0,0 +1,54 @@
1
+ export interface SearchContextState {
2
+ query: string
3
+ page: number
4
+ pageSize: number
5
+ sort?: string
6
+ filters: Record<string, any>
7
+ }
8
+
9
+ export class SearchContext {
10
+ state: SearchContextState
11
+
12
+ constructor(initial?: Partial<SearchContextState>) {
13
+ this.state = {
14
+ query: '',
15
+ page: 1,
16
+ pageSize: 20,
17
+ filters: {},
18
+ ...initial
19
+ }
20
+ }
21
+
22
+ setQuery(query: string) {
23
+ this.state.query = query
24
+ }
25
+
26
+ setPage(page: number) {
27
+ this.state.page = page
28
+ }
29
+
30
+ setPageSize(size: number) {
31
+ this.state.pageSize = size
32
+ }
33
+
34
+ setSort(sort: string | undefined) {
35
+ this.state.sort = sort
36
+ }
37
+
38
+ setFilter(key: string, value: any) {
39
+ this.state.filters[key] = value
40
+ }
41
+
42
+ removeFilter(key: string) {
43
+ delete this.state.filters[key]
44
+ }
45
+
46
+ reset() {
47
+ this.state = {
48
+ query: '',
49
+ page: 1,
50
+ pageSize: 20,
51
+ filters: {}
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,27 @@
1
+ import type { SearchAdapter } from '@meeovi/core'
2
+ import { SearchContext, type SearchContextState } from './SearchContext'
3
+ import { QueryBuilder } from './QueryBuilder'
4
+ import { SearchPipeline } from './Pipeline'
5
+
6
+ export class SearchManager<TItem = any> {
7
+ context: SearchContext
8
+ pipeline: SearchPipeline
9
+ adapter: SearchAdapter<TItem>
10
+
11
+ constructor(adapter: SearchAdapter<TItem>, initial?: Partial<SearchContextState>) {
12
+ this.context = new SearchContext(initial)
13
+ this.pipeline = new SearchPipeline()
14
+ this.adapter = adapter
15
+ }
16
+
17
+ async search() {
18
+ const builder = new QueryBuilder(this.context)
19
+ let query = builder.build()
20
+
21
+ query = this.pipeline.runBefore(query)
22
+
23
+ const result = await this.adapter.search(query as any)
24
+
25
+ return this.pipeline.runAfter(result)
26
+ }
27
+ }
@@ -0,0 +1,6 @@
1
+ declare module '@meeovi/core/dist/types/events' {
2
+ interface AlternateEventMap {
3
+ 'search:query': { term: string }
4
+ 'search:results': { term: string; total: number }
5
+ }
6
+ }
@@ -0,0 +1,9 @@
1
+ export * from './module'
2
+ export * from './adapter/types'
3
+ export * from './core/SearchManager'
4
+ export * from './core/SearchContext'
5
+ export * from './core/Filters'
6
+ export * from './core/Facets'
7
+ export * from './bridges/instantsearch'
8
+ export * from './bridges/vue'
9
+ export * from './bridges/react'
@@ -0,0 +1,72 @@
1
+ import {
2
+ defineAlternateModule,
3
+ useAlternateEventBus,
4
+ useAlternateContext,
5
+ type AlternateContext
6
+ } from '@meeovi/core'
7
+
8
+ import { validateSearchConfig, type SearchModuleConfig } from './config/schema'
9
+ import type { SearchAdapter } from '@meeovi/core'
10
+ import { createOpenSearchAdapter } from './adapter/opensearch'
11
+ import { createMeilisearchAdapter } from './adapter/meilisearch'
12
+ import { SearchManager } from './core/SearchManager'
13
+ import type { MeeoviSearchItem } from './adapter/types'
14
+
15
+ declare module '@meeovi/core' {
16
+ interface AlternateConfig {
17
+ search?: SearchModuleConfig
18
+ }
19
+
20
+ interface AlternateContext {
21
+ searchManager?: import('./core/SearchManager').SearchManager<MeeoviSearchItem>
22
+ }
23
+ }
24
+
25
+ export default defineAlternateModule({
26
+ id: 'search',
27
+ adapters: {},
28
+
29
+ async setup(ctx: AlternateContext) {
30
+ const bus = useAlternateEventBus()
31
+ const config = ctx.config.search
32
+
33
+ if (!config) return
34
+
35
+ validateSearchConfig(config)
36
+
37
+ if (config.defaultProvider === 'opensearch') {
38
+ const providerConfig = config.providers.opensearch as
39
+ | Parameters<typeof createOpenSearchAdapter>[0]
40
+ | undefined
41
+
42
+ if (providerConfig) {
43
+ this.adapters = {
44
+ search: createOpenSearchAdapter(providerConfig)
45
+ }
46
+ }
47
+ }
48
+
49
+ if (config.defaultProvider === 'meilisearch') {
50
+ const providerConfig = config.providers.meilisearch as
51
+ | Parameters<typeof createMeilisearchAdapter>[0]
52
+ | undefined
53
+
54
+ if (providerConfig) {
55
+ this.adapters = {
56
+ search: createMeilisearchAdapter(providerConfig)
57
+ }
58
+ }
59
+ }
60
+
61
+ const adapter = ctx.getAdapter('search') as
62
+ | SearchAdapter<MeeoviSearchItem>
63
+ | undefined
64
+ if (adapter) {
65
+ ctx.searchManager = new SearchManager<MeeoviSearchItem>(adapter)
66
+ }
67
+
68
+ bus.on('app:ready', () => {
69
+ console.info('[@meeovi/search] Search module initialized')
70
+ })
71
+ }
72
+ })
@@ -0,0 +1,8 @@
1
+ export interface GlobalSearchResult {
2
+ id?: string;
3
+ title?: string;
4
+ type?: string;
5
+ description?: string;
6
+ image?: string;
7
+ url?: string;
8
+ }
@@ -0,0 +1,12 @@
1
+ declare module '#imports' {
2
+ export function useRuntimeConfig(): { public?: { searchUrl?: string } }
3
+ }
4
+
5
+ declare module '@searchkit/client' {
6
+ export class SearchkitClient {
7
+ constructor(config?: { url?: string; host?: string })
8
+ query(...args: unknown[]): unknown
9
+ fetch(...args: unknown[]): unknown
10
+ }
11
+ export type SearchkitClientConfig = { url?: string; host?: string }
12
+ }
@@ -0,0 +1,6 @@
1
+ export function normalizeOpenSearchHit(hit: any) {
2
+ return {
3
+ id: hit._id,
4
+ ...hit._source
5
+ }
6
+ }
@@ -0,0 +1,85 @@
1
+ <template>
2
+ <div class="resultsPage">
3
+ <ais-instant-search :search-client="searchClient" :index-name="indexName">
4
+ <header class="search-header">
5
+ <ais-search-box />
6
+ <ais-stats />
7
+ <ais-sort-by :items="sortingOptions" />
8
+ </header>
9
+
10
+ <div class="search-body">
11
+ <aside class="filters">
12
+ <ais-refinement-list attribute="category" />
13
+ <ais-refinement-list attribute="brand" />
14
+ <ais-range-input attribute="price" />
15
+ <ais-rating-menu attribute="rating_rounded" />
16
+ </aside>
17
+
18
+ <main class="results">
19
+ <ais-hits>
20
+ <template #item="{ item }">
21
+ <article @click="openResult(item)" class="hit">
22
+ <h3><ais-highlight attribute="title" :hit="item" /></h3>
23
+ <p><ais-snippet attribute="plot" :hit="item" /></p>
24
+ </article>
25
+ </template>
26
+ </ais-hits>
27
+
28
+ <ais-pagination />
29
+ </main>
30
+ </div>
31
+ </ais-instant-search>
32
+ </div>
33
+ </template>
34
+
35
+ <script setup>
36
+ import { ref } from 'vue'
37
+ import { useRouter, useRoute } from 'vue-router'
38
+ import { useRuntimeConfig } from '#imports'
39
+ import Client from '@searchkit/instantsearch-client'
40
+ import { getSearchClient, getIndexName } from '../utils/search/client'
41
+
42
+ import 'instantsearch.css/themes/satellite-min.css'
43
+
44
+ // initialize search client using existing helper (falls back safely)
45
+ let searchClient
46
+ let indexName = 'default'
47
+ try {
48
+ searchClient = getSearchClient()
49
+ indexName = getIndexName()
50
+ } catch (e) {
51
+ // fallback placeholder
52
+ // eslint-disable-next-line no-console
53
+ console.warn('Search client unavailable:', e.message || e)
54
+ searchClient = { _client: 'searchkit-fallback' }
55
+ }
56
+
57
+ const router = useRouter()
58
+ const route = useRoute()
59
+
60
+ function openResult(item) {
61
+ const id = item._id ?? item.id ?? ''
62
+ const title = item.title ?? item.name ?? ''
63
+ router.push({ path: route.path, query: { ...route.query, id, title } })
64
+ }
65
+
66
+ const sortingOptions = [
67
+ { value: indexName, label: 'Featured' },
68
+ { value: `${indexName}:price:asc`, label: 'Price: Low to High' },
69
+ { value: `${indexName}:price:desc`, label: 'Price: High to Low' },
70
+ { value: `${indexName}:rating:desc`, label: 'Rating: High to Low' }
71
+ ]
72
+
73
+ definePageMeta({ layout: 'nolive' })
74
+ useHead({ title: 'Search Results' })
75
+ </script>
76
+
77
+ <style scoped>
78
+ .search-body {
79
+ display: flex;
80
+ gap: 1rem;
81
+ }
82
+ .filters { width: 260px }
83
+ .results { flex: 1 }
84
+ .hit { padding: 0.75rem; border-bottom: 1px solid #eee }
85
+ </style>
@@ -0,0 +1,35 @@
1
+ import { h } from 'vue'
2
+ import { defineNuxtPlugin } from '#imports'
3
+ import InstantSearch from 'vue-instantsearch/vue3/es'
4
+
5
+ export default defineNuxtPlugin(nuxtApp => {
6
+ // Register the original InstantSearch plugin (registers other components)
7
+ nuxtApp.vueApp.use(InstantSearch)
8
+ // Capture the original registered component (if the plugin registered it)
9
+ const OriginalInstantSearch = nuxtApp.vueApp.component('ais-instant-search') || InstantSearch
10
+
11
+ // Override `ais-instant-search` with a small wrapper that sets
12
+ // future.preserveSharedStateOnUnmount = true by default to silence
13
+ // the deprecation warning and adopt the new behavior.
14
+ const AisInstantSearchWrapper = {
15
+ name: 'AisInstantSearch',
16
+ inheritAttrs: false,
17
+ setup(_, { attrs, slots }) {
18
+ const mergedAttrs = {
19
+ ...attrs,
20
+ future: {
21
+ preserveSharedStateOnUnmount: true,
22
+ ...(attrs.future || {})
23
+ }
24
+ }
25
+ if (process.env.NODE_ENV !== 'production') {
26
+ // helpful debug when running locally
27
+ // eslint-disable-next-line no-console
28
+ console.debug('[instantsearch] merged future options', mergedAttrs.future)
29
+ }
30
+ return () => h(OriginalInstantSearch, mergedAttrs, slots)
31
+ }
32
+ }
33
+
34
+ nuxtApp.vueApp.component('ais-instant-search', AisInstantSearchWrapper)
35
+ })
@@ -0,0 +1,20 @@
1
+ import { defineNuxtPlugin } from '#imports'
2
+ import { getSearchClient, getIndexName } from '../utils/search/client'
3
+
4
+ export default defineNuxtPlugin((nuxtApp) => {
5
+ let searchClient
6
+ let indexName
7
+ try {
8
+ searchClient = getSearchClient()
9
+ indexName = getIndexName()
10
+ } catch (e) {
11
+ // fallback placeholder to avoid build/runtime crash when not configured
12
+ // eslint-disable-next-line no-console
13
+ console.warn('Search client not configured:', e.message || e)
14
+ searchClient = { _client: 'searchkit-fallback' }
15
+ indexName = 'default'
16
+ }
17
+
18
+ nuxtApp.provide('searchClient', searchClient)
19
+ nuxtApp.provide('searchIndexName', indexName)
20
+ })
package/nuxt.config.ts ADDED
@@ -0,0 +1,11 @@
1
+ import {
2
+ defineNuxtConfig
3
+ } from 'nuxt/config'
4
+
5
+ export default defineNuxtConfig({
6
+ $meta: {
7
+ name: 'search',
8
+ },
9
+
10
+ runtimeConfig: {}
11
+ })
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@meeovi/layer-search",
3
+ "version": "1.0.3",
4
+ "description": "Powerful search module for the Alternate Framework.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "module": "dist/index.mjs",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "keywords": [
16
+ "meeovi",
17
+ "search",
18
+ "module",
19
+ "alternate-framework",
20
+ "vue",
21
+ "instantsearch"
22
+ ],
23
+ "bin": {
24
+ "meeovi-search": "./dist/cli.js"
25
+ },
26
+ "author": "Meeovi",
27
+ "license": "MIT",
28
+ "type": "module",
29
+ "scripts": {
30
+ "build": "tsc -p tsconfig.json"
31
+ },
32
+ "dependencies": {
33
+ "@meeovi/core": "^1.0.3",
34
+ "@types/react": "^19.2.9",
35
+ "react": "^19.2.3",
36
+ "typescript": "^5.9.3",
37
+ "vue": "^3.2.47"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^25.0.10",
41
+ "nuxt": "^4.3.0"
42
+ }
43
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "declaration": true,
4
+ "emitDeclarationOnly": false,
5
+ "outDir": "dist",
6
+ "moduleResolution": "bundler",
7
+ "module": "ESNext",
8
+ "target": "ESNext",
9
+ "strict": true,
10
+ "jsx": "react-jsx",
11
+ "skipLibCheck": true,
12
+ "noEmitOnError": false
13
+ }
14
+ }