From ee1b6de807bd3b260c512b770073dd7eb997b0e6 Mon Sep 17 00:00:00 2001
From: "Henrik.Askjer" <henrik.askjer@uib.no>
Date: Thu, 2 Sep 2021 15:31:38 +0200
Subject: [PATCH] Replace suggestion api

---
 package-lock.json                 |  84 ++++++++++++++++++-
 package.json                      |   4 +-
 src/components/Autocomplete.vue   |  70 +++++++---------
 src/components/DictionaryView.vue | 131 +++++++++++++++++++++---------
 4 files changed, 207 insertions(+), 82 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 859b1397..4710a532 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,7 @@
         "@fortawesome/free-solid-svg-icons": "^5.15.3",
         "@fortawesome/vue-fontawesome": "^2.0.2",
         "axios": "^0.21.1",
+        "axios-cache-adapter": "^2.7.3",
         "core-js": "~3.6.5",
         "debounce": "^1.2.1",
         "inflection-table": "https://git.app.uib.no/api/v4/projects/16442/jobs/artifacts/0.2.25/raw/module.tar.gz?job=publish",
@@ -3389,6 +3390,18 @@
         "follow-redirects": "^1.10.0"
       }
     },
+    "node_modules/axios-cache-adapter": {
+      "version": "2.7.3",
+      "resolved": "https://registry.npmjs.org/axios-cache-adapter/-/axios-cache-adapter-2.7.3.tgz",
+      "integrity": "sha512-A+ZKJ9lhpjthOEp4Z3QR/a9xC4du1ALaAsejgRGrH9ef6kSDxdFrhRpulqsh9khsEnwXxGfgpUuDp1YXMNMEiQ==",
+      "dependencies": {
+        "cache-control-esm": "1.0.0",
+        "md5": "^2.2.1"
+      },
+      "peerDependencies": {
+        "axios": "~0.21.1"
+      }
+    },
     "node_modules/axios/node_modules/follow-redirects": {
       "version": "1.14.1",
       "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz",
@@ -3987,6 +4000,11 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/cache-control-esm": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/cache-control-esm/-/cache-control-esm-1.0.0.tgz",
+      "integrity": "sha512-Fa3UV4+eIk4EOih8FTV6EEsVKO0W5XWtNs6FC3InTfVz+EjurjPfDXY5wZDo/lxjDxg5RjNcurLyxEJBcEUx9g=="
+    },
     "node_modules/cache-loader": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/cache-loader/-/cache-loader-4.1.0.tgz",
@@ -4147,6 +4165,14 @@
       "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
       "dev": true
     },
+    "node_modules/charenc": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
+      "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=",
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/check-types": {
       "version": "8.0.3",
       "resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz",
@@ -5119,6 +5145,14 @@
         "semver": "bin/semver"
       }
     },
+    "node_modules/crypt": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
+      "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=",
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/crypto-browserify": {
       "version": "3.12.0",
       "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
@@ -8568,8 +8602,7 @@
     "node_modules/is-buffer": {
       "version": "1.1.6",
       "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
-      "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
-      "dev": true
+      "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
     },
     "node_modules/is-callable": {
       "version": "1.2.3",
@@ -9416,6 +9449,16 @@
       "integrity": "sha512-wRJtOo1v1ch+gN8PRsj0IGJznk+kQ8mz13ds/nuhLI+Qyf/931ZlRpd92oq0IRPpZIb+bhX8pRjzIVdcPDKmiQ==",
       "dev": true
     },
+    "node_modules/md5": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
+      "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
+      "dependencies": {
+        "charenc": "0.0.2",
+        "crypt": "0.0.2",
+        "is-buffer": "~1.1.6"
+      }
+    },
     "node_modules/md5.js": {
       "version": "1.3.5",
       "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@@ -18220,6 +18263,15 @@
         }
       }
     },
+    "axios-cache-adapter": {
+      "version": "2.7.3",
+      "resolved": "https://registry.npmjs.org/axios-cache-adapter/-/axios-cache-adapter-2.7.3.tgz",
+      "integrity": "sha512-A+ZKJ9lhpjthOEp4Z3QR/a9xC4du1ALaAsejgRGrH9ef6kSDxdFrhRpulqsh9khsEnwXxGfgpUuDp1YXMNMEiQ==",
+      "requires": {
+        "cache-control-esm": "1.0.0",
+        "md5": "^2.2.1"
+      }
+    },
     "babel-eslint": {
       "version": "10.1.0",
       "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
@@ -18703,6 +18755,11 @@
         "unset-value": "^1.0.0"
       }
     },
+    "cache-control-esm": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/cache-control-esm/-/cache-control-esm-1.0.0.tgz",
+      "integrity": "sha512-Fa3UV4+eIk4EOih8FTV6EEsVKO0W5XWtNs6FC3InTfVz+EjurjPfDXY5wZDo/lxjDxg5RjNcurLyxEJBcEUx9g=="
+    },
     "cache-loader": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/cache-loader/-/cache-loader-4.1.0.tgz",
@@ -18826,6 +18883,11 @@
       "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
       "dev": true
     },
