Skip to content

Commit

Permalink
perf: lazy loading locales (#267)
Browse files Browse the repository at this point in the history
feat: add the `indices` parameter, to load external indices

Closes #262, closes #266
  • Loading branch information
razonyang authored Oct 24, 2024
1 parent 3bb42d6 commit 23784f5
Show file tree
Hide file tree
Showing 12 changed files with 225 additions and 170 deletions.
51 changes: 45 additions & 6 deletions assets/search/js/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ class Engine {

private initialized = false

private sites = []

public getBaseURL(site): string {
return this.sites[site] ? this.sites[site].baseURL : ''
}

public taxonomies: Record<string, string[]> = {}

public years: string[] = []

public langs: Record<string, string> = {}

/**
* Initialize the search index.
*
Expand Down Expand Up @@ -38,16 +50,43 @@ class Engine {

const promises = new Array<Promise<Array<unknown>>>
for (const i in params.indices) {
const promise = fetch(params.indices[i]).then((resp) => resp.json())
const index = params.indices[i]
const promise = fetch(index).then((resp) => resp.json())
promises.push(promise)
}

return Promise.all(promises).then((resp) => {
let pages = resp[0]
for (let i = 1; i < resp.length; i++) {
pages = pages.concat(resp[i])
let pages = []
for (let i = 0; i < resp.length; i++) {
const data = resp[i]
if (Array.isArray(data)) {
// backward compatibility
pages = pages.concat(data)
} else {
const baseURL = data.baseURL.replace(/\/$/, "")
const siteIdx = this.sites.push({baseURL: baseURL}) - 1
pages = pages.concat(data.pages.map((page) => {
return {...page, site: siteIdx}
}))

for (const taxonomy in data.taxonomies) {
if (this.taxonomies[taxonomy] === undefined) {
this.taxonomies[taxonomy] = []
}
this.taxonomies[taxonomy].push(...data.taxonomies[taxonomy])
}

Object.assign(this.langs, data.langs)
this.years.push(...data.years)
}
}
for (const taxonomy in this.taxonomies) {
const terms = this.taxonomies[taxonomy]
this.taxonomies[taxonomy] = terms.filter((term, i) => terms.indexOf(term) === i).sort()
}

this.years = this.years.filter((year, i) => this.years.indexOf(year) === i).sort()

this.index = new Fuse(pages, {
isCaseSensitive: params.case_sensitive,
minMatchCharLength: params.min_match_char_length,
Expand All @@ -62,7 +101,7 @@ class Engine {
})
}).catch((err) => {
this.indexFailed = true
throw new Error(err)
throw err
})
}

Expand All @@ -85,7 +124,7 @@ class Engine {
keys.push({name: 'content', weight: params.key_weights.content})
}

for (const taxonomy in params.taxonomies) {
for (const taxonomy in engine.taxonomies) {
keys.push({name: taxonomy, weight: params.key_weights.taxonomies})
}

Expand Down
113 changes: 59 additions & 54 deletions assets/search/js/form.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { default as params } from '@params'
import i18n from './i18n'
import { translate } from './i18n'
import Renderer from './renderer'
import engine from './engine'
import Spinner from './spinner'
Expand Down Expand Up @@ -39,45 +39,36 @@ export default class Form {
<div class="search-input-group">
<span class="search-input-icon">${params.icons['search']}</span>
<span class="search-spinner">${params.icons['spinner']}</span>
<input type="search" name="q" class="search-input search-form-control" placeholder="${i18n.translate('input_placeholder')}" autocomplete="off" disabled/>
<input type="search" name="q" class="search-input search-form-control" placeholder="${translate('input_placeholder')}" autocomplete="off" disabled/>
<button class="search-reset-button disabled" type="reset">${params.icons['reset']}</button>
</div>
<button class="search-modal-close" type="button">${i18n.translate('cancel')}</button>
<button class="search-modal-close" type="button">${translate('cancel')}</button>
</div>
<div class="search-form-meta">
<div class="search-panel">
${this.renderLanguage()}
${this.renderSorting()}
${this.renderYears()}
${this.renderTaxonomies()}
<button class="search-panel-action search-expand-toggle${params.expand_results_meta ? ' active' : ''}" title="Expand">
<span class="search-panel-action-icon">${params.icons['expand']}</span>
<span class="search-panel-action-label">${i18n.translate('expand')}</span>
</button>
</div>
<div class="search-panel"></div>
<div class="search-stat"></div>
</div>
</form>`
}

private renderLanguage(): string {
if (Object.keys(params.langs).length < 2) {
if (Object.keys(engine.langs).length < 2) {
return ''
}

const lang = document.documentElement.getAttribute('lang') ?? ''

let label = i18n.translate('all')
let label = translate('all')
let options = `<li class="search-dropdown-item${lang ? '' : ' active'}" data-value="">${label}</li>`

for (const i in params.langs) {
const item = params.langs[i]
for (const langCode in engine.langs) {
const langName = engine.langs[langCode]
let className = ''
if (item.lang === lang) {
if (langCode === lang) {
className = ' active'
label = item.name
label = langName
}
options += `<li class="search-dropdown-item${className}" data-value="${item.lang}">${item.name}</li>`
options += `<li class="search-dropdown-item${className}" data-value="${langName}">${langName}</li>`
}

return `<div class="search-dropdown search-panel-action search-filter-lang${lang ? ' active' : ''}" data-value="${lang}">
Expand All @@ -90,35 +81,35 @@ export default class Form {
}

private renderSorting(): string {
if (params.langs.length < 2) {
if (engine.langs.length < 2) {
return ''
}

const defaultSort = i18n.translate('sort_by_default')
const defaultSort = translate('sort_by_default')

return `<div class="search-dropdown search-panel-action search-sorting">
<button class="search-dropdown-toggle" type="button" aria-expanded="false" title="Sorting">
${params.icons['sort']} <span class="search-dropdown-label">${defaultSort}</span>
</button>
<ul class="search-dropdown-menu">
<li class="search-dropdown-item active" data-value="">${defaultSort}</li>
<li class="search-dropdown-item" data-value="date_asc">${i18n.translate('sort_by_date_asc')}</li>
<li class="search-dropdown-item" data-value="date_desc">${i18n.translate('sort_by_date_desc')}</li>
<li class="search-dropdown-item" data-value="date_asc">${translate('sort_by_date_asc')}</li>
<li class="search-dropdown-item" data-value="date_desc">${translate('sort_by_date_desc')}</li>
</ul>
</div>`
}

private renderYears(): string {
if (params.years.length === 0) {
if (engine.years.length === 0) {
return ''
}

let items = ''
for (const year of params.years) {
for (const year of engine.years) {
items += `<li class="search-dropdown-item" data-value="${year}">${year}</li>`
}

const label = i18n.translate('years')
const label = translate('years')
return `<div class="search-dropdown search-panel-action search-years" multiple>
<button class="search-dropdown-toggle" type="button" aria-expanded="false" title="${label}">
${params.icons['year']} <span class="search-dropdown-label">${label}</span>
Expand All @@ -129,8 +120,8 @@ export default class Form {

private renderTaxonomies(): string {
let v = ''
for (const name in params.taxonomies) {
v += this.renderTaxonomy(name, params.taxonomies[name])
for (const name in engine.taxonomies) {
v += this.renderTaxonomy(name, engine.taxonomies[name])
}
return v
}
Expand All @@ -141,7 +132,7 @@ export default class Form {
v += `<li class="search-dropdown-item" data-value="${name}">${name}</li>`
}

const label = i18n.translate("taxonomy_" + name, null, name)
const label = translate("taxonomy_" + name, null, name)
return `<div class="search-dropdown search-panel-action search-taxonomies search-taxonomies-${name}" multiple>
<button class="search-dropdown-toggle" type="button" aria-expanded="false" title="${label}">
${params.icons['taxonomies']} <span class="search-dropdown-label">${label}</span>
Expand Down Expand Up @@ -182,27 +173,6 @@ export default class Form {
this.input.value = e.detail.value
this.submit()
})

this.language = this.ele.querySelector('.search-filter-lang') as HTMLElement
this.language?.addEventListener('change', () => {
this.submit()
})

this.sorting = this.ele.querySelector('.search-sorting') as HTMLElement
this.sorting.addEventListener('change', () => {
this.submit()
})

this.years = this.ele.querySelector('.search-years') as HTMLElement
this.years?.addEventListener('change', () => {
this.submit()
})

this.ele.querySelectorAll('.search-taxonomies').forEach((el) => {
el.addEventListener('change', () => {
this.submit()
})
})

this.ele.addEventListener('reset', () => {
const lang = document.documentElement.getAttribute('lang') ?? ''
Expand Down Expand Up @@ -231,10 +201,12 @@ export default class Form {

this.spinner.show()
engine.init().then(() => {
this.renderPanel()
}).then(() => {
this.input.removeAttribute('disabled')
}).catch((err) => {
this.input.value = i18n.translate('index_fails')
throw new Error(err)
this.input.value = translate('index_fails')
throw err
}).then(() => {
if (!this.modal) {
this.fillInputByURL()
Expand All @@ -252,6 +224,39 @@ export default class Form {
this.submit()
})
}
}

private renderPanel() {
const panel = this.ele.querySelector('.search-panel') as HTMLElement
panel.innerHTML = `${this.renderLanguage()}
${this.renderSorting()}
${this.renderYears()}
${this.renderTaxonomies()}
<button class="search-panel-action search-expand-toggle${params.expand_results_meta ? ' active' : ''}" title="Expand">
<span class="search-panel-action-icon">${params.icons['expand']}</span>
<span class="search-panel-action-label">${translate('expand')}</span>
</button>`

this.language = this.ele.querySelector('.search-filter-lang') as HTMLElement
this.language?.addEventListener('change', () => {
this.submit()
})

this.sorting = this.ele.querySelector('.search-sorting') as HTMLElement
this.sorting.addEventListener('change', () => {
this.submit()
})

this.years = this.ele.querySelector('.search-years') as HTMLElement
this.years?.addEventListener('change', () => {
this.submit()
})

this.ele.querySelectorAll('.search-taxonomies').forEach((el) => {
el.addEventListener('change', () => {
this.submit()
})
})

const expand = this.ele.querySelector('.search-expand-toggle')
expand?.addEventListener('click', (e) => {
Expand Down Expand Up @@ -357,7 +362,7 @@ export default class Form {
getTaxonomies(): Record<string, Array<string>> {
const v = {}

for (const taxonomy in params.taxonomies) {
for (const taxonomy in engine.taxonomies) {
const terms: Array<string> = []
document.querySelectorAll(`.search-taxonomies-${taxonomy} .search-dropdown-item.active`).forEach((item) => {
terms.push(item.getAttribute('data-value') ?? '')
Expand Down
16 changes: 14 additions & 2 deletions assets/search/js/i18n.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import Translator from "mods/i18n/translator"
import { default as params } from '@params'

const i18n = new Translator(params.i18n, params.defaultLang)
let i18n: Translator = undefined

export default i18n
export const init = (): Promise<boolean> => {
return new Promise((resolve) => {
const lang = document.documentElement.getAttribute('lang') ?? params.lang
fetch(params.locales[lang]).then((resp) => resp.json()).then((messages) => {
i18n = new Translator({[lang]: messages}, lang)
resolve(true)
})
})
}

export const translate = (s: string, ctx: null|Record<string, string>): string => {
return i18n.translate(s, ctx)
}
20 changes: 18 additions & 2 deletions assets/search/js/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
import { default as params } from '@params'
import "./dropdown"
import "./navigator"
import "./modal"
import "./search"
import Modal from "./modal"
import Search from "./search"
import { init as i18nInit } from "./i18n"

(() => {
'use strict'

document.addEventListener('DOMContentLoaded', () => {
i18nInit().then(() => {
new Search()

if (params.modal_container !== '') {
(new Modal()).init()
}
})
})
})()
28 changes: 11 additions & 17 deletions assets/search/js/modal.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import { default as params } from '@params'
import i18n from './i18n'
import { translate } from './i18n'
import keyboard from './keyboard'
import Form from './form'
import Spinner from './spinner'
import Renderer from './renderer'
import { Navigate, Select, Shortcut, Shortcuts } from './shortcuts'

const searchShortcut: Shortcut = {
kbds: [params.shortcut_search],
action: i18n.translate('to_search'),
const searchShortcut = (): Shortcut => {
return {
kbds: [params.shortcut_search],
action: translate('to_search'),
}
}

const closeShortcut: Shortcut = {
kbds: [params.shortcut_close],
action: i18n.translate('to_close'),
const closeShortcut = (): Shortcut => {
return {
kbds: [params.shortcut_close],
action: translate('to_close'),
}
}

export default class Modal {
Expand Down Expand Up @@ -121,13 +125,3 @@ export default class Modal {
this.form.focus()
}
}

(() => {
'use strict'

document.addEventListener('DOMContentLoaded', () => {
if (params.modal_container !== '') {
(new Modal()).init()
}
})
})()
Loading

0 comments on commit 23784f5

Please sign in to comment.