@rubixstudios/payload-typesense 1.0.10 → 1.1.0

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.
@@ -18,6 +18,10 @@ export interface HeadlessSearchInputProps<T = Record<string, unknown>> extends B
18
18
  * Number of results to show per page
19
19
  */
20
20
  perPage?: number;
21
+ /**
22
+ * Show date in search results
23
+ */
24
+ renderDate?: boolean;
21
25
  /**
22
26
  * Custom render function for error state
23
27
  */
@@ -39,6 +43,10 @@ export interface HeadlessSearchInputProps<T = Record<string, unknown>> extends B
39
43
  * Custom render function for loading state
40
44
  */
41
45
  renderLoading?: () => React.ReactNode;
46
+ /**
47
+ * Show match percentage in search results
48
+ */
49
+ renderMatchPercentage?: boolean;
42
50
  /**
43
51
  * Custom render function for no results
44
52
  */
@@ -82,7 +90,7 @@ export interface HeadlessSearchInputProps<T = Record<string, unknown>> extends B
82
90
  */
83
91
  theme?: string | ThemeConfig;
84
92
  }
85
- declare const HeadlessSearchInput: <T = Record<string, unknown>>({ baseUrl, className, collection, collections, debounceMs, enableSuggestions: _enableSuggestions, errorClassName, inputClassName, inputWrapperClassName, minQueryLength, noResultsClassName, onResultClick, onResults, onSearch, perPage, placeholder, renderError, renderInput, renderNoResults, renderResult, renderResultsHeader, resultItemClassName, resultsClassName, resultsContainerClassName, resultsHeaderClassName, resultsListClassName, showLoading, showResultCount, showSearchTime, theme, }: HeadlessSearchInputProps<T>) => React.ReactElement;
93
+ declare const HeadlessSearchInput: <T = Record<string, unknown>>({ baseUrl, className, collection, collections, debounceMs, enableSuggestions: _enableSuggestions, errorClassName, inputClassName, inputWrapperClassName, minQueryLength, noResultsClassName, onResultClick, onResults, onSearch, perPage, placeholder, renderDate, renderError, renderInput, renderMatchPercentage, renderNoResults, renderResult, renderResultsHeader, resultItemClassName, resultsClassName, resultsContainerClassName, resultsHeaderClassName, resultsListClassName, showLoading, showResultCount, showSearchTime, theme, }: HeadlessSearchInputProps<T>) => React.ReactElement;
86
94
  export default HeadlessSearchInput;
87
95
  export { HeadlessSearchInput };
88
96
  //# sourceMappingURL=HeadlessSearchInput.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"HeadlessSearchInput.d.ts","sourceRoot":"","sources":["../../src/components/HeadlessSearchInput.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmD,MAAM,OAAO,CAAA;AAEvE,OAAO,KAAK,EAAE,oBAAoB,EAAkB,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACzF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAIpD,MAAM,WAAW,wBAAwB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CACnE,SAAQ,oBAAoB,CAAC,CAAC,CAAC;IAC/B;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB;;OAEG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAA;IAChD;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE;QACpB,SAAS,EAAE,MAAM,CAAA;QACjB,MAAM,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAA;QACrC,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAA;QAC1D,OAAO,EAAE,MAAM,IAAI,CAAA;QACnB,SAAS,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,KAAK,IAAI,CAAA;QAC3C,WAAW,EAAE,MAAM,CAAA;QACnB,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;QAC7C,KAAK,EAAE,MAAM,CAAA;KACd,KAAK,KAAK,CAAC,SAAS,CAAA;IACrB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAA;IACrC;;OAEG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAA;IACpD;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE;QAAE,OAAO,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;KAAE,KAAK,KAAK,CAAC,SAAS,CAAA;IACpI;;OAEG;IACH,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAA;IAC5E;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;OAEG;IACH,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;;OAEG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,WAAW,CAAA;CAC7B;AAED,QAAA,MAAM,mBAAmB,GAAI,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAG,6eA+BxD,wBAAwB,CAAC,CAAC,CAAC,KAAG,KAAK,CAAC,YAstBtC,CAAA;AAED,eAAe,mBAAmB,CAAA;AAClC,OAAO,EAAE,mBAAmB,EAAE,CAAA"}
