From 7c2bf700e931950ca53d00d1dfc50fb51dd0cfb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20R=C3=B6sch?= Date: Tue, 6 Jul 2021 18:07:14 +0200 Subject: [PATCH 1/7] Build search indices for each language --- build/loaders/lang-loader.js | 2 -- build/loaders/search-index-loader.js | 35 ++++++++++++++++++++++++++++ package.json | 2 ++ src/components/map/Map.vue | 2 +- src/lang/en.lang.json | 3 ++- src/lang/es-ES.lang.json | 3 ++- src/lang/he.lang.json | 3 --- src/lang/ru.lang.json | 3 ++- src/lang/zh.lang.json | 3 ++- src/routes.js | 2 +- src/store/index.js | 4 ++++ src/store/search.js | 25 ++++++++++++++++++++ translation-comments.json | 6 ++++- yarn.lock | 10 ++++++++ 14 files changed, 91 insertions(+), 12 deletions(-) create mode 100644 build/loaders/search-index-loader.js delete mode 100644 src/lang/he.lang.json create mode 100644 src/store/search.js diff --git a/build/loaders/lang-loader.js b/build/loaders/lang-loader.js index 1dd339df..e813053e 100644 --- a/build/loaders/lang-loader.js +++ b/build/loaders/lang-loader.js @@ -13,8 +13,6 @@ module.exports = function (source) { const standardParser = parseStandardFile.bind(this) const eventParser = parseEventFile.bind(this) - // Eval is safe here since we're getting things directly from the JSON "loader" - // eslint-disable-next-line no-eval const messages = typeof source === 'string' ? JSON.parse(source) : source messages.events = build(lang, 'events', eventParser) diff --git a/build/loaders/search-index-loader.js b/build/loaders/search-index-loader.js new file mode 100644 index 00000000..5b71557e --- /dev/null +++ b/build/loaders/search-index-loader.js @@ -0,0 +1,35 @@ +const lunr = require('lunr') +require('lunr-languages/lunr.stemmer.support')(lunr) + +lunr.tokenizer.separator = /[\s-[\](){}]+/ + +module.exports = function (source) { + if (this.cacheable) { + this.cacheable() + } + + const messages = typeof source === 'string' ? JSON.parse(source) : source + + const searchable = ['events', 'locations', 'characters', 'misc'] + const index = lunr(function () { + const lunrLanguage = messages['search-language'] + if (lunrLanguage !== 'en') { + require(`lunr-languages/lunr.${lunrLanguage}`)(lunr) + this.use(lunr[lunrLanguage]) + } + + this.ref('id') + this.field('name', { boost: 2 }) + this.field('details') + this.metadataWhitelist = ['position'] + + searchable.forEach((entryType) => { + const entries = messages[entryType] ?? [] + Object.keys(entries).forEach(id => this.add({ ...entries[id], id: `${entryType}/${id}` })) + }) + }) + + return JSON.stringify(index) + .replace(/\u2028/g, '\\u2028') + .replace(/\u2029/g, '\\u2029') +} diff --git a/package.json b/package.json index 4486906e..261754fe 100755 --- a/package.json +++ b/package.json @@ -19,6 +19,8 @@ "hammerjs": "^2.0.8", "is-mobile": "^2.2.2", "jszip": "^3.5.0", + "lunr": "^2.3.9", + "lunr-languages": "^1.8.0", "register-service-worker": "^1.7.1", "seedrandom": "^3.0.5", "simple-markdown": "^0.7.2", diff --git a/src/components/map/Map.vue b/src/components/map/Map.vue index d2a7b198..1ced4325 100755 --- a/src/components/map/Map.vue +++ b/src/components/map/Map.vue @@ -107,7 +107,7 @@ export default { this.renderer.setSize(window.innerWidth, window.innerHeight) this.renderer.sortObjects = false - this.textureManager = new TextureManager(this.renderer, this.$t('textureLocale')) + this.textureManager = new TextureManager(this.renderer, this.$t('texture-locale')) this.composer = new EffectComposer(this.renderer) } catch (error) { diff --git a/src/lang/en.lang.json b/src/lang/en.lang.json index df1a71ff..c807a700 100755 --- a/src/lang/en.lang.json +++ b/src/lang/en.lang.json @@ -1,6 +1,7 @@ { - "textureLocale": "en", + "texture-locale": "en", "text-direction": "ltr", + "search-language": "en", "name": "Map of Roshar", "title": "{page} | The Stormlight Archive", "logo": "roshar.png", diff --git a/src/lang/es-ES.lang.json b/src/lang/es-ES.lang.json index 7573e40e..c28c5154 100755 --- a/src/lang/es-ES.lang.json +++ b/src/lang/es-ES.lang.json @@ -1,6 +1,7 @@ { - "textureLocale": "es", + "texture-locale": "es", "name": "Mapa de Roshar", + "search-language": "es", "title": "{page} | El archivo de las tormentas", "meta": { "translator": { diff --git a/src/lang/he.lang.json b/src/lang/he.lang.json deleted file mode 100644 index b6138e84..00000000 --- a/src/lang/he.lang.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "text-direction": "rtl" -} \ No newline at end of file diff --git a/src/lang/ru.lang.json b/src/lang/ru.lang.json index 9c53ed15..1c1985fe 100644 --- a/src/lang/ru.lang.json +++ b/src/lang/ru.lang.json @@ -1,6 +1,7 @@ { - "textureLocale": "ru", + "texture-locale": "ru", "text-direction": "ltr", + "search-language": "ru", "name": "Карта Рошара", "title": "{page} | «Архив буресвета»", "logo": "roshar-ru.png", diff --git a/src/lang/zh.lang.json b/src/lang/zh.lang.json index 5b3009cd..395ed35e 100644 --- a/src/lang/zh.lang.json +++ b/src/lang/zh.lang.json @@ -1,5 +1,6 @@ { - "textureLocale": "zh", + "texture-locale": "zh", + "search-language": "zh", "name": "柔刹地图", "title": "{page} | 飓光志", "logo": "roshar-zh.png", diff --git a/src/routes.js b/src/routes.js index d8710356..85decc5e 100644 --- a/src/routes.js +++ b/src/routes.js @@ -76,7 +76,7 @@ router.afterEach((to, from) => { const oldLocale = from.params.locale const newLocale = to.params.locale - if (oldLocale !== undefined && i18n.t('textureLocale', newLocale) !== i18n.t('textureLocale', oldLocale)) { + if (oldLocale !== undefined && i18n.t('texture-locale', newLocale) !== i18n.t('texture-locale', oldLocale)) { location.reload() } }) diff --git a/src/store/index.js b/src/store/index.js index 7bdeb95f..540fa29b 100755 --- a/src/store/index.js +++ b/src/store/index.js @@ -6,6 +6,7 @@ import baseCharacters from '@/store/characters.json' import baseMisc from '@/store/misc.json' import tagCategories from '@/store/tags.json' import { inverseLerp } from '@/utils' +import search from '@/store/search' Vue.use(Vuex) @@ -339,6 +340,9 @@ const getters = { } export default new Vuex.Store({ + modules: { + search + }, state: { events, years, diff --git a/src/store/search.js b/src/store/search.js new file mode 100644 index 00000000..d6345d2f --- /dev/null +++ b/src/store/search.js @@ -0,0 +1,25 @@ +import { Index } from 'lunr' + +export default { + namespaced: true, + state: () => ({ loadedIndices: {} }), + mutations: { + addIndex (state, { lang, index }) { + if (state.loadedIndices[lang] !== undefined) { + return + } + + state.loadedIndices[lang] = index + } + }, + actions: { + async loadIndex ({ state, commit }, lang) { + if (state.loadedIndices[lang] !== undefined) { + return + } + + const data = await import(/* webpackChunkName: "index-[request]" */ 'search-index-loader.js!@/lang/' + lang + '.lang.json') + commit('addIndex', { lang, index: Index.load(data.default) }) + } + } +} diff --git a/translation-comments.json b/translation-comments.json index 019102af..cd0a153b 100644 --- a/translation-comments.json +++ b/translation-comments.json @@ -1,5 +1,5 @@ { - "textureLocale": { + "texture-locale": { "name": "Texture Locale", "context": "This refers to a directory in *src/assets/textures/localized*. Only change this away from 'en' if you already had the textures translated!" }, @@ -7,6 +7,10 @@ "name": "Text Direction", "context": "Can be either 'ltr' for left-to-right languages (default) or 'rtl' for right-to-left-languages" }, + "search-language": { + "name": "Search Language", + "context": "Data to use for preparing translations for search. Consult the team on what to use here!" + }, "name": "Application Name", "title": { "name": "Page Title Template", diff --git a/yarn.lock b/yarn.lock index 3e759417..64dde03a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6569,6 +6569,16 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lunr-languages@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/lunr-languages/-/lunr-languages-1.8.0.tgz#ec4ea4268ad9a3bae25f8568b4fbb04be2c76aa2" + integrity sha512-oUwn4suj9A12nSs0ZBbu5GlqR0jzUunaTiDG0wS4gXTVBT5UjEwqm+O5U9sinwmC8kJtZAQSnxlzP/xu/mQxCA== + +lunr@^2.3.9: + version "2.3.9" + resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" + integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow== + make-dir@^1.0.0, make-dir@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" From c68f44e6d5a19d324890138b5f6e32132d19869f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20R=C3=B6sch?= Date: Mon, 20 Sep 2021 00:00:09 +0200 Subject: [PATCH 2/7] Introduce desktop search UI --- build/loaders/search-index-loader.js | 3 +- package.json | 1 + src/App.vue | 111 ++++++++++++- src/components/CalendarGuide.vue | 2 +- src/components/GoToDate.vue | 2 +- src/components/Info.vue | 81 ++------- src/components/Scrubber.vue | 2 +- src/components/Settings.vue | 94 ++--------- src/components/Tutorial.vue | 24 +-- src/components/search/Search.vue | 209 ++++++++++++++++++++++++ src/components/search/SearchResult.vue | 128 +++++++++++++++ src/components/search/SearchResults.vue | 84 ++++++++++ src/i18n.js | 3 +- src/lang/en.lang.json | 8 +- src/lang/es-ES.lang.json | 2 +- src/lang/search/es-ES.js | 5 + src/lang/search/ru.js | 5 + src/store/index.js | 17 +- src/store/search.js | 27 ++- yarn.lock | 117 ++++++++++++- 20 files changed, 739 insertions(+), 186 deletions(-) create mode 100644 src/components/search/Search.vue create mode 100644 src/components/search/SearchResult.vue create mode 100644 src/components/search/SearchResults.vue create mode 100644 src/lang/search/es-ES.js create mode 100644 src/lang/search/ru.js diff --git a/build/loaders/search-index-loader.js b/build/loaders/search-index-loader.js index 5b71557e..779777f6 100644 --- a/build/loaders/search-index-loader.js +++ b/build/loaders/search-index-loader.js @@ -19,9 +19,8 @@ module.exports = function (source) { } this.ref('id') - this.field('name', { boost: 2 }) + this.field('name', { boost: 4 }) this.field('details') - this.metadataWhitelist = ['position'] searchable.forEach((entryType) => { const entries = messages[entryType] ?? [] diff --git a/package.json b/package.json index 261754fe..453a6fa4 100755 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "imagemin-webp": "^6.0.0", "imagemin-zopfli": "^7.0.0", "js-string-escape": "^1.0.1", + "nodejieba": "^2.5.2", "sass": "^1.26.5", "sass-loader": "^8.0.2", "vue-template-compiler": "^2.6.11", diff --git a/src/App.vue b/src/App.vue index 252ee5e5..91c8fa13 100755 --- a/src/App.vue +++ b/src/App.vue @@ -11,8 +11,27 @@ - - +
+ + + +
+ + @@ -32,6 +51,7 @@ @@ -163,6 +192,78 @@ body { padding-left: 225px; } } + + .app__actions { + position: fixed; + top: 2rem; + display: grid; + grid-auto-flow: column; + grid-template-columns: repeat(3, minmax(0, max-content)); + grid-gap: 1rem; + max-width: calc(100% - 4rem); + + [dir=ltr] & { + right: 2rem; + } + + [dir=rtl] & { + left: 2rem; + } + + &-button { + display: flex; + align-items: center; + position: relative; + font-size: 1rem; + line-height: 1; + appearance: none; + outline: none; + box-sizing: border-box; + border: none; + z-index: 61; + background: #F5ECDA; + border-radius: 2rem; + padding: 0.75rem 0.75rem; + cursor: pointer; + transition: all 0.2s ease-in-out; + color: #242629; + pointer-events: auto; + box-shadow: 0 0.25rem 1rem rgba(0, 0, 0, 0.5); + + &:hover, &:active, &:focus { + background: saturate(darken(#F5ECDA, 10%), 5%); + } + + &--wide { + padding-left: 1.5rem; + padding-right: 1.5rem; + } + + &--hidden { + cursor: default !important; + box-shadow: 0 0 0 rgba(0, 0, 0, 0); + pointer-events: none; + opacity: 0; + transform: scale(0); + } + + [dir=ltr] & { + transform-origin: calc(100% - 1rem) 50%; + + &--wide .feather { + margin-right: 0.5rem; + } + } + + [dir=rtl] & { + transform-origin: 1rem 50%; + + &--wide .feather { + margin-left: 0.5rem; + } + } + } + } } button { diff --git a/src/components/CalendarGuide.vue b/src/components/CalendarGuide.vue index 861d0df5..e8a2f3df 100644 --- a/src/components/CalendarGuide.vue +++ b/src/components/CalendarGuide.vue @@ -134,7 +134,7 @@ export default { opacity: 0; } - &-enter-to, &-leave-from { + &-enter-to, &-leave { opacity: 1; } diff --git a/src/components/GoToDate.vue b/src/components/GoToDate.vue index ad77ef94..3b2fc89c 100644 --- a/src/components/GoToDate.vue +++ b/src/components/GoToDate.vue @@ -201,7 +201,7 @@ export default { } } - &-enter-to, &-leave-from { + &-enter-to, &-leave { opacity: 1; .go-to-date { diff --git a/src/components/Info.vue b/src/components/Info.vue index df5734eb..4eeef72b 100644 --- a/src/components/Info.vue +++ b/src/components/Info.vue @@ -1,16 +1,13 @@