Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/CaiJimmy/hugo-theme-stack.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJimmy Cai <jimmehcai@gmail.com>2020-11-06 13:33:51 +0300
committerJimmy Cai <jimmehcai@gmail.com>2020-11-06 13:33:51 +0300
commitf5d45458fd55a4fdbff846f132d039518b07d3a3 (patch)
tree38befe2d1eb89c325556e5b4b37ab5796b71d7e4
parentb97e86a7a763ebac35a3390f144da955626ba4db (diff)
refactor(search): create Search class
-rw-r--r--assets/ts/search.tsx324
1 files changed, 174 insertions, 150 deletions
diff --git a/assets/ts/search.tsx b/assets/ts/search.tsx
index 8be4b7d..9539caa 100644
--- a/assets/ts/search.tsx
+++ b/assets/ts/search.tsx
@@ -8,17 +8,6 @@ interface pageData {
matchCount: number
}
-const searchForm = document.querySelector('.search-form') as HTMLFormElement;
-const searchInput = searchForm.querySelector('input') as HTMLInputElement;
-const searchResultList = document.querySelector('.search-result--list') as HTMLDivElement;
-const searchResultTitle = document.querySelector('.search-result--title') as HTMLHeadingElement;
-
-let data: pageData[];
-
-function escapeRegExp(string) {
- return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
-}
-
/**
* Escape HTML tags as HTML entities
* Edited from:
@@ -40,187 +29,222 @@ function replaceHTMLEnt(str) {
return str.replace(/[&<>"]/g, replaceTag);
}
-async function getData() {
- if (!data) {
- /// Not fetched yet
- const jsonURL = searchForm.dataset.json;
- data = await fetch(jsonURL).then(res => res.json());
- }
-
- return data;
+function escapeRegExp(string) {
+ return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
}
-function updateQueryString(keywords: string, replaceState = false) {
- const pageURL = new URL(window.location.toString());
-
- if (keywords === '') {
- pageURL.searchParams.delete('keyword')
- }
- else {
- pageURL.searchParams.set('keyword', keywords);
+class Search {
+ private data: pageData[];
+ private form: HTMLFormElement;
+ private input: HTMLInputElement;
+ private list: HTMLDivElement;
+ private resultTitle: HTMLHeadElement;
+
+ constructor({ form, input, list, resultTitle }) {
+ this.form = form;
+ this.input = input;
+ this.list = list;
+ this.resultTitle = resultTitle;
+
+ this.handleQueryString();
+ this.bindQueryStringChange();
+ this.bindSearchForm();
}
- if (replaceState) {
- window.history.replaceState('', '', pageURL.toString());
- }
- else {
- window.history.pushState('', '', pageURL.toString());
- }
-}
+ private async searchKeywords(keywords: string[]) {
+ const rawData = await this.getData();
+ let results: pageData[] = [];
-function bindQueryStringChange() {
- window.addEventListener('popstate', (e) => {
- handleQueryString()
- })
-}
+ /// Sort keywords by their length
+ keywords.sort((a, b) => {
+ return b.length - a.length
+ });
-function handleQueryString() {
- const pageURL = new URL(window.location.toString());
- const keywords = pageURL.searchParams.get('keyword');
- searchInput.value = keywords;
+ for (const item of rawData) {
+ let result = {
+ ...item,
+ preview: '',
+ matchCount: 0
+ }
- if (keywords) {
- doSearch(keywords.split(' '));
- }
- else {
- clear()
- }
-}
+ let matched = false;
-function bindSearchForm() {
- let lastSearch = '';
+ for (const keyword of keywords) {
+ if (keyword === '') continue;
- const eventHandler = (e) => {
- e.preventDefault();
- const keywords = searchInput.value;
+ const regex = new RegExp(escapeRegExp(replaceHTMLEnt(keyword)), 'gi');
- updateQueryString(keywords, true);
+ const contentMatch = regex.exec(result.content);
+ regex.lastIndex = 0; /// Reset regex
- if (keywords === '') {
- return clear();
- }
+ const titleMatch = regex.exec(result.title);
+ regex.lastIndex = 0; /// Reset regex
- if (lastSearch === keywords) return;
- lastSearch = keywords;
+ if (titleMatch) {
+ result.title = result.title.replace(regex, Search.marker);
+ }
- doSearch(keywords.split(' '));
- }
+ if (titleMatch || contentMatch) {
+ matched = true;
+ ++result.matchCount;
- searchInput.addEventListener('input', eventHandler);
- searchInput.addEventListener('compositionend', eventHandler);
-}
+ let start = 0,
+ end = 100;
-function clear() {
- searchResultList.innerHTML = '';
- searchResultTitle.innerText = '';
-}
+ if (contentMatch) {
+ start = contentMatch.index - 20;
+ end = contentMatch.index + 80
-async function doSearch(keywords: string[]) {
- const startTime = performance.now();
+ if (start < 0) start = 0;
+ }
+
+ if (result.preview.indexOf(keyword) !== -1) {
+ result.preview = result.preview.replace(regex, Search.marker);
+ }
+ else {
+ if (start !== 0) result.preview += `[...] `;
+ result.preview += `${result.content.slice(start, end).replace(regex, Search.marker)} `;
+ }
+ }
+ }
+
+ if (matched) {
+ result.preview += '[...]';
+ results.push(result);
+ }
+ }
- const results = await searchKeywords(keywords);
- clear();
+ /** Result with more matches appears first */
+ return results.sort((a, b) => {
+ return b.matchCount - a.matchCount;
+ });
+ }
- for (const item of results) {
- searchResultList.append(render(item));
+ public static marker(match) {
+ return '<mark>' + match + '</mark>';
}
- const endTime = performance.now();
+ private async doSearch(keywords: string[]) {
+ const startTime = performance.now();
- searchResultTitle.innerText = `${results.length} pages (${((endTime - startTime) / 1000).toPrecision(1)} seconds)`;
-}
+ const results = await this.searchKeywords(keywords);
+ this.clear();
-function marker(match) {
- return '<mark>' + match + '</mark>';
-}
+ for (const item of results) {
+ this.list.append(Search.render(item));
+ }
-async function searchKeywords(keywords: string[]) {
- const rawData = await getData();
- let results: pageData[] = [];
+ const endTime = performance.now();
- /// Sort keywords by their length
- keywords.sort((a, b) => {
- return b.length - a.length
- });
+ this.resultTitle.innerText = `${results.length} pages (${((endTime - startTime) / 1000).toPrecision(1)} seconds)`;
+ }
- for (const item of rawData) {
- let result = {
- ...item,
- preview: '',
- matchCount: 0
+ public async getData() {
+ if (!this.data) {
+ /// Not fetched yet
+ const jsonURL = this.form.dataset.json;
+ this.data = await fetch(jsonURL).then(res => res.json());
}
- let matched = false;
-
- for (const keyword of keywords) {
- if (keyword === '') continue;
+ return this.data;
+ }
- const regex = new RegExp(escapeRegExp(replaceHTMLEnt(keyword)), 'gi');
+ private bindSearchForm() {
+ let lastSearch = '';
- const contentMatch = regex.exec(result.content);
- regex.lastIndex = 0; /// Reset regex
+ const eventHandler = (e) => {
+ e.preventDefault();
+ const keywords = this.input.value;
- const titleMatch = regex.exec(result.title);
- regex.lastIndex = 0; /// Reset regex
+ Search.updateQueryString(keywords, true);
- if (titleMatch) {
- result.title = result.title.replace(regex, marker);
+ if (keywords === '') {
+ return this.clear();
}
- if (titleMatch || contentMatch) {
- matched = true;
- ++result.matchCount;
+ if (lastSearch === keywords) return;
+ lastSearch = keywords;
- let start = 0,
- end = 100;
+ this.doSearch(keywords.split(' '));
+ }
- if (contentMatch) {
- start = contentMatch.index - 20;
- end = contentMatch.index + 80
+ this.input.addEventListener('input', eventHandler);
+ this.input.addEventListener('compositionend', eventHandler);
+ }
- if (start < 0) start = 0;
- }
+ private clear() {
+ this.list.innerHTML = '';
+ this.resultTitle.innerText = '';
+ }
- if (result.preview.indexOf(keyword) !== -1) {
- result.preview = result.preview.replace(regex, marker);
- }
- else {
- if (start !== 0) result.preview += `[...] `;
- result.preview += `${result.content.slice(start, end).replace(regex, marker)} `;
- }
- }
- }
+ private bindQueryStringChange() {
+ window.addEventListener('popstate', (e) => {
+ this.handleQueryString()
+ })
+ }
+
+ private handleQueryString() {
+ const pageURL = new URL(window.location.toString());
+ const keywords = pageURL.searchParams.get('keyword');
+ this.input.value = keywords;
- if (matched) {
- result.preview += '[...]';
- results.push(result);
+ if (keywords) {
+ this.doSearch(keywords.split(' '));
+ }
+ else {
+ this.clear()
}
}
- /** Result with more matches appears first */
- return results.sort((a, b) => {
- return b.matchCount - a.matchCount;
- });
-}
+ private static updateQueryString(keywords: string, replaceState = false) {
+ const pageURL = new URL(window.location.toString());
+
+ if (keywords === '') {
+ pageURL.searchParams.delete('keyword')
+ }
+ else {
+ pageURL.searchParams.set('keyword', keywords);
+ }
-const render = (item: pageData) => {
- return <article>
- <a href={item.permalink}>
- <div class="article-details">
- <h2 class="article-title" dangerouslySetInnerHTML={{ __html: item.title }}></h2>
- <secion class="article-preview" dangerouslySetInnerHTML={{ __html: item.preview }}></secion>
- </div>
- {item.image &&
- <div class="article-image">
- <img src={item.image} loading="lazy" />
+ if (replaceState) {
+ window.history.replaceState('', '', pageURL.toString());
+ }
+ else {
+ window.history.pushState('', '', pageURL.toString());
+ }
+ }
+
+ public static render(item: pageData) {
+ return <article>
+ <a href={item.permalink}>
+ <div class="article-details">
+ <h2 class="article-title" dangerouslySetInnerHTML={{ __html: item.title }}></h2>
+ <secion class="article-preview" dangerouslySetInnerHTML={{ __html: item.preview }}></secion>
</div>
- }
- </a>
- </article>;
+ {item.image &&
+ <div class="article-image">
+ <img src={item.image} loading="lazy" />
+ </div>
+ }
+ </a>
+ </article>;
+ }
}
-window.addEventListener('DOMContentLoaded', () => {
- handleQueryString();
- bindQueryStringChange();
- bindSearchForm();
-}) \ No newline at end of file
+window.addEventListener('load', () => {
+ setTimeout(function () {
+ const searchForm = document.querySelector('.search-form') as HTMLFormElement,
+ searchInput = searchForm.querySelector('input') as HTMLInputElement,
+ searchResultList = document.querySelector('.search-result--list') as HTMLDivElement,
+ searchResultTitle = document.querySelector('.search-result--title') as HTMLHeadingElement;
+
+ new Search({
+ form: searchForm,
+ input: searchInput,
+ list: searchResultList,
+ resultTitle: searchResultTitle
+ });
+ }, 0);
+})
+
+export default Search; \ No newline at end of file