diff --git a/assets/search/js/form.ts b/assets/search/js/form.ts index f4fead0670d..e8d3b45eb67 100644 --- a/assets/search/js/form.ts +++ b/assets/search/js/form.ts @@ -3,6 +3,7 @@ import i18n from './i18n' import Renderer from './renderer' import engine from './engine' import Spinner from './spinner' +import { default as Historiographer } from './historiographer' export default class Form { // Original page title. @@ -174,6 +175,10 @@ export default class Form { this.input.addEventListener('search', () => { this.submit() }) + document.addEventListener('search:input:change', (e) => { + this.input.value = e.detail.value + this.submit() + }) this.language = this.ele.querySelector('.search-filter-lang') as HTMLElement this.language?.addEventListener('change', () => { @@ -243,14 +248,21 @@ export default class Form { const query = this.getQuery() this.updatePage(query) - this.spinner.show() const sorting = this.getSorting() const lang = this.getLanguage() const years = this.getYears() const taxonomies = this.getTaxonomies() + + if (query === '' && Object.values(taxonomies).filter((item) => item.length > 0).length == 0) { + this.renderer.renderHistories() + return + } + + this.spinner.show() engine.search(query, sorting, lang, years, taxonomies).then(({ results, time }) => { this.renderer.render(query, results, time) }).finally(() => { + Historiographer.save(query) this.spinner.hide() }) } diff --git a/assets/search/js/historiographer.ts b/assets/search/js/historiographer.ts new file mode 100644 index 00000000000..8f4a5fcae29 --- /dev/null +++ b/assets/search/js/historiographer.ts @@ -0,0 +1,35 @@ +import params from '@params' + +class Historiographer { + private key = 'search-histories' + + get() { + let histories = JSON.parse(localStorage.getItem(this.key) ?? '[]') + if (!(histories instanceof Array)) { + histories = [] + } + + return histories.slice(0, params.historiesCount) + } + + save(query: string): void { + if (query === '') { + return + } + + let histories = this.get() + histories = histories.filter((history) => history.query !== query) + histories.unshift({ + query: query, + date: (new Date()), + }) + + if (histories.length > params.historiesCount) { + histories.pop() + } + + localStorage.setItem(this.key, JSON.stringify(histories)) + } +} + +export default (new Historiographer()) diff --git a/assets/search/js/modal.ts b/assets/search/js/modal.ts index 51ed22ecdee..0c54ad76303 100644 --- a/assets/search/js/modal.ts +++ b/assets/search/js/modal.ts @@ -21,14 +21,16 @@ export default class Modal { private container: HTMLElement + private renderer: Renderer + private form: Form private shortcuts: Shortcuts constructor() { const spinner = new Spinner('.search-modal .search-spinner') - const renderer = new Renderer('.search-modal .search-results', '.search-modal .search-stat', spinner) - this.form = new Form(spinner, renderer) + this.renderer = new Renderer('.search-modal .search-results', '.search-modal .search-stat', spinner) + this.form = new Form(spinner, this.renderer) this.shortcuts = new Shortcuts([ closeShortcut, searchShortcut, @@ -99,6 +101,7 @@ export default class Modal { ${this.renderFooter()} ` this.wrapper.appendChild(this.container) + this.renderer.renderHistories() } renderFooter(): string { diff --git a/assets/search/js/renderer.ts b/assets/search/js/renderer.ts index 7cbe1027f31..80a9c5b5074 100644 --- a/assets/search/js/renderer.ts +++ b/assets/search/js/renderer.ts @@ -1,6 +1,7 @@ import { default as params } from '@params' import i18n from './i18n' import Spinner from './spinner' +import { default as Historiographer } from './historiographer' export default class Renderer { private initialized = false @@ -209,6 +210,33 @@ export default class Renderer { observer.observe(container, { childList: true }); } + renderHistories() { + this.results = [] + this.clean() + const histories = Historiographer.get() + let html = '' + histories.forEach((history) => { + html += ` +
${params.icons['history']}
+
+
${history.query}
+
${history.date ? history.date.toLocaleString() : ''}
+
+
` + }) + this.getContainer().insertAdjacentHTML('beforeend', html) + this.getContainer().querySelectorAll('.search-history').forEach((ele) => { + ele.addEventListener('click', (e) => { + e.preventDefault() + document.dispatchEvent(new CustomEvent('search:input:change', { + detail: { + value: ele.getAttribute('data-query') + } + })) + }) + }) + } + activeResult(target) { if (target.ariaSelected === 'true') { return diff --git a/assets/search/js/search.ts b/assets/search/js/search.ts index 3d51ff5c42f..0d07404a1c8 100644 --- a/assets/search/js/search.ts +++ b/assets/search/js/search.ts @@ -10,6 +10,8 @@ export default class Search { private shortcuts: Shortcuts + private renderer: Renderer + constructor() { const container = document.querySelector('.search-container') as HTMLElement if (!container) { @@ -19,8 +21,8 @@ export default class Search { this.container = container const spinner = new Spinner('.search-container .search-spinner') - const renderer = new Renderer('.search-container .search-results', '.search-container .search-stat', spinner) - this.form = new Form(spinner, renderer) + this.renderer = new Renderer('.search-container .search-results', '.search-container .search-stat', spinner) + this.form = new Form(spinner, this.renderer) this.form.modal = false this.shortcuts = new Shortcuts([Navigate, Select]) @@ -34,6 +36,7 @@ export default class Search { ` this.container.insertAdjacentHTML('beforeend', html) this.form.init() + this.renderer.renderHistories() } } diff --git a/hugo.toml b/hugo.toml index efd9db1e8e1..c73d2da04f5 100644 --- a/hugo.toml +++ b/hugo.toml @@ -50,3 +50,4 @@ expand_results_meta = false lazy_loading = true filter_taxonomies = true filter_years = true +histories_count = 5 diff --git a/layouts/partials/search/assets/js-resource.html b/layouts/partials/search/assets/js-resource.html index 881fd6739a9..95619d5ca9d 100644 --- a/layouts/partials/search/assets/js-resource.html +++ b/layouts/partials/search/assets/js-resource.html @@ -56,6 +56,7 @@ {{- $icons := dict "page" (partial "icons/icon" (dict "vendor" "bootstrap" "name" "file-earmark-richtext" "size" "2em")) "heading" (partial "icons/icon" (dict "vendor" "bootstrap" "name" "hash" "size" "2em")) + "history" (partial "icons/icon" (dict "vendor" "bootstrap" "name" "clock" "size" "2em")) "meta" (partial "icons/icon" (dict "vendor" "bootstrap" "name" "info-circle" "size" "2em")) "search" (partial "icons/icon" (dict "vendor" "bootstrap" "name" "search" "size" "1.25em")) "spinner" (partial "icons/icon" (dict "vendor" "bootstrap" "name" "arrow-clockwise" "size" "1.25em")) @@ -89,6 +90,7 @@ "icons" $icons "indices" $indices "i18n" $i18n.Values + "historiesCount" (default 5 site.Params.search.histories_count) "defaultLang" $defaultLang "langs" $langs.Values "years" $years