Skip to content

Commit 2a74fc7

Browse files
author
Leonid Buneev
committed
1 parent 7b15c6b commit 2a74fc7

File tree

3 files changed

+88
-40
lines changed

3 files changed

+88
-40
lines changed

components/SearchBox.vue

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,18 @@
2525
@mouseenter="focus(i)"
2626
>
2727
<a :href="s.path + s.slug" @click.prevent>
28-
<div v-if="s.parentPageTitle" class="parent-page-title" v-html="highlight(s.parentPageTitle)" />
28+
<div v-if="s.parentPageTitle" class="parent-page-title" v-html="s.parentPageTitle" />
2929
<div class="suggestion-row">
3030
<div class="page-title">{{ s.title || s.path }}</div>
3131
<div class="suggestion-content">
32-
<div v-if="s.headingStr" class="header">{{ s.headingStr }}</div>
33-
<div v-if="s.contentStr">{{ s.contentStr }}</div>
32+
<!-- prettier-ignore -->
33+
<div v-if="s.headingStr" class="header">
34+
{{ s.headingDisplay.prefix }}<span class="highlight">{{ s.headingDisplay.highlightedContent }}</span>{{ s.headingDisplay.suffix }}
35+
</div>
36+
<!-- prettier-ignore -->
37+
<div v-if="s.contentStr">
38+
{{ s.contentDisplay.prefix }}<span class="highlight">{{ s.contentDisplay.highlightedContent }}</span>{{ s.contentDisplay.suffix }}
39+
</div>
3440
</div>
3541
</div>
3642
</a>
@@ -60,9 +66,8 @@ export default {
6066
computed: {
6167
queryTerms() {
6268
if (!this.query) return []
63-
const result = this.query
64-
.trim()
65-
.toLowerCase()
69+
const result = flexsearchSvc
70+
.normalizeString(this.query)
6671
.split(/[^\p{L}\p{N}_]+/iu)
6772
.filter(t => t)
6873
return result
@@ -102,28 +107,25 @@ export default {
102107
document.removeEventListener('keydown', this.onHotkey)
103108
},
104109
methods: {
105-
highlight(str) {
106-
if (!this.queryTerms.length) return str
107-
return str
108-
},
109110
async getSuggestions() {
110-
if (!this.query) return
111-
if (!this.queryTerms.length) {
111+
if (!this.query || !this.queryTerms.length) {
112112
this.suggestions = []
113113
return
114114
}
115-
const suggestions = await flexsearchSvc.match(
115+
let suggestions = await flexsearchSvc.match(
116116
this.query,
117117
this.queryTerms,
118118
this.$site.themeConfig.searchMaxSuggestions || SEARCH_MAX_SUGGESTIONS,
119119
)
120-
121-
// augment suggestions with user-provided function
122120
if (hooks.processSuggestions) {
123-
this.suggestions = await hooks.processSuggestions(suggestions, this.query, this.queryTerms)
124-
} else {
125-
this.suggestions = suggestions
121+
// augment suggestions with user-provided function
122+
suggestions = await hooks.processSuggestions(suggestions, this.query, this.queryTerms)
126123
}
124+
this.suggestions = suggestions.map(s => ({
125+
...s,
126+
headingDisplay: highlight(s.headingStr, s.headingHighlight),
127+
contentDisplay: highlight(s.contentStr, s.contentHighlight),
128+
}))
127129
},
128130
getPageLocalePath(page) {
129131
for (const localePath in this.$site.locales || {}) {
@@ -210,6 +212,20 @@ export default {
210212
},
211213
},
212214
}
215+
216+
function highlight(str, strHighlight) {
217+
if (!str) return {}
218+
if (!strHighlight) return { prefix: str }
219+
const [start, length] = strHighlight
220+
const end = start + length
221+
222+
const prefix = str.slice(0, start)
223+
const highlightedContent = str.slice(start, end)
224+
const suffix = str.slice(end)
225+
return { prefix, highlightedContent, suffix }
226+
227+
// return `${prefix}<span class="highlight">${highlightedContent}</span>${suffix}`
228+
}
213229
</script>
214230

215231
<style lang="stylus">
@@ -278,6 +294,8 @@ export default {
278294
padding 5px
279295
font-weight 600
280296
.suggestion-content
297+
.highlight
298+
text-decoration: underline
281299
border 1px solid $borderColor
282300
font-weight 400
283301
border-right none

index.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ module.exports = (options, ctx, globalCtx) => ({
2121

2222
for (const h of $page.headers || []) {
2323
const titlePlaintext = $page._context.markdown.renderInline(h.title)
24+
h.normalizedTitle = normalizeText(titlePlaintext)
2425
h.charIndex = plaintext.indexOf(titlePlaintext)
2526
if (h.charIndex === -1) h.charIndex = null
2627
}
2728
$page.headersStr = $page.headers ? $page.headers.map(h => h.title).join(' ') : null
2829
$page.content = plaintext
29-
$page.contentLowercase = plaintext.toLowerCase()
30+
$page.normalizedContent = normalizeText(plaintext)
31+
3032
$page.charsets = getCharsets(plaintext)
3133

3234
// Take title from sidebar if it's missing on the page itself
@@ -47,6 +49,13 @@ module.exports = (options, ctx, globalCtx) => ({
4749
},
4850
})
4951

52+
function normalizeText(text) {
53+
return text
54+
.toLowerCase()
55+
.normalize('NFD')
56+
.replace(/[\u0300-\u036f]/g, '')
57+
}
58+
5059
function getCustomTitles(globalCtx) {
5160
try {
5261
const sidebarConfig = _.get(globalCtx, '_pluginContext.themeConfig.sidebar')

services/flexsearchSvc.js

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export default {
8888
const result = searchResult.map(page => ({
8989
...page,
9090
parentPageTitle: getParentPageTitle(page),
91-
...getAdditionalInfo(page, queryString, queryTerms),
91+
...getAdditionalInfo(page, normalizeString(queryString), queryTerms),
9292
}))
9393

9494
const resultByParent = _.groupBy(result, 'parentPageTitle')
@@ -101,6 +101,7 @@ export default {
101101
)
102102
.flat()
103103
},
104+
normalizeString,
104105
}
105106

106107
function getParentPageTitle(page) {
@@ -117,15 +118,15 @@ function getAdditionalInfo(page, queryString, queryTerms) {
117118
const match = getMatch(page, query, queryTerms)
118119
if (!match)
119120
return {
120-
headingStr: getFullHeading(page),
121+
...getFullHeading(page),
121122
slug: '',
122123
contentStr: null,
123124
}
124125

125126
if (match.headerIndex != null) {
126127
// header match
127128
return {
128-
headingStr: getFullHeading(page, match.headerIndex),
129+
...getFullHeading(page, match.headerIndex, match),
129130
slug: '#' + page.headers[match.headerIndex].slug,
130131
contentStr: null,
131132
}
@@ -136,22 +137,27 @@ function getAdditionalInfo(page, queryString, queryTerms) {
136137
if (headerIndex === -1) headerIndex = null
137138

138139
return {
139-
headingStr: getFullHeading(page, headerIndex),
140+
...getFullHeading(page, headerIndex),
140141
slug: headerIndex == null ? '' : '#' + page.headers[headerIndex].slug,
141-
contentStr: getContentStr(page, match),
142+
...getContentStr(page, match),
142143
}
143144
}
144145

145-
function getFullHeading(page, headerIndex) {
146-
if (headerIndex == null) return page.title
146+
function getFullHeading(page, headerIndex, match) {
147+
if (headerIndex == null) return { headingStr: page.title }
147148
const headersPath = []
148149
while (headerIndex != null) {
149150
const header = page.headers[headerIndex]
150151
headersPath.unshift(header)
151152
headerIndex = _.findLastIndex(page.headers, h => h.level === header.level - 1, headerIndex - 1)
152153
if (headerIndex === -1) headerIndex = null
153154
}
154-
return headersPath.map(h => h.title).join(' > ')
155+
156+
const headingStr = headersPath.map(h => h.title).join(' > ')
157+
const prefixPath = headersPath.slice(0, -1)
158+
const prefixLength = _.sum(prefixPath.map(p => (p.title || '').length)) + prefixPath.length * 3
159+
const headingHighlight = match && match.headerIndex != null && [match.charIndex + prefixLength, match.termLength]
160+
return { headingStr, headingHighlight }
155161
}
156162

157163
function getMatch(page, query, terms) {
@@ -170,12 +176,10 @@ function getMatch(page, query, terms) {
170176
}
171177

172178
function getHeaderMatch(page, term) {
173-
// if (page.slug && page.slug.includes('versioning'))
174-
console.log(page)
175179
if (!page.headers) return null
176180
for (let i = 0; i < page.headers.length; i++) {
177181
const h = page.headers[i]
178-
const charIndex = h.title.toLowerCase().indexOf(term)
182+
const charIndex = h.normalizedTitle.indexOf(term)
179183
if (charIndex === -1) continue
180184
return {
181185
headerIndex: i,
@@ -187,8 +191,8 @@ function getHeaderMatch(page, term) {
187191
}
188192

189193
function getContentMatch(page, term) {
190-
if (!page.contentLowercase) return null
191-
const charIndex = page.contentLowercase.indexOf(term)
194+
if (!page.normalizedContent) return null
195+
const charIndex = page.normalizedContent.indexOf(term)
192196
if (charIndex === -1) return null
193197

194198
return { headerIndex: null, charIndex, termLength: term.length }
@@ -205,16 +209,33 @@ function getContentStr(page, match) {
205209
if (lineEndIndex === -1) lineEndIndex = page.content.length
206210

207211
const line = page.content.slice(lineStartIndex, lineEndIndex)
208-
209-
if (snippetLength >= line.length) return line
210-
211212
const lineCharIndex = charIndex - lineStartIndex
213+
const contentHighlight = [lineCharIndex, termLength]
212214

213-
const additionalCharactersFromStart = (snippetLength - termLength) / 2
215+
if (snippetLength >= line.length) return { contentStr: line, contentHighlight }
216+
217+
const additionalCharactersFromStart = _.round((snippetLength - termLength) / 2)
214218
const snippetStart = Math.max(lineCharIndex - additionalCharactersFromStart, 0)
215219
const snippetEnd = Math.min(snippetStart + snippetLength, line.length)
216-
let result = line.slice(snippetStart, snippetEnd)
217-
if (snippetStart > 0) result = '...' + result
218-
if (snippetEnd < line.length) result = result + '...'
219-
return result
220+
let contentStr = line.slice(snippetStart, snippetEnd)
221+
contentHighlight[0] = contentHighlight[0] - snippetStart
222+
223+
if (snippetStart > 0) {
224+
contentStr = '...' + contentStr
225+
contentHighlight[0] = contentHighlight[0] + 3
226+
}
227+
if (snippetEnd < line.length) contentStr = contentStr + '...'
228+
return {
229+
contentStr,
230+
contentHighlight,
231+
}
232+
}
233+
234+
function normalizeString(str) {
235+
if (!str) return str
236+
return str
237+
.trim()
238+
.toLowerCase()
239+
.normalize('NFD')
240+
.replace(/[\u0300-\u036f]/g, '')
220241
}

0 commit comments

Comments
 (0)