Skip to content

Commit 0902be4

Browse files
authored
Implement Crop (#50)
* Add vanilla-JS playground * Remove vanilla playground * Add crop handling * Add ... to crop * Refacto * Add desctructuration
1 parent 4cb4826 commit 0902be4

File tree

4 files changed

+115
-37
lines changed

4 files changed

+115
-37
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,6 @@ dist
102102

103103
# TernJS port file
104104
.tern-port
105+
106+
# misc
107+
.DS_Store

playgrounds/vue/src/App.vue

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
<ais-highlight :hit="item" attribute="name"/>
3535
</div>
3636
<img :src="item.image" align="left" :alt="item.image">
37+
<div class="hit-description">
38+
<ais-snippet :hit="item" attribute="description"/>
39+
</div>
3740
<div class="hit-info">price: {{item.price}}</div>
3841
<div class="hit-info">release date: {{item.releaseDate}}</div>
3942
</div>
@@ -44,6 +47,8 @@
4447

4548
<ais-configure
4649
:hits-per-page.camel="6"
50+
:attributesToSnippet="['description:50']"
51+
snippetEllipsisText=""
4752
/>
4853
</div>
4954
</ais-instant-search>
@@ -60,10 +65,7 @@ export default {
6065
return {
6166
searchClient: instantMeiliSearch(
6267
"https://demos.meilisearch.com",
63-
"dc3fedaf922de8937fdea01f0a7d59557f1fd31832cb8440ce94231cfdde7f25",
64-
{
65-
paginationTotalHits: 60
66-
}
68+
"dc3fedaf922de8937fdea01f0a7d59557f1fd31832cb8440ce94231cfdde7f25"
6769
)
6870
};
6971
}
@@ -94,7 +96,7 @@ body {
9496
margin-bottom: 0.5em;
9597
}
9698
97-
.ais-Highlight-highlighted {
99+
.ais-Highlight-highlighted, .ais-Snippet-highlighted {
98100
background: cyan;
99101
font-style: normal;
100102
}
@@ -107,6 +109,12 @@ body {
107109
margin-bottom: 0.5em;
108110
}
109111
112+
.hit-description {
113+
font-size: 90%;
114+
margin-bottom: 0.5em;
115+
color: grey;
116+
}
117+
110118
.hit-info {
111119
font-size: 90%;
112120
}

src/format.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { isString } from './utils.js'
2+
3+
function replaceHighlightTags(value, highlightPreTag, highlightPostTag) {
4+
let newHighlightValue = value || ''
5+
// If the value of the attribute is a string,
6+
// the highlight is applied by MeiliSearch (<em> tags)
7+
// and we replace the <em> by the expected tag for InstantSearch
8+
if (isString(value)) {
9+
newHighlightValue = value
10+
.replace(/<em>/g, highlightPreTag)
11+
.replace(/<\/em>/g, highlightPostTag)
12+
}
13+
return newHighlightValue.toString()
14+
}
15+
16+
function createHighlighResult({
17+
formattedHit,
18+
highlightPreTag,
19+
highlightPostTag,
20+
}) {
21+
// formattedHit is the `_formatted` object returned by MeiliSearch.
22+
// It contains all the highlighted and croped attributes
23+
return Object.keys(formattedHit).reduce((result, key) => {
24+
result[key] = {
25+
value: replaceHighlightTags(
26+
formattedHit[key],
27+
highlightPreTag,
28+
highlightPostTag
29+
),
30+
}
31+
return result
32+
}, {})
33+
}
34+
35+
function snippetFinalValue(
36+
value,
37+
snippetEllipsisText,
38+
highlightPreTag,
39+
highlightPostTag
40+
) {
41+
let newValue = value
42+
// manage a kind of `...` for the crop until this issue is solved: https://github.com/meilisearch/MeiliSearch/issues/923
43+
// `...` is put if we are at the middle of a sentence (instead at the middle of the document field)
44+
if (snippetEllipsisText !== undefined && isString(newValue)) {
45+
if (
46+
newValue[0] === newValue[0].toLowerCase() && // beginning of a sentence
47+
newValue.startsWith('<em>') === false // beginning of the document field, otherwise MeiliSearch would crop around the highligh
48+
) {
49+
newValue = `${snippetEllipsisText}${newValue}`
50+
}
51+
if (!!newValue.match(/[.!?]$/) === false) {
52+
// end of the sentence
53+
newValue = `${newValue}${snippetEllipsisText}`
54+
}
55+
}
56+
return replaceHighlightTags(newValue, highlightPreTag, highlightPostTag)
57+
}
58+
59+
function createSnippetResult({
60+
formattedHit,
61+
attributesToSnippet,
62+
snippetEllipsisText,
63+
highlightPreTag,
64+
highlightPostTag,
65+
}) {
66+
if (attributesToSnippet === undefined) {
67+
return null
68+
}
69+
attributesToSnippet = attributesToSnippet.map(
70+
(attribute) => attribute.split(':')[0]
71+
)
72+
// formattedHit is the `_formatted` object returned by MeiliSearch.
73+
// It contains all the highlighted and croped attributes
74+
return Object.keys(formattedHit).reduce((result, key) => {
75+
if (attributesToSnippet.includes(key)) {
76+
result[key] = {
77+
value: snippetFinalValue(
78+
formattedHit[key],
79+
snippetEllipsisText,
80+
highlightPreTag,
81+
highlightPostTag
82+
),
83+
}
84+
}
85+
return result
86+
}, {})
87+
}
88+
89+
export { createHighlighResult, createSnippetResult }

src/index.js

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import MeiliSearch from 'meilisearch'
2-
import { isString, removeUndefinedFromObject } from './utils.js'
2+
import { removeUndefinedFromObject } from './utils.js'
3+
import { createHighlighResult, createSnippetResult } from './format.js'
34

45
export default function instantMeiliSearch(hostUrl, apiKey, options = {}) {
56
return {
@@ -12,38 +13,18 @@ export default function instantMeiliSearch(hostUrl, apiKey, options = {}) {
1213
const limit = this.pagination // if pagination widget is set, use paginationTotalHits as limit
1314
? this.paginationTotalHits
1415
: this.hitsPerPage
16+
const { query, facets, facetFilters, attributesToSnippet } = params
1517
const searchInput = {
16-
q: this.placeholderSearch && params.query === '' ? null : params.query,
17-
facetsDistribution: params.facets.length ? params.facets : undefined,
18-
facetFilters: params.facetFilters,
18+
q: this.placeholderSearch && query === '' ? null : query,
19+
facetsDistribution: facets.length ? facets : undefined,
20+
facetFilters: facetFilters,
1921
attributesToHighlight: this.attributesToHighlight,
22+
attributesToCrop: attributesToSnippet,
2023
limit,
2124
}
2225
return removeUndefinedFromObject(searchInput)
2326
},
2427

25-
replaceHighlightTags: function (
26-
formattedHit,
27-
highlightPreTag,
28-
highlightPostTag
29-
) {
30-
// formattedHit is the `_formatted` object returned by MeiliSearch.
31-
// It contains all the highlighted attributes
32-
return Object.keys(formattedHit).reduce((result, key) => {
33-
let newHighlightString = formattedHit[key] || ''
34-
// If the value of the attribute is a string,
35-
// the highlight is applied by MeiliSearch (<em> tags)
36-
// and we replace the <em> by the expected tag for InstantSearch
37-
if (isString(formattedHit[key])) {
38-
newHighlightString = formattedHit[key]
39-
.replace(/<em>/g, highlightPreTag)
40-
.replace(/<\/em>/g, highlightPostTag)
41-
}
42-
result[key] = { value: newHighlightString.toString() }
43-
return result
44-
}, {})
45-
},
46-
4728
parseHits: function (meiliSearchHits, params) {
4829
if (this.pagination) {
4930
const start = params.page * this.hitsPerPage
@@ -55,11 +36,8 @@ export default function instantMeiliSearch(hostUrl, apiKey, options = {}) {
5536
delete hit._formatted
5637
return {
5738
...hit,
58-
_highlightResult: this.replaceHighlightTags(
59-
formattedHit,
60-
params.highlightPreTag,
61-
params.highlightPostTag
62-
),
39+
_highlightResult: createHighlighResult({ formattedHit, ...params }),
40+
_snippetResult: createSnippetResult({ formattedHit, ...params }),
6341
}
6442
})
6543
},
@@ -108,7 +86,7 @@ export default function instantMeiliSearch(hostUrl, apiKey, options = {}) {
10886
// Params got from InstantSearch
10987
const params = requests[0].params
11088
this.pagination = params.page !== undefined // If the pagination widget has been set
111-
this.hitsPerPage = params.hitsPerPage || 20 // 20 is the MeiliSearch's default limit value. It can be changed with `InsantSearch.configure`.
89+
this.hitsPerPage = params.hitsPerPage || 20 // 20 is the MeiliSearch's default limit value. `hitsPerPage` can be changed with `InsantSearch.configure`.
11290
// Gets information from IS and transforms it for MeiliSearch
11391
const searchInput = this.transformToMeiliSearchParams(params)
11492
const indexUid = requests[0].indexName

0 commit comments

Comments
 (0)