mirror of
https://github.com/next-theme/hexo-theme-next.git
synced 2026-01-18 18:33:42 +00:00
Optimize local search (#64)
* Highlight search keywords * Sort search results by the number of keywords included
This commit is contained in:
parent
1651043319
commit
0ebb0c5810
@ -3,7 +3,7 @@
|
|||||||
<i class="fa fa-search"></i>
|
<i class="fa fa-search"></i>
|
||||||
</span>
|
</span>
|
||||||
<div class="search-input-container">
|
<div class="search-input-container">
|
||||||
<input autocomplete="off" autocapitalize="off"
|
<input autocomplete="off" autocapitalize="off" maxlength="80"
|
||||||
placeholder="{{ __('search.placeholder') }}" spellcheck="false"
|
placeholder="{{ __('search.placeholder') }}" spellcheck="false"
|
||||||
type="search" class="search-input">
|
type="search" class="search-input">
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -9,7 +9,6 @@ const { parse } = require('url');
|
|||||||
*/
|
*/
|
||||||
hexo.extend.helper.register('next_config', function() {
|
hexo.extend.helper.register('next_config', function() {
|
||||||
const { config, theme, next_version } = this;
|
const { config, theme, next_version } = this;
|
||||||
config.algolia = config.algolia || {};
|
|
||||||
const exportConfig = {
|
const exportConfig = {
|
||||||
hostname : parse(config.url).hostname || config.url,
|
hostname : parse(config.url).hostname || config.url,
|
||||||
root : config.root,
|
root : config.root,
|
||||||
@ -24,19 +23,22 @@ hexo.extend.helper.register('next_config', function() {
|
|||||||
lazyload : theme.lazyload,
|
lazyload : theme.lazyload,
|
||||||
pangu : theme.pangu,
|
pangu : theme.pangu,
|
||||||
comments : theme.comments,
|
comments : theme.comments,
|
||||||
algolia : {
|
motion : theme.motion,
|
||||||
|
prism : config.prismjs.enable && !config.prismjs.preprocess
|
||||||
|
};
|
||||||
|
if (theme.algolia_search && theme.algolia_search.enable) {
|
||||||
|
config.algolia = config.algolia || {};
|
||||||
|
exportConfig.algolia = {
|
||||||
appID : config.algolia.applicationID,
|
appID : config.algolia.applicationID,
|
||||||
apiKey : config.algolia.apiKey,
|
apiKey : config.algolia.apiKey,
|
||||||
indexName: config.algolia.indexName,
|
indexName: config.algolia.indexName,
|
||||||
hits : theme.algolia_search.hits,
|
hits : theme.algolia_search.hits,
|
||||||
labels : theme.algolia_search.labels
|
labels : theme.algolia_search.labels
|
||||||
},
|
};
|
||||||
localsearch: theme.local_search,
|
}
|
||||||
motion : theme.motion,
|
|
||||||
prism : config.prismjs.enable && !config.prismjs.preprocess
|
|
||||||
};
|
|
||||||
if (config.search) {
|
if (config.search) {
|
||||||
exportConfig.path = config.search.path;
|
exportConfig.path = config.search.path;
|
||||||
|
exportConfig.localsearch = theme.local_search;
|
||||||
}
|
}
|
||||||
return `<script class="hexo-configurations">
|
return `<script class="hexo-configurations">
|
||||||
var NexT = window.NexT || {};
|
var NexT = window.NexT || {};
|
||||||
|
|||||||
@ -154,12 +154,6 @@ if (hexo-config('local_search.enable')) {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-keyword {
|
|
||||||
border-bottom: 1px dashed $red;
|
|
||||||
color: $red;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
#search-result {
|
#search-result {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: calc(100% - 55px);
|
height: calc(100% - 55px);
|
||||||
@ -172,4 +166,11 @@ if (hexo-config('local_search.enable')) {
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mark.search-keyword {
|
||||||
|
background: transparent;
|
||||||
|
border-bottom: 1px dashed $red;
|
||||||
|
color: $red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,52 +1,56 @@
|
|||||||
/* global CONFIG */
|
/* global CONFIG */
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
if (!CONFIG.path) {
|
||||||
|
console.warn('`hexo-generator-searchdb` plugin is not installed!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Popup Window
|
// Popup Window
|
||||||
let isfetched = false;
|
let isfetched = false;
|
||||||
let datas;
|
let datas;
|
||||||
let isXml = true;
|
|
||||||
// Search DB path
|
|
||||||
let searchPath = CONFIG.path;
|
|
||||||
if (searchPath.length === 0) {
|
|
||||||
searchPath = 'search.xml';
|
|
||||||
} else if (searchPath.endsWith('json')) {
|
|
||||||
isXml = false;
|
|
||||||
}
|
|
||||||
const input = document.querySelector('.search-input');
|
const input = document.querySelector('.search-input');
|
||||||
const resultContent = document.getElementById('search-result');
|
|
||||||
|
|
||||||
const getIndexByWord = (word, text, caseSensitive) => {
|
const getIndexByWord = (words, text, caseSensitive = false) => {
|
||||||
if (CONFIG.localsearch.unescape) {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.innerText = word;
|
|
||||||
word = div.innerHTML;
|
|
||||||
}
|
|
||||||
const wordLen = word.length;
|
|
||||||
if (wordLen === 0) return [];
|
|
||||||
let startPosition = 0;
|
|
||||||
let position = [];
|
|
||||||
const index = [];
|
const index = [];
|
||||||
if (!caseSensitive) {
|
const included = new Set();
|
||||||
text = text.toLowerCase();
|
words.forEach(word => {
|
||||||
word = word.toLowerCase();
|
if (CONFIG.localsearch.unescape) {
|
||||||
}
|
const div = document.createElement('div');
|
||||||
while ((position = text.indexOf(word, startPosition)) > -1) {
|
div.innerText = word;
|
||||||
index.push({ position, word });
|
word = div.innerHTML;
|
||||||
startPosition = position + wordLen;
|
}
|
||||||
}
|
const wordLen = word.length;
|
||||||
return index;
|
if (wordLen === 0) return;
|
||||||
|
let startPosition = 0;
|
||||||
|
let position = -1;
|
||||||
|
if (!caseSensitive) {
|
||||||
|
text = text.toLowerCase();
|
||||||
|
word = word.toLowerCase();
|
||||||
|
}
|
||||||
|
while ((position = text.indexOf(word, startPosition)) > -1) {
|
||||||
|
index.push({ position, word });
|
||||||
|
included.add(word);
|
||||||
|
startPosition = position + wordLen;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Sort index by position of keyword
|
||||||
|
index.sort((left, right) => {
|
||||||
|
if (left.position !== right.position) {
|
||||||
|
return left.position - right.position;
|
||||||
|
}
|
||||||
|
return right.word.length - left.word.length;
|
||||||
|
});
|
||||||
|
return [index, included];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Merge hits into slices
|
// Merge hits into slices
|
||||||
const mergeIntoSlice = (start, end, index, searchText) => {
|
const mergeIntoSlice = (start, end, index) => {
|
||||||
let item = index[index.length - 1];
|
let item = index[0];
|
||||||
let { position, word } = item;
|
let { position, word } = item;
|
||||||
const hits = [];
|
const hits = [];
|
||||||
let searchTextCountInSlice = 0;
|
const count = new Set();
|
||||||
while (position + word.length <= end && index.length !== 0) {
|
while (position + word.length <= end && index.length !== 0) {
|
||||||
if (word === searchText) {
|
count.add(word);
|
||||||
searchTextCountInSlice++;
|
|
||||||
}
|
|
||||||
hits.push({
|
hits.push({
|
||||||
position,
|
position,
|
||||||
length: word.length
|
length: word.length
|
||||||
@ -54,13 +58,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const wordEnd = position + word.length;
|
const wordEnd = position + word.length;
|
||||||
|
|
||||||
// Move to next position of hit
|
// Move to next position of hit
|
||||||
index.pop();
|
index.shift();
|
||||||
while (index.length !== 0) {
|
while (index.length !== 0) {
|
||||||
item = index[index.length - 1];
|
item = index[0];
|
||||||
position = item.position;
|
position = item.position;
|
||||||
word = item.word;
|
word = item.word;
|
||||||
if (wordEnd > position) {
|
if (wordEnd > position) {
|
||||||
index.pop();
|
index.shift();
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -70,136 +74,114 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
hits,
|
hits,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
searchTextCount: searchTextCountInSlice
|
count: count.size
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Highlight title and content
|
// Highlight title and content
|
||||||
const highlightKeyword = (text, slice) => {
|
const highlightKeyword = (val, slice) => {
|
||||||
let result = '';
|
let result = '';
|
||||||
let prevEnd = slice.start;
|
let index = slice.start;
|
||||||
slice.hits.forEach(hit => {
|
for (const { position, length } of slice.hits) {
|
||||||
result += text.substring(prevEnd, hit.position);
|
result += val.substring(index, position);
|
||||||
const end = hit.position + hit.length;
|
index = position + length;
|
||||||
result += `<b class="search-keyword">${text.substring(hit.position, end)}</b>`;
|
result += `<mark class="search-keyword">${val.substr(position, length)}</mark>`;
|
||||||
prevEnd = end;
|
}
|
||||||
});
|
result += val.substring(index, slice.end);
|
||||||
result += text.substring(prevEnd, slice.end);
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getResultItems = keywords => {
|
||||||
|
const resultItems = [];
|
||||||
|
datas.forEach(({ title, content, url }) => {
|
||||||
|
// The number of different keywords included in the article.
|
||||||
|
const [indexOfTitle, keysOfTitle] = getIndexByWord(keywords, title);
|
||||||
|
const [indexOfContent, keysOfContent] = getIndexByWord(keywords, content);
|
||||||
|
const includedCount = new Set([...keysOfTitle, ...keysOfContent]).size;
|
||||||
|
|
||||||
|
// Show search results
|
||||||
|
const hitCount = indexOfTitle.length + indexOfContent.length;
|
||||||
|
if (hitCount === 0) return;
|
||||||
|
|
||||||
|
const slicesOfTitle = [];
|
||||||
|
if (indexOfTitle.length !== 0) {
|
||||||
|
slicesOfTitle.push(mergeIntoSlice(0, title.length, indexOfTitle));
|
||||||
|
}
|
||||||
|
|
||||||
|
let slicesOfContent = [];
|
||||||
|
while (indexOfContent.length !== 0) {
|
||||||
|
const item = indexOfContent[0];
|
||||||
|
const { position } = item;
|
||||||
|
// Cut out 100 characters. The maxlength of .search-input is 80.
|
||||||
|
const start = Math.max(0, position - 20);
|
||||||
|
const end = Math.min(content.length, position + 80);
|
||||||
|
slicesOfContent.push(mergeIntoSlice(start, end, indexOfContent));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort slices in content by included keywords' count and hits' count
|
||||||
|
slicesOfContent.sort((left, right) => {
|
||||||
|
if (left.count !== right.count) {
|
||||||
|
return right.count - left.count;
|
||||||
|
} else if (left.hits.length !== right.hits.length) {
|
||||||
|
return right.hits.length - left.hits.length;
|
||||||
|
}
|
||||||
|
return left.start - right.start;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Select top N slices in content
|
||||||
|
const upperBound = parseInt(CONFIG.localsearch.top_n_per_article, 10);
|
||||||
|
if (upperBound >= 0) {
|
||||||
|
slicesOfContent = slicesOfContent.slice(0, upperBound);
|
||||||
|
}
|
||||||
|
|
||||||
|
let resultItem = '';
|
||||||
|
|
||||||
|
url = new URL(url, location.origin);
|
||||||
|
url.searchParams.append('highlight', keywords.join(' '));
|
||||||
|
|
||||||
|
if (slicesOfTitle.length !== 0) {
|
||||||
|
resultItem += `<li><a href="${url.href}" class="search-result-title">${highlightKeyword(title, slicesOfTitle[0])}</a>`;
|
||||||
|
} else {
|
||||||
|
resultItem += `<li><a href="${url.href}" class="search-result-title">${title}</a>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
slicesOfContent.forEach(slice => {
|
||||||
|
resultItem += `<a href="${url.href}"><p class="search-result">${highlightKeyword(content, slice)}...</p></a>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
resultItem += '</li>';
|
||||||
|
resultItems.push({
|
||||||
|
item: resultItem,
|
||||||
|
id : resultItems.length,
|
||||||
|
hitCount,
|
||||||
|
includedCount
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return resultItems;
|
||||||
|
};
|
||||||
|
|
||||||
const inputEventFunction = () => {
|
const inputEventFunction = () => {
|
||||||
if (!isfetched) return;
|
if (!isfetched) return;
|
||||||
const searchText = input.value.trim().toLowerCase();
|
const searchText = input.value.trim().toLowerCase();
|
||||||
const keywords = searchText.split(/[-\s]+/);
|
const keywords = searchText.split(/[-\s]+/);
|
||||||
if (keywords.length > 1) {
|
const resultContent = document.getElementById('search-result');
|
||||||
keywords.push(searchText);
|
let resultItems = [];
|
||||||
}
|
|
||||||
const resultItems = [];
|
|
||||||
if (searchText.length > 0) {
|
if (searchText.length > 0) {
|
||||||
// Perform local searching
|
// Perform local searching
|
||||||
datas.forEach(({ title, content, url }) => {
|
resultItems = getResultItems(keywords);
|
||||||
const titleInLowerCase = title.toLowerCase();
|
|
||||||
const contentInLowerCase = content.toLowerCase();
|
|
||||||
let indexOfTitle = [];
|
|
||||||
let indexOfContent = [];
|
|
||||||
let searchTextCount = 0;
|
|
||||||
keywords.forEach(keyword => {
|
|
||||||
indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));
|
|
||||||
indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show search results
|
|
||||||
if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
|
|
||||||
const hitCount = indexOfTitle.length + indexOfContent.length;
|
|
||||||
// Sort index by position of keyword
|
|
||||||
[indexOfTitle, indexOfContent].forEach(index => {
|
|
||||||
index.sort((itemLeft, itemRight) => {
|
|
||||||
if (itemRight.position !== itemLeft.position) {
|
|
||||||
return itemRight.position - itemLeft.position;
|
|
||||||
}
|
|
||||||
return itemLeft.word.length - itemRight.word.length;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const slicesOfTitle = [];
|
|
||||||
if (indexOfTitle.length !== 0) {
|
|
||||||
const tmp = mergeIntoSlice(0, title.length, indexOfTitle, searchText);
|
|
||||||
searchTextCount += tmp.searchTextCountInSlice;
|
|
||||||
slicesOfTitle.push(tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
let slicesOfContent = [];
|
|
||||||
while (indexOfContent.length !== 0) {
|
|
||||||
const item = indexOfContent[indexOfContent.length - 1];
|
|
||||||
const { position, word } = item;
|
|
||||||
// Cut out 100 characters
|
|
||||||
let start = position - 20;
|
|
||||||
let end = position + 80;
|
|
||||||
if (start < 0) {
|
|
||||||
start = 0;
|
|
||||||
}
|
|
||||||
if (end < position + word.length) {
|
|
||||||
end = position + word.length;
|
|
||||||
}
|
|
||||||
if (end > content.length) {
|
|
||||||
end = content.length;
|
|
||||||
}
|
|
||||||
const tmp = mergeIntoSlice(start, end, indexOfContent, searchText);
|
|
||||||
searchTextCount += tmp.searchTextCountInSlice;
|
|
||||||
slicesOfContent.push(tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort slices in content by search text's count and hits' count
|
|
||||||
slicesOfContent.sort((sliceLeft, sliceRight) => {
|
|
||||||
if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
|
|
||||||
return sliceRight.searchTextCount - sliceLeft.searchTextCount;
|
|
||||||
} else if (sliceLeft.hits.length !== sliceRight.hits.length) {
|
|
||||||
return sliceRight.hits.length - sliceLeft.hits.length;
|
|
||||||
}
|
|
||||||
return sliceLeft.start - sliceRight.start;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Select top N slices in content
|
|
||||||
const upperBound = parseInt(CONFIG.localsearch.top_n_per_article, 10);
|
|
||||||
if (upperBound >= 0) {
|
|
||||||
slicesOfContent = slicesOfContent.slice(0, upperBound);
|
|
||||||
}
|
|
||||||
|
|
||||||
let resultItem = '';
|
|
||||||
|
|
||||||
if (slicesOfTitle.length !== 0) {
|
|
||||||
resultItem += `<li><a href="${url}" class="search-result-title">${highlightKeyword(title, slicesOfTitle[0])}</a>`;
|
|
||||||
} else {
|
|
||||||
resultItem += `<li><a href="${url}" class="search-result-title">${title}</a>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
slicesOfContent.forEach(slice => {
|
|
||||||
resultItem += `<a href="${url}"><p class="search-result">${highlightKeyword(content, slice)}...</p></a>`;
|
|
||||||
});
|
|
||||||
|
|
||||||
resultItem += '</li>';
|
|
||||||
resultItems.push({
|
|
||||||
item: resultItem,
|
|
||||||
id : resultItems.length,
|
|
||||||
hitCount,
|
|
||||||
searchTextCount
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (keywords.length === 1 && keywords[0] === '') {
|
if (keywords.length === 1 && keywords[0] === '') {
|
||||||
resultContent.innerHTML = '<div id="no-result"><i class="fa fa-search fa-5x"></i></div>';
|
resultContent.innerHTML = '<div id="no-result"><i class="fa fa-search fa-5x"></i></div>';
|
||||||
} else if (resultItems.length === 0) {
|
} else if (resultItems.length === 0) {
|
||||||
resultContent.innerHTML = '<div id="no-result"><i class="far fa-frown fa-5x"></i></div>';
|
resultContent.innerHTML = '<div id="no-result"><i class="far fa-frown fa-5x"></i></div>';
|
||||||
} else {
|
} else {
|
||||||
resultItems.sort((resultLeft, resultRight) => {
|
resultItems.sort((left, right) => {
|
||||||
if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
|
if (left.includedCount !== right.includedCount) {
|
||||||
return resultRight.searchTextCount - resultLeft.searchTextCount;
|
return right.includedCount - left.includedCount;
|
||||||
} else if (resultLeft.hitCount !== resultRight.hitCount) {
|
} else if (left.hitCount !== right.hitCount) {
|
||||||
return resultRight.hitCount - resultLeft.hitCount;
|
return right.hitCount - left.hitCount;
|
||||||
}
|
}
|
||||||
return resultRight.id - resultLeft.id;
|
return right.id - left.id;
|
||||||
});
|
});
|
||||||
resultContent.innerHTML = `<ul class="search-result-list">${resultItems.map(result => result.item).join('')}</ul>`;
|
resultContent.innerHTML = `<ul class="search-result-list">${resultItems.map(result => result.item).join('')}</ul>`;
|
||||||
window.pjax && window.pjax.refresh(resultContent);
|
window.pjax && window.pjax.refresh(resultContent);
|
||||||
@ -207,7 +189,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fetchData = () => {
|
const fetchData = () => {
|
||||||
fetch(CONFIG.root + searchPath)
|
// Search DB path
|
||||||
|
const searchPath = CONFIG.root + CONFIG.path;
|
||||||
|
const isXml = !CONFIG.path.endsWith('json');
|
||||||
|
fetch(searchPath)
|
||||||
.then(response => response.text())
|
.then(response => response.text())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
// Get the contents from search data
|
// Get the contents from search data
|
||||||
@ -227,11 +212,68 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
return data;
|
return data;
|
||||||
});
|
});
|
||||||
// Remove loading animation
|
// Remove loading animation
|
||||||
document.getElementById('no-result').innerHTML = '<i class="fa fa-search fa-5x"></i>';
|
|
||||||
inputEventFunction();
|
inputEventFunction();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function returns the parsed url parameters of the
|
||||||
|
* current request. Multiple values per key are supported,
|
||||||
|
* it will always return arrays of strings for the value parts.
|
||||||
|
*/
|
||||||
|
const getQueryParameters = () => {
|
||||||
|
const s = location.search;
|
||||||
|
const parts = s.substr(s.indexOf('?') + 1).split('&');
|
||||||
|
const result = {};
|
||||||
|
for (const part of parts) {
|
||||||
|
const [key, value] = part.split('=', 2);
|
||||||
|
if (key in result) {
|
||||||
|
result[key].push(value);
|
||||||
|
} else {
|
||||||
|
result[key] = [value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Highlight by wrapping node in mark elements with the given class name
|
||||||
|
const highlightText = (node, slice, className) => {
|
||||||
|
const val = node.nodeValue;
|
||||||
|
let index = slice.start;
|
||||||
|
const children = [];
|
||||||
|
for (const { position, length } of slice.hits) {
|
||||||
|
const text = document.createTextNode(val.substring(index, position));
|
||||||
|
index = position + length;
|
||||||
|
const mark = document.createElement('mark');
|
||||||
|
mark.className = className;
|
||||||
|
mark.appendChild(document.createTextNode(val.substr(position, length)));
|
||||||
|
children.push(text, mark);
|
||||||
|
}
|
||||||
|
node.nodeValue = val.substring(index, slice.end);
|
||||||
|
children.forEach(element => {
|
||||||
|
node.parentNode.insertBefore(element, node);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Highlight the search words provided in the url in the text
|
||||||
|
const highlightSearchWords = () => {
|
||||||
|
const params = getQueryParameters();
|
||||||
|
const keywords = params.highlight ? params.highlight[0].split(/\+/).map(decodeURIComponent) : [];
|
||||||
|
const body = document.querySelector('.post-body');
|
||||||
|
if (!keywords.length || !body) return;
|
||||||
|
const walk = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, null, false);
|
||||||
|
const allNodes = [];
|
||||||
|
while (walk.nextNode()) {
|
||||||
|
if (!walk.currentNode.parentNode.matches('button, select, textarea')) allNodes.push(walk.currentNode);
|
||||||
|
}
|
||||||
|
allNodes.forEach(node => {
|
||||||
|
const [indexOfNode] = getIndexByWord(keywords, node.nodeValue);
|
||||||
|
if (!indexOfNode.length) return;
|
||||||
|
const slice = mergeIntoSlice(0, node.nodeValue.length, indexOfNode);
|
||||||
|
highlightText(node, slice, 'search-keyword');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
if (CONFIG.localsearch.preload) {
|
if (CONFIG.localsearch.preload) {
|
||||||
fetchData();
|
fetchData();
|
||||||
}
|
}
|
||||||
@ -268,7 +310,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose);
|
document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose);
|
||||||
document.addEventListener('pjax:success', onPopupClose);
|
document.addEventListener('pjax:success', () => {
|
||||||
|
highlightSearchWords();
|
||||||
|
onPopupClose();
|
||||||
|
});
|
||||||
window.addEventListener('keyup', event => {
|
window.addEventListener('keyup', event => {
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
onPopupClose();
|
onPopupClose();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user