+    "charenc": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
+      "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc="
+    },
     "check-types": {
       "version": "8.0.3",
       "resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz",
@@ -19621,6 +19683,11 @@
         }
       }
     },
+    "crypt": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
+      "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs="
+    },
     "crypto-browserify": {
       "version": "3.12.0",
       "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
@@ -22356,8 +22423,7 @@
     "is-buffer": {
       "version": "1.1.6",
       "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
-      "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
-      "dev": true
+      "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
     },
     "is-callable": {
       "version": "1.2.3",
@@ -23012,6 +23078,16 @@
       "integrity": "sha512-wRJtOo1v1ch+gN8PRsj0IGJznk+kQ8mz13ds/nuhLI+Qyf/931ZlRpd92oq0IRPpZIb+bhX8pRjzIVdcPDKmiQ==",
       "dev": true
     },
+    "md5": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
+      "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
+      "requires": {
+        "charenc": "0.0.2",
+        "crypt": "0.0.2",
+        "is-buffer": "~1.1.6"
+      }
+    },
     "md5.js": {
       "version": "1.3.5",
       "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
diff --git a/package.json b/package.json
index fbf246c2..9b7689c3 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
     "@fortawesome/free-solid-svg-icons": "^5.15.3",
     "@fortawesome/vue-fontawesome": "^2.0.2",
     "axios": "^0.21.1",
+    "axios-cache-adapter": "^2.7.3",
     "core-js": "~3.6.5",
     "debounce": "^1.2.1",
     "inflection-table": "https://git.app.uib.no/api/v4/projects/16442/jobs/artifacts/0.2.25/raw/module.tar.gz?job=publish",
@@ -50,7 +51,8 @@
     ],
     "rules": {
       "no-irregular-whitespace": "off",
-      "no-unused-vars": "off"
+      "no-unused-vars": "off",
+      "no-console": "off"
     },
     "parserOptions": {
       "parser": "babel-eslint"
diff --git a/src/components/Autocomplete.vue b/src/components/Autocomplete.vue
index 725f1827..a042536d 100644
--- a/src/components/Autocomplete.vue
+++ b/src/components/Autocomplete.vue
@@ -36,11 +36,10 @@
 </template>
 
 <script>
-  import axios from "axios"
 
   export default {
     props: {
-      endpoint: String,
+      api: Function,
     },
     data: function() {
       return {
@@ -62,37 +61,17 @@
       select(item) {
         if (item) {
           if (typeof item != 'string') {
-            let self = this
-            /*
-            if (item.articles) {
-            axios.get(self.endpoint + 'articles?', {params: {lord: item.match,
-                                                             dict: self.$parent.lang}})
-              .then(
-                function(response) {
-                    ['bob', 'nob'].forEach((dict_tag) => {
-                      response.data[dict_tag].forEach((article_id) => {
-                        let article = {}
-                        article.article_id = article_id
-                        article.dictionary = dict_tag
-                        article.match = item.match
-                        item.articles.push(article)
-                        })
-                      })
-                    }
-                  )
+            this.items = []
+            this.suggesting = false
+            if (item.article_promise) {
+              item.article_promise.then((response) => {
+                item.article_ids =  response.data
+                this.submit(item)
+              })
+            }
+            else {
+              this.submit(item)
               }
-              */
-              this.items = []
-              this.suggesting = false
-
-              self.$emit('submit', item)
-
-              setTimeout(() => {
-                self.$refs.autocomplete.$refs.input.select()
-                this.items = []
-                this.suggesting = false
-                }, 1)
-
           }
           // If blurred
           else {
@@ -104,7 +83,7 @@
     methods: {
       run_query(q) {
         this.suggesting = true
-        // Put full text search in the list while processing suggestions
+        // Keep full text search in the list while requesting suggestions
         if (this.items[0]) {
           if (this.items[0].lang) {
           this.items.unshift({q: q, label: q})
@@ -114,16 +93,17 @@
           }
         }
         let self = this
-        axios.get(self.endpoint + 'suggest?', { params: {q: q,
-                                                         dict: self.$parent.lang,
-                                                         n: 9}} )
-            .then(
-                  function(response) {
+        self.api.get('suggest?', {params: {q: q, dict: self.$parent.lang, n: 9}})
+            .then(async (response) => {
                         if (self.$refs.autocomplete.searchInput == q & self.suggesting) {
 
                           let hits = []
                           response.data.forEach((item, i) => {
-                            let hit = {q: q, match: item[0], label: item[0], articles: []}
+                            let match = item[0]
+                            let hit = {q: q, match: match, label: match}
+
+                            hit.article_promise = self.api.get('articles?', {params: {lord: match, dict: self.$parent.lang}})
+
                             hit.lang = item[1]
                             hits.push(hit)
 
@@ -132,12 +112,22 @@
                           hits.push({q: q, label: q + ' '})
                           self.items = hits
                         }
-
                       self.loading = false
                     })
 
       },
+      submit(item) {
+
+          this.$emit('submit', item)
+          let self = this
 
+          setTimeout(() => {
+          self.$refs.autocomplete.$refs.input.select()
+          this.items = []
+          this.suggesting = false
+          }, 1)
+
+      }
     },
   }
 </script>
diff --git a/src/components/DictionaryView.vue b/src/components/DictionaryView.vue
index 408b3004..359d5132 100644
--- a/src/components/DictionaryView.vue
+++ b/src/components/DictionaryView.vue
@@ -29,7 +29,7 @@
           </v-radio>
         </v-radio-group>
       </div>
-      <Autocomplete @submit="select_result" :endpoint="oda_endpoint">
+      <Autocomplete @submit="select_result" :api="get_oda_api">
       </Autocomplete>
     </div>
     <div id="spinner" v-if="waiting">
@@ -73,8 +73,20 @@ import Article from './Article.vue'
 import SearchResults from './SearchResults.vue'
 import Autocomplete from './Autocomplete.vue'
 
+import { setup } from 'axios-cache-adapter'
+
+const oda_api = setup({
+  baseURL: 'https://oda.uib.no/opal-api/',
+  cache: {
+    maxAge: 15 * 60 * 1000,
+    exclude: {
+      query: false
+    }
+  }
+})
+
+
 var api_endpoint = process.env.VUE_APP_API_PREFIX + '/api/dict'
-const oda_endpoint = 'https://oda.uib.no/opal-api/'
 
 function compare_by_hgno(lemma_text) {
   return function(art1, art2) {
@@ -190,9 +202,9 @@ export default {
     api_pref: function() {
       return api_endpoint + '/' + this.lang + '/article/'
     },
-    oda_endpoint: function() {
-      return oda_endpoint
-    }
+    get_oda_api: function() {
+      return oda_api
+    },
   },
   components: {
     Article,
@@ -200,38 +212,83 @@ export default {
     SearchResults
   },
   methods: {
-    select_result: function(event) {
-      this.event = event
-      if(event.articles){
-        let source = '/' + this.lang + '/w/' + event.match
-        this.$router.push(source)
-        /*
-        this.search_results = event.articles.map(a => Object.assign(a, {source: source}))
-        this.article = null
-        this.error = null
-        
-        history.replaceState({article: this.article, search_results: this.search_results, lang: this.lang, error: this.error}, '')
-        */
-        this.waiting_for_articles = true
-        navigate_to_word(this, event.match)
-        this.$plausible.trackEvent('dropdown selection', {props: {query: event.q, match: event.match}})
-      }else{
-        this.waiting_for_articles = true
-        this.article = null
-        this.$router.push(`/${this.lang}/search/${event.q}`)
-        navigate_to_search(this, event.q)
-        this.$plausible.trackEvent('dropdown selection', {props: {query: event.q, match: '<fritekstsøk>'}})
-      }
-    },
-    update_lang_form: function(event) {
-      if (this.event){
-        this.event.articles = null
-        this.select_result(this.event)
-      }
-      else{
-        navigate_to_search(this, this.$router.history.current.params.word || this.$router.history.current.params.query)
-      }
-    },
+    select_result: function (event) {
+        this.event = event
+        if (event.article_ids) {
+          let source = '/' + this.lang + '/w/' + event.match
+          this.$router.push(source)
+          let unwrapped = []
+          for (const d in event.article_ids) {
+            event.article_ids[d].forEach(i => unwrapped.push({
+              dictionary: d,
+              id: i
+            }))
+          }
+
+          let self = this
+          Promise.all(unwrapped.map((article) => {
+              return axios.get(`${api_endpoint}/${article.dictionary}/article/${article.id}`)
+
+            }))
+            .then((response) => {
+              self.search_results = response.map((element, index) => {
+                return Object.assign(element.data, {
+                  source: source,
+                  match: event.match,
+                  dictionary: unwrapped[index].dictionary
+                })
+              })
+              self.article = null
+              self.error = null
+
+            })
+            .catch(error => {
+              self.search_results = []
+              if (error.response) {
+                self.error = "Noe gikk galt på serversiden"
+              } else {
+                self.error = "Nettverksproblemer, prøv igjen"
+              }
+            })
+            .then(() => {
+              self.$plausible.trackEvent('dropdown selection', {
+                props: {
+                  query: event.q,
+                  match: event.match
+                }
+              })
+              self.waiting_for_articles = false
+              history.replaceState({
+                article: self.article,
+                search_results: self.search_results,
+                lang: self.lang,
+                error: self.error
+              }, '')
+            })
+
+
+
+        } else {
+          this.waiting_for_articles = true
+          this.article = null
+          this.$router.push(`/${this.lang}/search/${event.q}`)
+          navigate_to_search(this, event.q)
+          this.$plausible.trackEvent('dropdown selection', {
+            props: {
+              query: event.q,
+              match: '<fritekstsøk>'
+            }
+          })
+        }
+      },
+      update_lang_form: function (event) {
+        if (this.event) {
+          this.event.articles = null
+          this.select_result(this.event)
+        } else {
+          navigate_to_search(this, this.$router.history.current.params.word || this.$router.history.current.params.query)
+        }
+      },
     article_link_click: function(item) {
       if (this.article && this.article.article_id == item.article_id){
         this.article_key++
-- 
GitLab