From f8fa1f2779153a549bfce436d7bb714d1157a6cd Mon Sep 17 00:00:00 2001
From: Mimi <1119186082@qq.com>
Date: Tue, 28 Jul 2020 15:23:33 +0800
Subject: [PATCH 01/16] Update
---
source/js/local-search.js | 267 ++++++++++++++++++++++++--------------
1 file changed, 172 insertions(+), 95 deletions(-)
diff --git a/source/js/local-search.js b/source/js/local-search.js
index 5e5ae1550..05e88fac1 100644
--- a/source/js/local-search.js
+++ b/source/js/local-search.js
@@ -81,125 +81,131 @@ document.addEventListener('DOMContentLoaded', () => {
slice.hits.forEach(hit => {
result += text.substring(prevEnd, hit.position);
const end = hit.position + hit.length;
- result += `${text.substring(hit.position, end)}`;
+ result += `${text.substring(hit.position, end)}`;
prevEnd = end;
});
result += text.substring(prevEnd, slice.end);
return result;
};
- const inputEventFunction = () => {
- if (!isfetched) return;
- const searchText = input.value.trim().toLowerCase();
- const keywords = searchText.split(/[-\s]+/);
- if (keywords.length > 1) {
- keywords.push(searchText);
- }
+ const getResultItems = (searchText, keywords) => {
const resultItems = [];
- if (searchText.length > 0) {
- // Perform local searching
- datas.forEach(({ title, content, url }) => {
- 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;
- });
- });
+ console.log(searchText)
+ datas.forEach(({ title, content, url }) => {
+ 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));
+ });
- const slicesOfTitle = [];
- if (indexOfTitle.length !== 0) {
- const tmp = mergeIntoSlice(0, title.length, indexOfTitle, searchText);
- searchTextCount += tmp.searchTextCountInSlice;
- slicesOfTitle.push(tmp);
+ // Show search results
+ if (indexOfTitle.length === 0 && indexOfContent.length === 0) return;
+ const hitCount = indexOfTitle.length + indexOfContent.length;
+ // Sort index by position of keyword
+ [indexOfTitle, indexOfContent].forEach(index => {
+ index.sort((left, right) => {
+ if (right.position !== left.position) {
+ return right.position - left.position;
}
+ return left.word.length - right.word.length;
+ });
+ });
- 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);
- }
+ const slicesOfTitle = [];
+ if (indexOfTitle.length !== 0) {
+ const tmp = mergeIntoSlice(0, title.length, indexOfTitle, searchText);
+ searchTextCount += tmp.searchTextCountInSlice;
+ slicesOfTitle.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;
- });
+ 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);
+ }
- // Select top N slices in content
- const upperBound = parseInt(CONFIG.localsearch.top_n_per_article, 10);
- if (upperBound >= 0) {
- slicesOfContent = slicesOfContent.slice(0, upperBound);
- }
+ // Sort slices in content by search text's count and hits' count
+ slicesOfContent.sort((left, right) => {
+ if (left.searchTextCount !== right.searchTextCount) {
+ return right.searchTextCount - left.searchTextCount;
+ } else if (left.hits.length !== right.hits.length) {
+ return right.hits.length - left.hits.length;
+ }
+ return left.start - right.start;
+ });
- let resultItem = '';
+ // Select top N slices in content
+ const upperBound = parseInt(CONFIG.localsearch.top_n_per_article, 10);
+ if (upperBound >= 0) {
+ slicesOfContent = slicesOfContent.slice(0, upperBound);
+ }
- if (slicesOfTitle.length !== 0) {
- resultItem += `
${highlightKeyword(title, slicesOfTitle[0])}`;
- } else {
- resultItem += `${title}`;
- }
+ let resultItem = '';
- slicesOfContent.forEach(slice => {
- resultItem += `${highlightKeyword(content, slice)}...
`;
- });
+ if (slicesOfTitle.length !== 0) {
+ resultItem += `${highlightKeyword(title, slicesOfTitle[0])}`;
+ } else {
+ resultItem += `${title}`;
+ }
- resultItem += '';
- resultItems.push({
- item: resultItem,
- id : resultItems.length,
- hitCount,
- searchTextCount
- });
- }
+ slicesOfContent.forEach(slice => {
+ resultItem += `${highlightKeyword(content, slice)}...
`;
+ });
+
+ resultItem += '';
+ resultItems.push({
+ item: resultItem,
+ id : resultItems.length,
+ hitCount,
+ searchTextCount
});
+ });
+ return resultItems;
+ }
+
+ const inputEventFunction = () => {
+ if (!isfetched) return;
+ const searchText = input.value.trim().toLowerCase();
+ const keywords = searchText.split(/[-\s]+/);
+ if (keywords.length > 1) {
+ keywords.push(searchText);
+ }
+ let resultItems = [];
+ if (searchText.length > 0) {
+ // Perform local searching
+ resultItems = getResultItems(searchText, keywords);
}
if (keywords.length === 1 && keywords[0] === '') {
resultContent.innerHTML = '
';
} else if (resultItems.length === 0) {
resultContent.innerHTML = '
';
} else {
- resultItems.sort((resultLeft, resultRight) => {
- if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
- return resultRight.searchTextCount - resultLeft.searchTextCount;
- } else if (resultLeft.hitCount !== resultRight.hitCount) {
- return resultRight.hitCount - resultLeft.hitCount;
+ resultItems.sort((left, right) => {
+ if (left.searchTextCount !== right.searchTextCount) {
+ return right.searchTextCount - left.searchTextCount;
+ } else if (left.hitCount !== right.hitCount) {
+ return right.hitCount - left.hitCount;
}
- return resultRight.id - resultLeft.id;
+ return right.id - left.id;
});
resultContent.innerHTML = `${resultItems.map(result => result.item).join('')}
`;
window.pjax && window.pjax.refresh(resultContent);
@@ -232,6 +238,77 @@ document.addEventListener('DOMContentLoaded', () => {
});
};
+ /**
+ * small helper function to urldecode strings
+ */
+ const urldecode = x => {
+ return decodeURIComponent(x).replace(/\+/g, ' ');
+ };
+
+ /**
+ * 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 parts = location.search.split('?')[1].split('&');
+ const result = {};
+ for (let part of parts) {
+ let [key, value] = part.split('=', 2);
+ key = urldecode(key);
+ value = urldecode(value);
+ if (key in result) {
+ result[key].push(value);
+ } else {
+ result[key] = [value];
+ }
+ }
+ return result;
+ };
+
+ /**
+ * highlight a given string on a jquery object by wrapping it in
+ * span elements with the given class name.
+ */
+ const highlightText = (element, terms, className) => {
+ let text;
+ terms.forEach(term => {
+ text = term.toLowerCase()
+ });
+ const highlight = node => {
+ const val = node.nodeValue;
+ const pos = val.toLowerCase().indexOf(text);
+ if (pos >= 0) {
+ const span = document.createElement("mark");
+ span.className = className;
+ span.appendChild(document.createTextNode(val.substr(pos, text.length)));
+ const next = document.createTextNode(val.substr(pos + text.length));
+ node.parentNode.insertBefore(span, node.parentNode.insertBefore(next, node.nextSibling));
+ node.nodeValue = val.substr(0, pos);
+ highlight(next);
+ }
+ }
+ if (!element.parentNode.matches("button, select, textarea")) highlight(element);
+ };
+
+ /**
+ * highlight the search words provided in the url in the text
+ */
+ window.highlightSearchWords = () => {
+ const params = getQueryParameters();
+ const terms = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
+ const body = document.querySelector('.post-body');
+ if (!terms.length || !body) return;
+ const walk = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, null, false);
+ const allNodes = [];
+ while (walk.nextNode()) {
+ allNodes.push(walk.currentNode);
+ }
+ allNodes.forEach(node => {
+ highlightText(node, terms, 'search-keyword');
+ });
+ };
+
if (CONFIG.localsearch.preload) {
fetchData();
}
From d30faa87b5c890afdb17088514d3938bb988149a Mon Sep 17 00:00:00 2001
From: Mimi <1119186082@qq.com>
Date: Tue, 28 Jul 2020 15:24:12 +0800
Subject: [PATCH 02/16] Style
---
.../css/_common/components/third-party/search.styl | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/source/css/_common/components/third-party/search.styl b/source/css/_common/components/third-party/search.styl
index ff75552b7..0673cbb9c 100644
--- a/source/css/_common/components/third-party/search.styl
+++ b/source/css/_common/components/third-party/search.styl
@@ -154,12 +154,6 @@ if (hexo-config('local_search.enable')) {
font-weight: bold;
}
- .search-keyword {
- border-bottom: 1px dashed $red;
- color: $red;
- font-weight: bold;
- }
-
#search-result {
display: flex;
height: calc(100% - 55px);
@@ -172,4 +166,11 @@ if (hexo-config('local_search.enable')) {
margin: auto;
}
}
+
+ mark.search-keyword {
+ background: transparent;
+ border-bottom: 1px dashed $red;
+ color: $red;
+ font-weight: bold;
+ }
}
From 5adbf86822c1a694ec64b3a3d0f1eab4d262698f Mon Sep 17 00:00:00 2001
From: Mimi <1119186082@qq.com>
Date: Tue, 28 Jul 2020 15:48:12 +0800
Subject: [PATCH 03/16] Fix
---
source/js/local-search.js | 37 +++++++++++++++++--------------------
1 file changed, 17 insertions(+), 20 deletions(-)
diff --git a/source/js/local-search.js b/source/js/local-search.js
index 05e88fac1..f5b1417d7 100644
--- a/source/js/local-search.js
+++ b/source/js/local-search.js
@@ -24,7 +24,7 @@ document.addEventListener('DOMContentLoaded', () => {
const wordLen = word.length;
if (wordLen === 0) return [];
let startPosition = 0;
- let position = [];
+ let position = -1;
const index = [];
if (!caseSensitive) {
text = text.toLowerCase();
@@ -42,10 +42,10 @@ document.addEventListener('DOMContentLoaded', () => {
let item = index[index.length - 1];
let { position, word } = item;
const hits = [];
- let searchTextCountInSlice = 0;
+ let searchTextCount = 0;
while (position + word.length <= end && index.length !== 0) {
if (word === searchText) {
- searchTextCountInSlice++;
+ searchTextCount++;
}
hits.push({
position,
@@ -70,7 +70,7 @@ document.addEventListener('DOMContentLoaded', () => {
hits,
start,
end,
- searchTextCount: searchTextCountInSlice
+ searchTextCount
};
};
@@ -90,35 +90,32 @@ document.addEventListener('DOMContentLoaded', () => {
const getResultItems = (searchText, keywords) => {
const resultItems = [];
- console.log(searchText)
datas.forEach(({ title, content, url }) => {
- 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));
+ indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, title, false));
+ indexOfContent = indexOfContent.concat(getIndexByWord(keyword, content, false));
});
// Show search results
- if (indexOfTitle.length === 0 && indexOfContent.length === 0) return;
const hitCount = indexOfTitle.length + indexOfContent.length;
+ if (hitCount === 0) return;
// Sort index by position of keyword
- [indexOfTitle, indexOfContent].forEach(index => {
- index.sort((left, right) => {
- if (right.position !== left.position) {
- return right.position - left.position;
- }
- return left.word.length - right.word.length;
- });
- });
+ const compare = (left, right) => {
+ if (right.position !== left.position) {
+ return right.position - left.position;
+ }
+ return left.word.length - right.word.length;
+ };
+ indexOfTitle.sort(compare);
+ indexOfContent.sort(compare);
const slicesOfTitle = [];
if (indexOfTitle.length !== 0) {
const tmp = mergeIntoSlice(0, title.length, indexOfTitle, searchText);
- searchTextCount += tmp.searchTextCountInSlice;
+ searchTextCount += tmp.searchTextCount;
slicesOfTitle.push(tmp);
}
@@ -139,7 +136,7 @@ document.addEventListener('DOMContentLoaded', () => {
end = content.length;
}
const tmp = mergeIntoSlice(start, end, indexOfContent, searchText);
- searchTextCount += tmp.searchTextCountInSlice;
+ searchTextCount += tmp.searchTextCount;
slicesOfContent.push(tmp);
}
From 2501c710712308329c0351b0200357d3669eefd9 Mon Sep 17 00:00:00 2001
From: Mimi <1119186082@qq.com>
Date: Tue, 28 Jul 2020 16:29:29 +0800
Subject: [PATCH 04/16] Rename to count
---
source/js/local-search.js | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/source/js/local-search.js b/source/js/local-search.js
index f5b1417d7..cf60d2136 100644
--- a/source/js/local-search.js
+++ b/source/js/local-search.js
@@ -42,10 +42,10 @@ document.addEventListener('DOMContentLoaded', () => {
let item = index[index.length - 1];
let { position, word } = item;
const hits = [];
- let searchTextCount = 0;
+ let count = 0;
while (position + word.length <= end && index.length !== 0) {
if (word === searchText) {
- searchTextCount++;
+ count++;
}
hits.push({
position,
@@ -70,7 +70,7 @@ document.addEventListener('DOMContentLoaded', () => {
hits,
start,
end,
- searchTextCount
+ count
};
};
@@ -115,7 +115,7 @@ document.addEventListener('DOMContentLoaded', () => {
const slicesOfTitle = [];
if (indexOfTitle.length !== 0) {
const tmp = mergeIntoSlice(0, title.length, indexOfTitle, searchText);
- searchTextCount += tmp.searchTextCount;
+ searchTextCount += tmp.count;
slicesOfTitle.push(tmp);
}
@@ -136,14 +136,14 @@ document.addEventListener('DOMContentLoaded', () => {
end = content.length;
}
const tmp = mergeIntoSlice(start, end, indexOfContent, searchText);
- searchTextCount += tmp.searchTextCount;
+ searchTextCount += tmp.count;
slicesOfContent.push(tmp);
}
// Sort slices in content by search text's count and hits' count
slicesOfContent.sort((left, right) => {
- if (left.searchTextCount !== right.searchTextCount) {
- return right.searchTextCount - left.searchTextCount;
+ 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;
}
From ff01f29c179c32da739b05b7848479a44abc42e6 Mon Sep 17 00:00:00 2001
From: Mimi <1119186082@qq.com>
Date: Tue, 28 Jul 2020 18:20:47 +0800
Subject: [PATCH 05/16] Reverse index
---
source/js/local-search.js | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/source/js/local-search.js b/source/js/local-search.js
index cf60d2136..889f015c7 100644
--- a/source/js/local-search.js
+++ b/source/js/local-search.js
@@ -39,7 +39,7 @@ document.addEventListener('DOMContentLoaded', () => {
// Merge hits into slices
const mergeIntoSlice = (start, end, index, searchText) => {
- let item = index[index.length - 1];
+ let item = index[0];
let { position, word } = item;
const hits = [];
let count = 0;
@@ -54,13 +54,13 @@ document.addEventListener('DOMContentLoaded', () => {
const wordEnd = position + word.length;
// Move to next position of hit
- index.pop();
+ index.shift();
while (index.length !== 0) {
- item = index[index.length - 1];
+ item = index[0];
position = item.position;
word = item.word;
if (wordEnd > position) {
- index.pop();
+ index.shift();
} else {
break;
}
@@ -104,10 +104,10 @@ document.addEventListener('DOMContentLoaded', () => {
if (hitCount === 0) return;
// Sort index by position of keyword
const compare = (left, right) => {
- if (right.position !== left.position) {
- return right.position - left.position;
+ if (left.position !== right.position) {
+ return left.position - right.position;
}
- return left.word.length - right.word.length;
+ return right.word.length - left.word.length;
};
indexOfTitle.sort(compare);
indexOfContent.sort(compare);
@@ -121,7 +121,7 @@ document.addEventListener('DOMContentLoaded', () => {
let slicesOfContent = [];
while (indexOfContent.length !== 0) {
- const item = indexOfContent[indexOfContent.length - 1];
+ const item = indexOfContent[0];
const { position, word } = item;
// Cut out 100 characters
let start = position - 20;
From 86e7955caef2b2154bcbbba07fc8e56fae5c2c34 Mon Sep 17 00:00:00 2001
From: Mimi <1119186082@qq.com>
Date: Tue, 28 Jul 2020 21:43:53 +0800
Subject: [PATCH 06/16] Sort by includedCount
---
layout/_partials/search/localsearch.njk | 2 +-
source/js/local-search.js | 57 +++++++++----------------
2 files changed, 21 insertions(+), 38 deletions(-)
diff --git a/layout/_partials/search/localsearch.njk b/layout/_partials/search/localsearch.njk
index df81e95d3..26931e6c7 100644
--- a/layout/_partials/search/localsearch.njk
+++ b/layout/_partials/search/localsearch.njk
@@ -3,7 +3,7 @@
-
diff --git a/source/js/local-search.js b/source/js/local-search.js
index 889f015c7..c2a6f3c35 100644
--- a/source/js/local-search.js
+++ b/source/js/local-search.js
@@ -38,15 +38,11 @@ document.addEventListener('DOMContentLoaded', () => {
};
// Merge hits into slices
- const mergeIntoSlice = (start, end, index, searchText) => {
+ const mergeIntoSlice = (start, end, index) => {
let item = index[0];
let { position, word } = item;
const hits = [];
- let count = 0;
while (position + word.length <= end && index.length !== 0) {
- if (word === searchText) {
- count++;
- }
hits.push({
position,
length: word.length
@@ -69,8 +65,7 @@ document.addEventListener('DOMContentLoaded', () => {
return {
hits,
start,
- end,
- count
+ end
};
};
@@ -88,15 +83,19 @@ document.addEventListener('DOMContentLoaded', () => {
return result;
};
- const getResultItems = (searchText, keywords) => {
+ const getResultItems = (keywords) => {
const resultItems = [];
datas.forEach(({ title, content, url }) => {
let indexOfTitle = [];
let indexOfContent = [];
- let searchTextCount = 0;
+ // The number of different keywords included in the article.
+ let includedCount = 0;
keywords.forEach(keyword => {
- indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, title, false));
- indexOfContent = indexOfContent.concat(getIndexByWord(keyword, content, false));
+ const titleIndex = getIndexByWord(keyword, title, false);
+ const contentIndex = getIndexByWord(keyword, content, false);
+ if (titleIndex.length + contentIndex.length > 0) includedCount++;
+ indexOfTitle = indexOfTitle.concat(titleIndex);
+ indexOfContent = indexOfContent.concat(contentIndex);
});
// Show search results
@@ -114,30 +113,17 @@ document.addEventListener('DOMContentLoaded', () => {
const slicesOfTitle = [];
if (indexOfTitle.length !== 0) {
- const tmp = mergeIntoSlice(0, title.length, indexOfTitle, searchText);
- searchTextCount += tmp.count;
- slicesOfTitle.push(tmp);
+ slicesOfTitle.push(mergeIntoSlice(0, title.length, indexOfTitle));
}
let slicesOfContent = [];
while (indexOfContent.length !== 0) {
const item = indexOfContent[0];
- 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.count;
- slicesOfContent.push(tmp);
+ 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 search text's count and hits' count
@@ -173,7 +159,7 @@ document.addEventListener('DOMContentLoaded', () => {
item: resultItem,
id : resultItems.length,
hitCount,
- searchTextCount
+ includedCount
});
});
return resultItems;
@@ -183,13 +169,10 @@ document.addEventListener('DOMContentLoaded', () => {
if (!isfetched) return;
const searchText = input.value.trim().toLowerCase();
const keywords = searchText.split(/[-\s]+/);
- if (keywords.length > 1) {
- keywords.push(searchText);
- }
let resultItems = [];
if (searchText.length > 0) {
// Perform local searching
- resultItems = getResultItems(searchText, keywords);
+ resultItems = getResultItems(keywords);
}
if (keywords.length === 1 && keywords[0] === '') {
resultContent.innerHTML = '
';
@@ -197,8 +180,8 @@ document.addEventListener('DOMContentLoaded', () => {
resultContent.innerHTML = '
';
} else {
resultItems.sort((left, right) => {
- if (left.searchTextCount !== right.searchTextCount) {
- return right.searchTextCount - left.searchTextCount;
+ if (left.includedCount !== right.includedCount) {
+ return right.includedCount - left.includedCount;
} else if (left.hitCount !== right.hitCount) {
return right.hitCount - left.hitCount;
}
From bd2385767c85884f4a9454e55c9738ef40451482 Mon Sep 17 00:00:00 2001
From: Mimi <1119186082@qq.com>
Date: Tue, 28 Jul 2020 22:27:12 +0800
Subject: [PATCH 07/16] Jobs done
---
source/js/local-search.js | 64 +++++++++++++++++++++------------------
1 file changed, 35 insertions(+), 29 deletions(-)
diff --git a/source/js/local-search.js b/source/js/local-search.js
index c2a6f3c35..14f1de17c 100644
--- a/source/js/local-search.js
+++ b/source/js/local-search.js
@@ -37,6 +37,14 @@ document.addEventListener('DOMContentLoaded', () => {
return index;
};
+ // Sort index by position of keyword
+ const compare = (left, right) => {
+ if (left.position !== right.position) {
+ return left.position - right.position;
+ }
+ return right.word.length - left.word.length;
+ };
+
// Merge hits into slices
const mergeIntoSlice = (start, end, index) => {
let item = index[0];
@@ -101,13 +109,7 @@ document.addEventListener('DOMContentLoaded', () => {
// Show search results
const hitCount = indexOfTitle.length + indexOfContent.length;
if (hitCount === 0) return;
- // Sort index by position of keyword
- const compare = (left, right) => {
- if (left.position !== right.position) {
- return left.position - right.position;
- }
- return right.word.length - left.word.length;
- };
+
indexOfTitle.sort(compare);
indexOfContent.sort(compare);
@@ -250,25 +252,22 @@ document.addEventListener('DOMContentLoaded', () => {
* highlight a given string on a jquery object by wrapping it in
* span elements with the given class name.
*/
- const highlightText = (element, terms, className) => {
- let text;
- terms.forEach(term => {
- text = term.toLowerCase()
- });
- const highlight = node => {
- const val = node.nodeValue;
- const pos = val.toLowerCase().indexOf(text);
- if (pos >= 0) {
- const span = document.createElement("mark");
- span.className = className;
- span.appendChild(document.createTextNode(val.substr(pos, text.length)));
- const next = document.createTextNode(val.substr(pos + text.length));
- node.parentNode.insertBefore(span, node.parentNode.insertBefore(next, node.nextSibling));
- node.nodeValue = val.substr(0, pos);
- highlight(next);
- }
+ const highlightText = (node, hits, className) => {
+ const val = node.nodeValue;
+ let index = 0;
+ const children = [];
+ for (let { position, length } of hits) {
+ const text = document.createTextNode(val.substr(index, position - index));
+ index = position + length;
+ const mark = document.createElement("mark");
+ mark.className = className;
+ mark.appendChild(document.createTextNode(val.substr(position, length)));
+ children.push(text, mark);
}
- if (!element.parentNode.matches("button, select, textarea")) highlight(element);
+ node.nodeValue = val.substr(index, val.length);
+ children.forEach(element => {
+ node.parentNode.insertBefore(element, node);
+ });
};
/**
@@ -276,16 +275,23 @@ document.addEventListener('DOMContentLoaded', () => {
*/
window.highlightSearchWords = () => {
const params = getQueryParameters();
- const terms = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
+ const keywords = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
const body = document.querySelector('.post-body');
- if (!terms.length || !body) return;
+ if (!keywords.length || !body) return;
const walk = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, null, false);
const allNodes = [];
while (walk.nextNode()) {
- allNodes.push(walk.currentNode);
+ if (!walk.currentNode.parentNode.matches("button, select, textarea")) allNodes.push(walk.currentNode);
}
allNodes.forEach(node => {
- highlightText(node, terms, 'search-keyword');
+ let indexOfNode = [];
+ keywords.forEach(keyword => {
+ const nodeIndex = getIndexByWord(keyword, node.nodeValue, false);
+ indexOfNode = indexOfNode.concat(nodeIndex);
+ });
+ if (!indexOfNode.length) return;
+ const { hits } = mergeIntoSlice(0, node.nodeValue.length, indexOfNode);
+ highlightText(node, hits, 'search-keyword');
});
};
From 57998b56be2af3242571b4e79791d7e8a3c1e392 Mon Sep 17 00:00:00 2001
From: Mimi <1119186082@qq.com>
Date: Tue, 28 Jul 2020 22:34:38 +0800
Subject: [PATCH 08/16] Fix eslint
---
source/js/local-search.js | 26 ++++++++++----------------
1 file changed, 10 insertions(+), 16 deletions(-)
diff --git a/source/js/local-search.js b/source/js/local-search.js
index 14f1de17c..26ef2830e 100644
--- a/source/js/local-search.js
+++ b/source/js/local-search.js
@@ -165,7 +165,7 @@ document.addEventListener('DOMContentLoaded', () => {
});
});
return resultItems;
- }
+ };
const inputEventFunction = () => {
if (!isfetched) return;
@@ -220,9 +220,7 @@ document.addEventListener('DOMContentLoaded', () => {
});
};
- /**
- * small helper function to urldecode strings
- */
+ // Small helper function to urldecode strings
const urldecode = x => {
return decodeURIComponent(x).replace(/\+/g, ' ');
};
@@ -235,7 +233,7 @@ document.addEventListener('DOMContentLoaded', () => {
const getQueryParameters = () => {
const parts = location.search.split('?')[1].split('&');
const result = {};
- for (let part of parts) {
+ for (const part of parts) {
let [key, value] = part.split('=', 2);
key = urldecode(key);
value = urldecode(value);
@@ -248,18 +246,15 @@ document.addEventListener('DOMContentLoaded', () => {
return result;
};
- /**
- * highlight a given string on a jquery object by wrapping it in
- * span elements with the given class name.
- */
+ // Highlight by wrapping node in mark elements with the given class name
const highlightText = (node, hits, className) => {
const val = node.nodeValue;
let index = 0;
const children = [];
- for (let { position, length } of hits) {
+ for (const { position, length } of hits) {
const text = document.createTextNode(val.substr(index, position - index));
index = position + length;
- const mark = document.createElement("mark");
+ const mark = document.createElement('mark');
mark.className = className;
mark.appendChild(document.createTextNode(val.substr(position, length)));
children.push(text, mark);
@@ -270,18 +265,16 @@ document.addEventListener('DOMContentLoaded', () => {
});
};
- /**
- * highlight the search words provided in the url in the text
- */
+ // Highlight the search words provided in the url in the text
window.highlightSearchWords = () => {
const params = getQueryParameters();
- const keywords = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
+ const keywords = params.highlight ? params.highlight[0].split(/\s+/) : [];
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);
+ if (!walk.currentNode.parentNode.matches('button, select, textarea')) allNodes.push(walk.currentNode);
}
allNodes.forEach(node => {
let indexOfNode = [];
@@ -290,6 +283,7 @@ document.addEventListener('DOMContentLoaded', () => {
indexOfNode = indexOfNode.concat(nodeIndex);
});
if (!indexOfNode.length) return;
+ indexOfNode.sort(compare);
const { hits } = mergeIntoSlice(0, node.nodeValue.length, indexOfNode);
highlightText(node, hits, 'search-keyword');
});
From 90cc90b858744b5488e2f516f2de4cba60e9b5f8 Mon Sep 17 00:00:00 2001
From: Mimi <1119186082@qq.com>
Date: Tue, 28 Jul 2020 23:12:49 +0800
Subject: [PATCH 09/16] Reuse
---
source/js/local-search.js | 86 +++++++++++++++++----------------------
1 file changed, 38 insertions(+), 48 deletions(-)
diff --git a/source/js/local-search.js b/source/js/local-search.js
index 26ef2830e..aaf02b730 100644
--- a/source/js/local-search.js
+++ b/source/js/local-search.js
@@ -15,34 +15,39 @@ document.addEventListener('DOMContentLoaded', () => {
const input = document.querySelector('.search-input');
const resultContent = document.getElementById('search-result');
- const getIndexByWord = (word, text, caseSensitive) => {
- 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 = -1;
- const index = [];
- if (!caseSensitive) {
- text = text.toLowerCase();
- word = word.toLowerCase();
- }
- while ((position = text.indexOf(word, startPosition)) > -1) {
- index.push({ position, word });
- startPosition = position + wordLen;
- }
- return index;
- };
+ const getIndexByWord = (words, text, caseSensitive = false) => {
+ // Sort index by position of keyword
+ const compare = (left, right) => {
+ if (left.position !== right.position) {
+ return left.position - right.position;
+ }
+ return right.word.length - left.word.length;
+ };
- // Sort index by position of keyword
- const compare = (left, right) => {
- if (left.position !== right.position) {
- return left.position - right.position;
- }
- return right.word.length - left.word.length;
+ const index = [];
+ const included = new Set();
+ words.forEach(word => {
+ 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 = -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;
+ }
+ });
+ index.sort(compare);
+ return [index, included];
};
// Merge hits into slices
@@ -94,25 +99,15 @@ document.addEventListener('DOMContentLoaded', () => {
const getResultItems = (keywords) => {
const resultItems = [];
datas.forEach(({ title, content, url }) => {
- let indexOfTitle = [];
- let indexOfContent = [];
// The number of different keywords included in the article.
- let includedCount = 0;
- keywords.forEach(keyword => {
- const titleIndex = getIndexByWord(keyword, title, false);
- const contentIndex = getIndexByWord(keyword, content, false);
- if (titleIndex.length + contentIndex.length > 0) includedCount++;
- indexOfTitle = indexOfTitle.concat(titleIndex);
- indexOfContent = indexOfContent.concat(contentIndex);
- });
+ 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;
- indexOfTitle.sort(compare);
- indexOfContent.sort(compare);
-
const slicesOfTitle = [];
if (indexOfTitle.length !== 0) {
slicesOfTitle.push(mergeIntoSlice(0, title.length, indexOfTitle));
@@ -252,14 +247,14 @@ document.addEventListener('DOMContentLoaded', () => {
let index = 0;
const children = [];
for (const { position, length } of hits) {
- const text = document.createTextNode(val.substr(index, position - index));
+ 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.substr(index, val.length);
+ node.nodeValue = val.substr(index);
children.forEach(element => {
node.parentNode.insertBefore(element, node);
});
@@ -277,13 +272,8 @@ document.addEventListener('DOMContentLoaded', () => {
if (!walk.currentNode.parentNode.matches('button, select, textarea')) allNodes.push(walk.currentNode);
}
allNodes.forEach(node => {
- let indexOfNode = [];
- keywords.forEach(keyword => {
- const nodeIndex = getIndexByWord(keyword, node.nodeValue, false);
- indexOfNode = indexOfNode.concat(nodeIndex);
- });
+ const [indexOfNode] = getIndexByWord(keywords, node.nodeValue);
if (!indexOfNode.length) return;
- indexOfNode.sort(compare);
const { hits } = mergeIntoSlice(0, node.nodeValue.length, indexOfNode);
highlightText(node, hits, 'search-keyword');
});
From 98d921be71782cdc3c281d8dad4912dcd62fb606 Mon Sep 17 00:00:00 2001
From: Mimi <1119186082@qq.com>
Date: Tue, 28 Jul 2020 23:45:32 +0800
Subject: [PATCH 10/16] Parse URL
---
source/js/local-search.js | 21 ++++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)
diff --git a/source/js/local-search.js b/source/js/local-search.js
index aaf02b730..5544b906c 100644
--- a/source/js/local-search.js
+++ b/source/js/local-search.js
@@ -96,7 +96,7 @@ document.addEventListener('DOMContentLoaded', () => {
return result;
};
- const getResultItems = (keywords) => {
+ const getResultItems = keywords => {
const resultItems = [];
datas.forEach(({ title, content, url }) => {
// The number of different keywords included in the article.
@@ -141,14 +141,17 @@ document.addEventListener('DOMContentLoaded', () => {
let resultItem = '';
+ url = new URL(url, location.origin);
+ url.searchParams.append('highlight', keywords.join(' '));
+
if (slicesOfTitle.length !== 0) {
- resultItem += `${highlightKeyword(title, slicesOfTitle[0])}`;
+ resultItem += `${highlightKeyword(title, slicesOfTitle[0])}`;
} else {
- resultItem += `${title}`;
+ resultItem += `${title}`;
}
slicesOfContent.forEach(slice => {
- resultItem += `${highlightKeyword(content, slice)}...
`;
+ resultItem += `${highlightKeyword(content, slice)}...
`;
});
resultItem += '';
@@ -226,7 +229,8 @@ document.addEventListener('DOMContentLoaded', () => {
* it will always return arrays of strings for the value parts.
*/
const getQueryParameters = () => {
- const parts = location.search.split('?')[1].split('&');
+ const s = location.search;
+ const parts = s.substr(s.indexOf('?') + 1).split('&');
const result = {};
for (const part of parts) {
let [key, value] = part.split('=', 2);
@@ -261,7 +265,7 @@ document.addEventListener('DOMContentLoaded', () => {
};
// Highlight the search words provided in the url in the text
- window.highlightSearchWords = () => {
+ const highlightSearchWords = () => {
const params = getQueryParameters();
const keywords = params.highlight ? params.highlight[0].split(/\s+/) : [];
const body = document.querySelector('.post-body');
@@ -315,7 +319,10 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose);
- document.addEventListener('pjax:success', onPopupClose);
+ document.addEventListener('pjax:success', () => {
+ highlightSearchWords();
+ onPopupClose();
+ });
window.addEventListener('keyup', event => {
if (event.key === 'Escape') {
onPopupClose();
From ee8bf7cabf4fcf2814178ff028f4f2c74441398b Mon Sep 17 00:00:00 2001
From: Mimi <1119186082@qq.com>
Date: Wed, 29 Jul 2020 00:01:17 +0800
Subject: [PATCH 11/16] Update
---
source/js/local-search.js | 11 ++---------
1 file changed, 2 insertions(+), 9 deletions(-)
diff --git a/source/js/local-search.js b/source/js/local-search.js
index 5544b906c..a320445ca 100644
--- a/source/js/local-search.js
+++ b/source/js/local-search.js
@@ -218,11 +218,6 @@ document.addEventListener('DOMContentLoaded', () => {
});
};
- // Small helper function to urldecode strings
- const urldecode = x => {
- return decodeURIComponent(x).replace(/\+/g, ' ');
- };
-
/**
* This function returns the parsed url parameters of the
* current request. Multiple values per key are supported,
@@ -233,9 +228,7 @@ document.addEventListener('DOMContentLoaded', () => {
const parts = s.substr(s.indexOf('?') + 1).split('&');
const result = {};
for (const part of parts) {
- let [key, value] = part.split('=', 2);
- key = urldecode(key);
- value = urldecode(value);
+ const [key, value] = part.split('=', 2);
if (key in result) {
result[key].push(value);
} else {
@@ -267,7 +260,7 @@ document.addEventListener('DOMContentLoaded', () => {
// Highlight the search words provided in the url in the text
const highlightSearchWords = () => {
const params = getQueryParameters();
- const keywords = params.highlight ? params.highlight[0].split(/\s+/) : [];
+ 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);
From 63e7a284fd6ed7f9346ff44251782a188a11944f Mon Sep 17 00:00:00 2001
From: Mimi <1119186082@qq.com>
Date: Wed, 29 Jul 2020 01:43:20 +0800
Subject: [PATCH 12/16] Detroit loading spinner
---
layout/_partials/search/localsearch.njk | 12 ++++++++-
.../components/third-party/search.styl | 25 +++++++++++++++++++
source/js/local-search.js | 1 -
3 files changed, 36 insertions(+), 2 deletions(-)
diff --git a/layout/_partials/search/localsearch.njk b/layout/_partials/search/localsearch.njk
index 26931e6c7..6a55af46c 100644
--- a/layout/_partials/search/localsearch.njk
+++ b/layout/_partials/search/localsearch.njk
@@ -13,6 +13,16 @@
diff --git a/source/css/_common/components/third-party/search.styl b/source/css/_common/components/third-party/search.styl
index 0673cbb9c..2518300ce 100644
--- a/source/css/_common/components/third-party/search.styl
+++ b/source/css/_common/components/third-party/search.styl
@@ -164,6 +164,31 @@ if (hexo-config('local_search.enable')) {
#no-result {
color: $grey-light;
margin: auto;
+
+ svg {
+ width: 6em;
+
+ .spinner {
+ animation: fa-spin 5s linear alternate-reverse infinite;
+ opacity: .5;
+ transform-origin: 50%;
+ }
+
+ use {
+ &:nth-of-type(1) {
+ animation-duration: 2s;
+ }
+ &:nth-of-type(2) {
+ animation-duration: 3s;
+ }
+ &:nth-of-type(3) {
+ animation-duration: 7s;
+ }
+ &:nth-of-type(4) {
+ animation-duration: 11s;
+ }
+ }
+ }
}
}
diff --git a/source/js/local-search.js b/source/js/local-search.js
index a320445ca..c8195c84d 100644
--- a/source/js/local-search.js
+++ b/source/js/local-search.js
@@ -213,7 +213,6 @@ document.addEventListener('DOMContentLoaded', () => {
return data;
});
// Remove loading animation
- document.getElementById('no-result').innerHTML = '';
inputEventFunction();
});
};
From 9a63279d93e9d002212c11cd2068834f1584a0a5 Mon Sep 17 00:00:00 2001
From: Mimi <1119186082@qq.com>
Date: Wed, 29 Jul 2020 07:31:02 +0800
Subject: [PATCH 13/16] Update
---
scripts/helpers/next-config.js | 16 +++++++++-------
source/js/local-search.js | 19 +++++++++----------
2 files changed, 18 insertions(+), 17 deletions(-)
diff --git a/scripts/helpers/next-config.js b/scripts/helpers/next-config.js
index cef8beaee..89d5b9aa2 100644
--- a/scripts/helpers/next-config.js
+++ b/scripts/helpers/next-config.js
@@ -9,7 +9,6 @@ const { parse } = require('url');
*/
hexo.extend.helper.register('next_config', function() {
const { config, theme, next_version } = this;
- config.algolia = config.algolia || {};
const exportConfig = {
hostname : parse(config.url).hostname || config.url,
root : config.root,
@@ -24,19 +23,22 @@ hexo.extend.helper.register('next_config', function() {
lazyload : theme.lazyload,
pangu : theme.pangu,
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,
apiKey : config.algolia.apiKey,
indexName: config.algolia.indexName,
hits : theme.algolia_search.hits,
labels : theme.algolia_search.labels
- },
- localsearch: theme.local_search,
- motion : theme.motion,
- prism : config.prismjs.enable && !config.prismjs.preprocess
- };
+ };
+ }
if (config.search) {
exportConfig.path = config.search.path;
+ exportConfig.localsearch = theme.local_search;
}
return `