1
+ {"version":3,"file":"HeadlessSearchInput.d.ts","sourceRoot":"","sources":["../../src/components/HeadlessSearchInput.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmD,MAAM,OAAO,CAAA;AAEvE,OAAO,KAAK,EAAE,oBAAoB,EAAkB,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACzF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAIpD,MAAM,WAAW,wBAAwB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CACnE,SAAQ,oBAAoB,CAAC,CAAC,CAAC;IAC/B;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB;;OAEG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAA;IAChD;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE;QACpB,SAAS,EAAE,MAAM,CAAA;QACjB,MAAM,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAA;QACrC,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAA;QAC1D,OAAO,EAAE,MAAM,IAAI,CAAA;QACnB,SAAS,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,KAAK,IAAI,CAAA;QAC3C,WAAW,EAAE,MAAM,CAAA;QACnB,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;QAC7C,KAAK,EAAE,MAAM,CAAA;KACd,KAAK,KAAK,CAAC,SAAS,CAAA;IACrB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAA;IACrC;;OAEG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B;;OAEG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAA;IACpD;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE;QAAE,OAAO,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;KAAE,KAAK,KAAK,CAAC,SAAS,CAAA;IACpI;;OAEG;IACH,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAA;IAC5E;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;OAEG;IACH,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;;OAEG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,WAAW,CAAA;CAC7B;AAED,QAAA,MAAM,mBAAmB,GAAI,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAG,ghBAiCxD,wBAAwB,CAAC,CAAC,CAAC,KAAG,KAAK,CAAC,YA4sBtC,CAAA;AAED,eAAe,mBAAmB,CAAA;AAClC,OAAO,EAAE,mBAAmB,EAAE,CAAA"}
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import React, { useCallback, useEffect, useRef, useState } from 'react';
3
3
  import { useThemeConfig } from './themes/hooks.js';
4
- const HeadlessSearchInput = ({ baseUrl, className = '', collection, collections, debounceMs = 300, enableSuggestions: _enableSuggestions = true, errorClassName = '', inputClassName = '', inputWrapperClassName = '', minQueryLength = 2, noResultsClassName = '', onResultClick, onResults, onSearch, perPage = 10, placeholder = 'Search...', renderError, renderInput, renderNoResults, renderResult, renderResultsHeader, resultItemClassName = '', resultsClassName = '', resultsContainerClassName = '', resultsHeaderClassName = '', resultsListClassName = '', showLoading = true, showResultCount = true, showSearchTime = true, theme = 'modern' })=>{
4
+ const HeadlessSearchInput = ({ baseUrl, className = '', collection, collections, debounceMs = 300, enableSuggestions: _enableSuggestions = true, errorClassName = '', inputClassName = '', inputWrapperClassName = '', minQueryLength = 2, noResultsClassName = '', onResultClick, onResults, onSearch, perPage = 10, placeholder = 'Search...', renderDate = true, renderError, renderInput, renderMatchPercentage = true, renderNoResults, renderResult, renderResultsHeader, resultItemClassName = '', resultsClassName = '', resultsContainerClassName = '', resultsHeaderClassName = '', resultsListClassName = '', showLoading = true, showResultCount = true, showSearchTime = true, theme = 'modern' })=>{
5
5
  const [query, setQuery] = useState('');
6
6
  const [results, setResults] = useState(null);
7
7
  const [isLoading, setIsLoading] = useState(false);
@@ -11,7 +11,6 @@ const HeadlessSearchInput = ({ baseUrl, className = '', collection, collections,
11
11
  const resultsRef = useRef(null);
12
12
  const debounceRef = useRef(undefined);
13
13
  const collectionsRef = useRef(collections);
14
- // Theme configuration
15
14
  const themeConfig = useThemeConfig({
16
15
  theme: typeof theme === 'string' ? theme : theme.theme || 'modern',
17
16
  ...typeof theme === 'object' ? theme : {}
@@ -24,7 +23,6 @@ const HeadlessSearchInput = ({ baseUrl, className = '', collection, collections,
24
23
  collections
25
24
  ]);
26
25
  // Debounced search function
27
- // Use refs to avoid recreating functions on every render
28
26
  const onResultsRef = useRef(onResults);
29
27
  const onSearchRef = useRef(onSearch);
30
28
  onResultsRef.current = onResults;
@@ -55,7 +53,6 @@ const HeadlessSearchInput = ({ baseUrl, className = '', collection, collections,
55
53
  throw new Error(`Search failed: ${response.status} ${response.statusText}`);
56
54
  }
57
55
  searchResults = await response.json();
58
- // Filter results if specific collections were requested
59
56
  if (collectionsRef.current && collectionsRef.current.length > 0) {
60
57
  const filteredHits = searchResults.hits?.filter((hit)=>hit.collection && collectionsRef.current.includes(hit.collection)) || [];
61
58
  const filteredCollections = searchResults.collections?.filter((col)=>col.collection && collectionsRef.current.includes(col.collection)) || [];
@@ -81,7 +78,6 @@ const HeadlessSearchInput = ({ baseUrl, className = '', collection, collections,
81
78
  perPage,
82
79
  minQueryLength
83
80
  ]);
84
- // Debounced search effect
85
81
  useEffect(()=>{
86
82
  if (debounceRef.current) {
87
83
  clearTimeout(debounceRef.current);
@@ -225,12 +221,12 @@ const HeadlessSearchInput = ({ baseUrl, className = '', collection, collections,
225
221
  style: {
226
222
  alignItems: 'flex-start',
227
223
  display: 'flex',
228
- gap: '12px'
224
+ gap: '12px',
225
+ padding: '6px'
229
226
  }
230
227
  }, /*#__PURE__*/ React.createElement("div", {
231
228
  style: {
232
- flexShrink: 0,
233
- marginTop: '4px'
229
+ flexShrink: 0
234
230
  }
235
231
  }, /*#__PURE__*/ React.createElement("div", {
236
232
  style: {
@@ -261,7 +257,7 @@ const HeadlessSearchInput = ({ baseUrl, className = '', collection, collections,
261
257
  style: {
262
258
  color: themeConfig.theme.colors.titleText,
263
259
  fontFamily: themeConfig.theme.typography.fontFamily,
264
- fontSize: themeConfig.theme.typography.fontSizeLg,
260
+ fontSize: themeConfig.theme.typography.fontSizeBase,
265
261
  fontWeight: themeConfig.theme.typography.fontWeightSemibold,
266
262
  lineHeight: themeConfig.theme.typography.lineHeightTight,
267
263
  margin: 0,
@@ -269,7 +265,7 @@ const HeadlessSearchInput = ({ baseUrl, className = '', collection, collections,
269
265
  textOverflow: 'ellipsis',
270
266
  whiteSpace: 'nowrap'
271
267
  }
272
- }, result.document?.title || result.document?.name || result.title || 'Untitled'), typeof result.text_match === 'number' && !isNaN(result.text_match) && /*#__PURE__*/ React.createElement("span", {
268
+ }, result.document?.title || result.document?.name || result.title || 'Untitled'), renderMatchPercentage && typeof result.text_match === 'number' && !isNaN(result.text_match) && /*#__PURE__*/ React.createElement("span", {
273
269
  style: {
274
270
  alignItems: 'center',
275
271
  backgroundColor: themeConfig.theme.colors.scoreBadge,
@@ -289,6 +285,7 @@ const HeadlessSearchInput = ({ baseUrl, className = '', collection, collections,
289
285
  color: themeConfig.theme.colors.descriptionText,
290
286
  display: '-webkit-box',
291
287
  fontSize: themeConfig.theme.typography.fontSizeSm,
288
+ fontWeight: themeConfig.theme.typography.fontWeightNormal,
292
289
  lineHeight: themeConfig.theme.typography.lineHeightNormal,
293
290
  marginTop: '4px',
294
291
  overflow: 'hidden',
@@ -304,7 +301,7 @@ const HeadlessSearchInput = ({ baseUrl, className = '', collection, collections,
304
301
  gap: '12px',
305
302
  marginTop: '8px'
306
303
  }
307
- }, /*#__PURE__*/ React.createElement("span", {
304
+ }, result.collection && /*#__PURE__*/ React.createElement("span", {
308
305
  style: {
309
306
  alignItems: 'center',
310
307
  display: 'inline-flex'
@@ -321,7 +318,7 @@ const HeadlessSearchInput = ({ baseUrl, className = '', collection, collections,
321
318
  clipRule: "evenodd",
322
319
  d: "M3 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z",
323
320
  fillRule: "evenodd"
324
- })), result.collection), (result.document?.updatedAt || result.updatedAt) && /*#__PURE__*/ React.createElement("span", {
321
+ })), result.collection), renderDate && (result.document?.updatedAt || result.updatedAt) && /*#__PURE__*/ React.createElement("span", {
325
322
  style: {
326
323
  alignItems: 'center',
327
324
  display: 'inline-flex'
@@ -338,27 +335,7 @@ const HeadlessSearchInput = ({ baseUrl, className = '', collection, collections,
338
335
  clipRule: "evenodd",
339
336
  d: "M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z",
340
337
  fillRule: "evenodd"
341
- })), new Date(result.document?.updatedAt || result.updatedAt).toLocaleDateString()))), /*#__PURE__*/ React.createElement("div", {
342
- style: {
343
- flexShrink: 0,
344
- opacity: 0,
345
- transition: `opacity ${themeConfig.theme.animations.transitionNormal} ${themeConfig.theme.animations.easeInOut}`
346
- }
347
- }, /*#__PURE__*/ React.createElement("svg", {
348
- fill: "none",
349
- stroke: "currentColor",
350
- style: {
351
- color: themeConfig.theme.colors.metaText,
352
- height: '16px',
353
- width: '16px'
354
- },
355
- viewBox: "0 0 24 24"
356
- }, /*#__PURE__*/ React.createElement("path", {
357
- d: "M9 5l7 7-7 7",
358
- strokeLinecap: "round",
359
- strokeLinejoin: "round",
360
- strokeWidth: 2
361
- }))))));
338
+ })), new Date(result.document?.updatedAt || result.updatedAt).toLocaleDateString()))))));
362
339
  };
363
340
  const defaultRenderNoResults = (_query)=>/*#__PURE__*/ React.createElement("div", {
364
341
  className: `${noResultsClassName}`,
@@ -556,7 +533,12 @@ const HeadlessSearchInput = ({ baseUrl, className = '', collection, collections,
556
533
  top: '50%',
557
534
  transform: 'translateY(-50%)'
558
535
  }
559
- }, /*#__PURE__*/ React.createElement("div", {
536
+ }, /*#__PURE__*/ React.createElement("style", null, `
537
+ @keyframes spin {
538
+ 0% { transform: rotate(0deg); }
539
+ 100% { transform: rotate(360deg); }
540
+ }
541
+ `), /*#__PURE__*/ React.createElement("div", {
560
542
  "data-testid": "loading-spinner",
561
543
  style: {
562
544
  animation: `spin 1s linear infinite`,
@@ -566,7 +548,7 @@ const HeadlessSearchInput = ({ baseUrl, className = '', collection, collections,
566
548
  height: '16px',
567
549
  width: '16px'
568
550
  }
569
- }))), isOpen && /*#__PURE__*/ React.createElement("div", {
551
+ }))), isOpen && results && /*#__PURE__*/ React.createElement("div", {
570
552
  className: `${resultsContainerClassName}`,
571
553
  ref: resultsRef,
572
554
  style: {
@@ -580,6 +562,7 @@ const HeadlessSearchInput = ({ baseUrl, className = '', collection, collections,
580
562
  marginTop: '10px',
581
563
  maxHeight: themeConfig.theme.spacing.resultsMaxHeight,
582
564
  overflow: 'hidden',
565
+ padding: '4px',
583
566
  position: 'absolute',
584
567
  right: '0',
585
568
  top: '100%',
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../src/components/themes/hooks.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAYvE,QAAA,MAAM,YAAY,mDAAgD,CAAA;AAElE;;GAEG;AACH,wBAAgB,QAAQ,IAAI,iBAAiB,CAM5C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,iBAAiB,CAuBrE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAK1E;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CACjC,UAAU,EAAE,WAAW,EACvB,QAAQ,EAAE,OAAO,GACf,WAAW,CA8Bb;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC7B,WAAW,EAAE,WAAW,EACxB,UAAU,EAAE,WAAW,EACvB,MAAM,EAAE,OAAO,GACb,WAAW,CAIb;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAc3E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK,CAAC,QAAQ,CAAC,CAKnE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,CAKrE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK,CAAC,YAAY,CAAC,CAK3E;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,GAAG,OAAO,CAUjD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAM1E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC7B,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,OAAO,CAAC,WAAW,CAAC,GAC7B,WAAW,CAOb;AAGD,OAAO,EAAE,YAAY,EAAE,CAAA"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../src/components/themes/hooks.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAWvE,QAAA,MAAM,YAAY,mDAAgD,CAAA;AAElE;;GAEG;AACH,wBAAgB,QAAQ,IAAI,iBAAiB,CAM5C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,iBAAiB,CAuBrE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAK1E;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CACjC,UAAU,EAAE,WAAW,EACvB,QAAQ,EAAE,OAAO,GACf,WAAW,CA8Bb;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC7B,WAAW,EAAE,WAAW,EACxB,UAAU,EAAE,WAAW,EACvB,MAAM,EAAE,OAAO,GACb,WAAW,CAIb;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAc3E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK,CAAC,QAAQ,CAAC,CAKnE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,CAKrE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK,CAAC,YAAY,CAAC,CAK3E;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,GAAG,OAAO,CAUjD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAM1E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC7B,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,OAAO,CAAC,WAAW,CAAC,GAC7B,WAAW,CAOb;AAGD,OAAO,EAAE,YAAY,EAAE,CAAA"}
@@ -1,7 +1,6 @@
1
1
  "use client";
2
2
  import { createContext, useCallback, useContext, useMemo } from "react";
3
3
  import { applyTheme, generateThemeClasses, getThemeVariables, isDarkTheme, isLightTheme, mergeThemeConfig } from "./utils.js";
4
- // Theme context
5
4
  const ThemeContext = createContext(null);
6
5
  /**
7
6
  * Hook to access theme context
@@ -26,10 +26,10 @@ const baseTheme = {
26
26
  inputPadding: "0.5rem 0.75rem",
27
27
  itemBorderRadius: "0",
28
28
  itemMargin: "0",
29
- itemPadding: "0.75rem 1rem",
29
+ itemPadding: "0.5rem",
30
30
  metaFontSize: "0.75rem",
31
31
  metaPadding: "0.5rem 0",
32
- resultsBorderRadius: "0 0 0.5rem 0.5rem",
32
+ resultsBorderRadius: "0.5rem",
33
33
  resultsMaxHeight: "24rem",
34
34
  resultsPadding: "0"
35
35
  },
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/components/themes/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAIlE;;GAEG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,KAAK,CAEjD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK,CA2B3D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CACnC,KAAK,EAAE,KAAK,EACZ,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAC/B,YAAY,CAgXd;AAED;;GAEG;AACH,wBAAgB,UAAU,CACzB,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,GACd,MAAM,CAaR;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAMjD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAElD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgCtE"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/components/themes/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAIlE;;GAEG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,KAAK,CAEjD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK,CA2B3D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CACnC,KAAK,EAAE,KAAK,EACZ,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAC/B,YAAY,CAsWd;AAED;;GAEG;AACH,wBAAgB,UAAU,CACzB,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,GACd,MAAM,CAaR;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAMjD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAElD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgCtE"}
@@ -38,18 +38,15 @@ import { defaultTheme, themes } from "./themes.js";
38
38
  const enableAnimations = config.enableAnimations !== false;
39
39
  const enableShadows = config.enableShadows !== false;
40
40
  const enableRoundedCorners = config.enableRoundedCorners !== false;
41
- // Helper function to create CSS string
42
41
  const css = (styles)=>{
43
42
  return Object.entries(styles).map(([key, value])=>{
44
43
  if (typeof value === "object" && value !== null) {
45
- // Handle nested objects like ':hover', '::placeholder'
46
44
  const nested = Object.entries(value).map(([nestedKey, nestedValue])=>`${nestedKey.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${nestedValue}`).join("; ");
47
45
  return `${key.replace(/([A-Z])/g, "-$1").toLowerCase()}: { ${nested} }`;
48
46
  }
49
47
  return `${key.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${value}`;
50
48
  }).join("; ");
51
49
  };
52
- // Container styles
53
50
  const containerStyles = css({
54
51
  margin: "0 auto",
55
52
  maxWidth: "600px",
@@ -66,7 +63,8 @@ import { defaultTheme, themes } from "./themes.js";
66
63
  borderRadius: enableRoundedCorners ? theme.spacing.resultsBorderRadius : "0",
67
64
  boxShadow: enableShadows ? theme.shadows.shadowLg : "none",
68
65
  left: "0",
69
- marginTop: "4px",
66
+ marginBottom: "2px",
67
+ marginTop: "2px",
70
68
  maxHeight: theme.spacing.resultsMaxHeight,
71
69
  overflowY: "auto",
72
70
  position: "absolute",
@@ -77,7 +75,6 @@ import { defaultTheme, themes } from "./themes.js";
77
75
  animation: "slideDown 0.2s ease-out"
78
76
  }
79
77
  });
80
- // Input styles
81
78
  const inputStyles = css({
82
79
  "::placeholder": {
83
80
  color: theme.colors.inputPlaceholder
@@ -105,7 +102,6 @@ import { defaultTheme, themes } from "./themes.js";
105
102
  transition: enableAnimations ? `all ${theme.animations.transitionNormal} ${theme.animations.easeInOut}` : "none",
106
103
  width: "100%"
107
104
  });
108
- // Results styles
109
105
  const resultsStyles = css({
110
106
  backgroundColor: theme.colors.resultsBackground,
111
107
  border: `1px solid ${theme.colors.resultsBorder}`,
@@ -132,9 +128,8 @@ import { defaultTheme, themes } from "./themes.js";
132
128
  padding: theme.spacing.headerPadding
133
129
  });
134
130
  const resultsListStyles = css({
135
- padding: "8px 0"
131
+ padding: "4px"
136
132
  });
137
- // Result item styles
138
133
  const resultItemStyles = css({
139
134
  ":focus": {
140
135
  backgroundColor: theme.colors.resultBackgroundFocus,
@@ -154,7 +149,6 @@ import { defaultTheme, themes } from "./themes.js";
154
149
  padding: theme.spacing.itemPadding,
155
150
  transition: enableAnimations ? `all ${theme.animations.transitionFast} ${theme.animations.easeInOut}` : "none"
156
151
  });
157
- // Content styles
158
152
  const resultTitleStyles = css({
159
153
  color: theme.colors.titleText,
160
154
  fontFamily: theme.typography.fontFamily,
@@ -196,7 +190,6 @@ import { defaultTheme, themes } from "./themes.js";
196
190
  justifyContent: "space-between",
197
191
  marginTop: "8px"
198
192
  });
199
- // Badge styles
200
193
  const collectionBadgeStyles = css({
201
194
  backgroundColor: theme.colors.collectionBadge,
202
195
  borderRadius: enableRoundedCorners ? "12px" : "0",
@@ -219,7 +212,6 @@ import { defaultTheme, themes } from "./themes.js";
219
212
  fontWeight: theme.typography.fontWeightMedium,
220
213
  padding: "2px 6px"
221
214
  });
222
- // State styles
223
215
  const loadingStyles = css({
224
216
  alignItems: "center",
225
217
  color: theme.colors.loadingText,
@@ -256,7 +248,6 @@ import { defaultTheme, themes } from "./themes.js";
256
248
  padding: "40px 20px",
257
249
  textAlign: "center"
258
250
  });
259
- // Facet styles
260
251
  const facetContainerStyles = css({
261
252
  backgroundColor: theme.colors.headerBackground,
262
253
  borderBottom: `1px solid ${theme.colors.headerBorder}`,
@@ -286,7 +277,6 @@ import { defaultTheme, themes } from "./themes.js";
286
277
  borderColor: theme.colors.facetActiveBackground,
287
278
  color: theme.colors.facetActiveText
288
279
  });
289
- // Utility styles
290
280
  const hiddenStyles = css({
291
281
  display: "none"
292
282
  });
@@ -1 +1 @@
1
- {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/endpoints/health.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,KAAK,SAAS,MAAM,WAAW,CAAA;AAEtC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AA4CxD;;GAEG;AACH,eAAO,MAAM,wBAAwB,GACnC,iBAAiB,SAAS,CAAC,MAAM,EACjC,eAAe,qBAAqB,EACpC,eAAe,MAAM,KACpB,cA4DF,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,gCAAgC,GAC3C,iBAAiB,SAAS,CAAC,MAAM,EACjC,eAAe,qBAAqB,EACpC,eAAe,MAAM,KACpB,cAuFF,CAAA"}
1
+ {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/endpoints/health.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,KAAK,SAAS,MAAM,WAAW,CAAA;AAEtC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AA4CxD;;GAEG;AACH,eAAO,MAAM,wBAAwB,GACnC,iBAAiB,SAAS,CAAC,MAAM,EACjC,eAAe,qBAAqB,EACpC,eAAe,MAAM,KACpB,cA2DF,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,gCAAgC,GAC3C,iBAAiB,SAAS,CAAC,MAAM,EACjC,eAAe,qBAAqB,EACpC,eAAe,MAAM,KACpB,cAuFF,CAAA"}
@@ -43,8 +43,7 @@ import { searchCache } from '../lib/cache.js';
43
43
  const typesenseInfo = isTypesenseHealthy ? {
44
44
  ok: true,
45
45
  version: 'unknown'
46
- } // Typesense doesn't expose version in health check
47
- : {
46
+ } : {
48
47
  ok: false
49
48
  };
50
49
  // Get collection information
@@ -81,7 +80,6 @@ import { searchCache } from '../lib/cache.js';
81
80
  version: pkg.version
82
81
  });
83
82
  } catch (_error) {
84
- // Handle health check error
85
83
  const errorResponse = {
86
84
  cache: getCacheStats(),
87
85
  error: _error instanceof Error ? _error.message : 'Unknown error',
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/endpoints/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,KAAK,SAAS,MAAM,WAAW,CAAA;AAEtC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAgJxD,eAAO,MAAM,qBAAqB,GACjC,iBAAiB,SAAS,CAAC,MAAM,EACjC,eAAe,qBAAqB,EACpC,eAAe,MAAM;;;;;;;;IA+CrB,CAAA"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/endpoints/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,KAAK,SAAS,MAAM,WAAW,CAAA;AAEtC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AA0IxD,eAAO,MAAM,qBAAqB,GACjC,iBAAiB,SAAS,CAAC,MAAM,EACjC,eAAe,qBAAqB,EACpC,eAAe,MAAM;;;;;;;;IA+CrB,CAAA"}
@@ -4,7 +4,6 @@ import { createDetailedHealthCheckHandler, createHealthCheckHandler } from "./he
4
4
  // Universal search across all collections
5
5
  const searchAllCollections = async (typesenseClient, pluginOptions, query, options)=>{
6
6
  try {
7
- // Universal search logic
8
7
  // Check cache first
9
8
  const cachedResult = searchCache.get(query, "universal", options);
10
9
  if (cachedResult) {
@@ -35,7 +34,6 @@ const searchAllCollections = async (typesenseClient, pluginOptions, query, optio
35
34
  };
36
35
  // Search collection
37
36
  const results = await typesenseClient.collections(collectionName).documents().search(searchParameters);
38
- // Process results
39
37
  // Add collection metadata to each hit
40
38
  return {
41
39
  collection: collectionName,
package/dist/lib/cache.js CHANGED
@@ -3,8 +3,8 @@ export class SearchCache {
3
3
  defaultTTL;
4
4
  maxSize;
5
5
  constructor(options = {}){
6
- this.defaultTTL = options.ttl || 5 * 60 * 1000; // 5 minutes default
7
- this.maxSize = options.maxSize || 1000; // 1000 entries default
6
+ this.defaultTTL = options.ttl || 5 * 60 * 1000;
7
+ this.maxSize = options.maxSize || 1000;
8
8
  }
9
9
  /**
10
10
  * Generate cache key from search parameters
package/dist/lib/hooks.js CHANGED
@@ -37,7 +37,6 @@ const syncDocumentToTypesense = async (typesenseClient, collectionSlug, doc, ope
37
37
  await typesenseClient.collections(collectionSlug).documents().upsert(typesenseDoc);
38
38
  // Document synced successfully
39
39
  } catch (error) {
40
- // Handle document sync error
41
40
  // Log the problematic document for debugging
42
41
  if (error.message.includes("validation")) {
43
42
  // Log problematic document details
@@ -47,7 +46,6 @@ const syncDocumentToTypesense = async (typesenseClient, collectionSlug, doc, ope
47
46
  const deleteDocumentFromTypesense = async (typesenseClient, collectionSlug, docId)=>{
48
47
  try {
49
48
  await typesenseClient.collections(collectionSlug).documents(docId).delete();
50
- // Document deleted successfully
51
49
  } catch (_error) {
52
50
  // Handle document deletion error
53
51
  }
@@ -1 +1 @@
1
- {"version":3,"file":"initialization.d.ts","sourceRoot":"","sources":["../../src/lib/initialization.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,SAAS,MAAM,WAAW,CAAA;AAEtC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AASxD,eAAO,MAAM,8BAA8B,GAC1C,SAAS,OAAO,EAChB,iBAAiB,SAAS,CAAC,MAAM,EACjC,eAAe,qBAAqB,kBAwCpC,CAAA"}
1
+ {"version":3,"file":"initialization.d.ts","sourceRoot":"","sources":["../../src/lib/initialization.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,SAAS,MAAM,WAAW,CAAA;AAEtC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AASxD,eAAO,MAAM,8BAA8B,GAC1C,SAAS,OAAO,EAChB,iBAAiB,SAAS,CAAC,MAAM,EACjC,eAAe,qBAAqB,kBAiCpC,CAAA"}
@@ -5,14 +5,11 @@ export const initializeTypesenseCollections = async (payload, typesenseClient, p
5
5
  // Validate configuration first
6
6
  const validation = validateConfig(pluginOptions);
7
7
  if (!validation.success) {
8
- // Handle configuration validation error
9
8
  throw new Error("Invalid plugin configuration");
10
9
  }
11
- // Configuration validated successfully
12
10
  // Test Typesense connection first
13
11
  const isConnected = await testTypesenseConnection(typesenseClient);
14
12
  if (!isConnected) {
15
- // Typesense connection failed
16
13
  return;
17
14
  }
18
15
  // Initialize Typesense collections
@@ -27,29 +24,23 @@ export const initializeTypesenseCollections = async (payload, typesenseClient, p
27
24
  }
28
25
  }
29
26
  }
30
- // Collections initialized successfully
31
27
  };
32
28
  const initializeCollection = async (payload, typesenseClient, collectionSlug, config)=>{
33
29
  // Get the collection config from Payload
34
30
  const collection = payload.collections[collectionSlug];
35
31
  if (!collection) {
36
- // Collection not found in Payload
37
32
  return;
38
33
  }
39
34
  // Create Typesense schema
40
35
  const schema = mapCollectionToTypesenseSchema(collection, collectionSlug, config);
41
- // Create schema for collection
42
36
  try {
43
37
  // Check if collection exists
44
38
  await typesenseClient.collections(collectionSlug).retrieve();
45
- // Collection already exists
46
39
  } catch (_error) {
47
40
  // Collection doesn't exist, create it
48
41
  try {
49
42
  await typesenseClient.collections().create(schema);
50
- // Collection created successfully
51
43
  } catch (_createError) {
52
- // Handle collection creation error
53
44
  return;
54
45
  }
55
46
  }
@@ -64,7 +55,6 @@ const syncExistingDocuments = async (payload, typesenseClient, collectionSlug, c
64
55
  limit: 1000
65
56
  });
66
57
  if (docs.length === 0) {
67
- // No documents to sync
68
58
  return;
69
59
  }
70
60
  // Batch sync documents
@@ -76,12 +66,9 @@ const syncExistingDocuments = async (payload, typesenseClient, collectionSlug, c
76
66
  const _importResult = await typesenseClient.collections(collectionSlug).documents().import(typesenseDocs, {
77
67
  action: "upsert"
78
68
  });
79
- // Documents synced successfully
80
69
  } catch (batchError) {
81
- // Handle batch sync error
82
70
  // Log detailed import results if available
83
71
  if (batchError.importResults) {
84
- // Handle import results error
85
72
  // Try to sync documents individually to identify problematic ones
86
73
  // Attempt individual document sync
87
74
  for(let j = 0; j < typesenseDocs.length; j++){
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubixstudios/payload-typesense",
3
- "version": "1.0.10",
3
+ "version": "1.1.0",
4
4
  "description": "A production-ready search plugin that integrates Typesense with Payload CMS, offering fast, typo-tolerant search with real-time synchronization. This fork by Rubix Studios reduces bloat and introduces targeted changes for improved performance, maintainability, and flexibility.",
5
5
  "license": "MIT",
6
6
  "author": "Rubix Studios <hello@rubixstudios.com.au> (https://rubixstudios.com.au)",
@@ -63,9 +63,9 @@
63
63
  "@semantic-release/npm": "^12.0.2",
64
64
  "@swc/cli": "^0.7.8",
65
65
  "@swc/core": "^1.13.5",
66
- "@types/react": "19.1.16",
67
- "eslint": "^9.36.0",
68
- "payload": "3.58.0",
66
+ "@types/react": "19.2.2",
67
+ "eslint": "^9.37.0",
68
+ "payload": "3.59.1",
69
69
  "rimraf": "6.0.1",
70
70
  "semantic-release": "^24.2.9",
71
71
  "typescript": "5.9.3"
@@ -92,7 +92,7 @@
92
92
  },
93
93
  "dependencies": {
94
94
  "typesense": "^2.1.0",
95
- "zod": "^4.1.11"
95
+ "zod": "^4.1.12"
96
96
  },
97
- "packageManager": "pnpm@10.17.1"
97
+ "packageManager": "pnpm@10.18.2"
98
98
  }