<template> <main> <div class="search_container"> <div class="lang_select_container"> <v-radio-group row v-model="lang" @change="update_lang_form"> <template v-slot:label tabindex="1"> <span aria-label="Vis resultat i begge ordbøkene eller bruk radioknapp for bokmålsordboka eller radioknapp for nynorskordboka">VIS</span> </template> <v-radio value="bob,nob" color="primary"> <template v-slot:label> <span> begge{{$vuetify.breakpoint.smAndDown ? '' : ' ordbøkene'}} </span> </template> </v-radio> <v-radio value="bob" color="primary"> <template v-slot:label> <span> {{$vuetify.breakpoint.xs ? 'bm' : 'bokmål (bm)'}} </span> </template> </v-radio> <v-radio value="nob" color="primary"> <template v-slot:label> <span> {{$vuetify.breakpoint.xs ? 'nn' : 'nynorsk (nn)'}} </span> </template> </v-radio> </v-radio-group> </div> <Autocomplete @submit="select_result" :api="get_oda_api"> </Autocomplete> </div> <div id="spinner" v-if="waiting"> <v-progress-circular indeterminate color="secondary" size="120"></v-progress-circular> </div> <SearchResults :hits="search_results" :lang="lang" :key="lang" @article-click="article_link_click" @details-click="details_click" v-if="search_results.length && ! waiting && ! article" /> <div id="single_article_container" v-if="article"> <div class="return_to_results" v-if="search_results && search_results.length"> <router-link :to="article.source" @click.native="return_to_results()"> <v-icon class="nav_arrow">arrow_left</v-icon>Tilbake til {{article.dictionary == 'bob' ? 'søkeresultater' : 'søkjeresultat'}} </router-link> </div> <Article :key="article_key" :article="article" @article-click="article_link_click" /> </div> <div class="welcome" v-if="! (article || error || search_results.length || waiting)"> <div class="monthly" :class="$vuetify.breakpoint.name"> <div> <Article :article="monthly_bm" @article-click="article_link_click" /> </div> <div> <Article :article="monthly_nn" @article-click="article_link_click" /> </div> </div> </div> <div class="error" v-if="error"> <h1>Ingen treff</h1> {{error}} </div> </main> </template> <script> import axios from "axios" import entities from '../utils/entities.js' 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' var oda_dev = 'https://oda.uib.no/opal/dev' function compare_by_hgno(lemma_text) { return function(art1, art2) { let lemma1 = art1.lemmas.filter(lemma => lemma.lemma == lemma_text)[0] let lemma2 = art2.lemmas.filter(lemma => lemma.lemma == lemma_text)[0] return lemma1.hgno - lemma2.hgno } } axios.interceptors.request.use(function (config) { if (!config.url) { config.headers["x-api-key"] = "ZkYiyRVXxH86ijsvhx3cH4SY5Iik2ijI3BKVJGMm" } return config; }, function (error) { return Promise.reject(error); }); function navigate_to_article(self, source) { let config = {headers: {}} if ((self.$route.query.nocache || '').toLowerCase() == 'true') { config.headers.cachebuster = Date.now() } axios.get(oda_dev + '/' + self.$route.params.lang + '/article/' + self.$route.params.id, config) .then(function(response){ self.article = Object.assign(response.data, {'dictionary': self.$route.params.lang, source: source, results: self.search_results}) self.search_results = [] self.error = null }) .catch(function(error){ if (error.response && error.response.status == 404) { self.error = "Vi har ingen artikkel med id " + self.$route.params.id } else { self.error = "Noe gikk galt..." } }) .then(function(response){ self.waiting_for_articles = false history.replaceState({article: self.article, search_results: [], lang: self.lang, error: self.error}, '') if (source) { self.$plausible.trackEvent('internal link incoming', {props: {origin: source}}) } }) } function navigate_to_search(self, query) { axios.get(self.api_old + 'search?q=' + query) .then(function(response){ self.search_results = response.data if (! self.search_results.length) { self.error = "Søk på «" + decodeURIComponent(query) + "» gir ingen treff i ordbøkene. Søk med * inne i ordet dersom du er usikker på skrivemåten. Bruk knappen «begge ordbøkene» om du har søkt i feil ordbok." } else { self.error = null } }) .catch(function(error){ if (error.response && error.response.status == 400) { self.error = "Søkeuttrykket inneholder feil" } else if (error.response) { self.error = "Noe gikk galt på serversiden" } else { self.error = "Nettverksproblemer, prøv igjen" } }) .then(function(_){ self.waiting_for_articles = false history.replaceState({article: self.article, search_results: self.search_results, lang: self.lang, error: self.error}, '') }) } function navigate_to_word(self, word) { axios.get(self.api_old + 'suggest?q=' + word) .then(function(response){ self.search_results = response.data.filter(result => result.match == word).sort(compare_by_hgno(word)) if (! self.search_results.length) { self.error = "Ordet «" + decodeURIComponent(word) + "» finnes ikke i ordbøkene" } else { self.error = null } }) .catch(function(error){ if (error.response) { self.error = "Noe gikk galt på serversiden" } else { self.error = "Nettverksproblemer, prøv igjen" } }) .then(function(_){ self.waiting_for_articles = false history.replaceState({article: self.article, search_results: self.search_results, lang: self.lang, error: self.error}, '') }) } export default { name: 'DictionaryView', data: function() { return { article_key: 0, search_results: [], lang: 'bob,nob', waiting_for_articles: true, waiting_for_metadata: true, article: null, error: null, monthly_bm: null, monthly_nn: null, event: null } }, computed: { waiting: function() { return (this.waiting_for_articles || this.waiting_for_metadata) && this.$route.name != 'root' }, api_pref: function() { return oda_dev + '/' + this.lang + '/article/' }, api_old: function() { return api_endpoint + '/' + this.lang + '/article/' }, get_oda_api: function() { return oda_api }, }, components: { Article, Autocomplete, SearchResults }, methods: { 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) { if (d == this.lang || this.lang == "bob,nob") { event.article_ids[d].forEach(i => unwrapped.push({ dictionary: d, id: i })) } } let self = this Promise.all(unwrapped.map((article) => { return axios.get(`${oda_dev}/${article.dictionary}/article/${article.id}.json`) })) .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) { this.lang = event if (this.event && !this.article) { this.event.articles = null this.select_result(this.event) } }, article_link_click: function(item) { if (this.article && this.article.article_id == item.article_id){ this.article_key++ history.replaceState({article: this.article, search_results: this.search_results, lang: this.lang, error: this.error}, '') }else{ this.article = null this.waiting_for_articles = true navigate_to_article(this, item.source) } }, details_click: function(item) { this.article = item.article history.replaceState({article: this.article, search_results: [], lang: this.lang, error: null}, '') }, return_to_results: function() { this.article = null } }, mounted: function(){ let self = this this.lang = 'bob,nob' Promise.all([ axios.get(api_endpoint + '/bob').then(function(response){ let concepts = response.data.concepts entities.bob = concepts }), axios.get(api_endpoint + '/nob').then(function(response){ let concepts = response.data.concepts entities.nob = concepts }) ]).then(function(_) { self.waiting_for_metadata = false if(self.$route.name == 'word') { self.lang = self.$route.params.lang navigate_to_word(self, self.$route.params.word) } else if(self.$route.name == 'lookup'){ navigate_to_article(self, self.$route.params.id) } else if (self.$route.name == 'search') { self.lang = self.$route.params.lang navigate_to_search(self, self.$route.params.query) } else { self.lang = self.$route.params.lang || 'bob,nob' self.waiting_for_articles = false history.replaceState({article: self.article, search_results: self.search_results, lang: self.lang, error: self.error}, '') } // words of the month axios.get(oda_dev + '/bob/article/5607.json').then(function(response){ self.monthly_bm = Object.assign(response.data, {dictionary: 'bob'}) }) axios.get(oda_dev + '/nob/article/78569.json').then(function(response){ self.monthly_nn = Object.assign(response.data, {dictionary: 'nob'}) }) }).catch(function(_){ self.error = "Et nettverksproblem hindret lasting av siden. Prøv å laste siden på nytt" self.waiting_for_metadata = false self.waiting_for_articles = false }) }, watch: { $route() { this.$plausible.trackEvent('language', {props: {code: this.$route.params.lang}}) } }, created: function() { let self = this window.onpopstate = function (event) { if (event.state) { self.article = event.state.article self.search_results = event.state.search_results self.lang = event.state.lang self.error = event.state.error } } } } </script> <style> main { flex: 1 0 auto; background-color: var(--v-tertiary-base); display: flex; flex-direction: column; } div.welcome { flex-grow: 10; background-image: url('../assets/books.jpg'); background-repeat: no-repeat; } div.welcome article { border-style: none; } .search_container { background-color: var(--v-tertiary-base); padding-top: 1px; padding-bottom: 10px; } #search_results, #spinner, #single_article_container, div.welcome, div.search_container, .error { padding-left: calc((100vw - 1000px) / 2); padding-right: calc((100vw - 1000px) / 2); } #spinner { padding-top: 40px; } div.monthly { display: flex; width: 100%; } div.monthly > div { flex: 50%; } div.monthly.sm, div.monthly.xs { flex-direction: column; } div.monthly article.bob .dict-label::before { content: "fra "; } div.monthly article.nob .dict-label::before { content: "frå "; } div.monthly details, div.monthly h3 { display: none; } .v-label span { color: var(--v-primary-base); } .lang_select_container { padding-left: 10px; } li.suggestion { font-weight: bold; padding-left: 20px; padding-top: 5px; padding-bottom: 5px; border: 0px; background-image: none; } ::selection { background: var(--v-secondary-base); color: white; } .return_to_results { display: table-cell; } .return_to_results a { color: var(--v-primary-base) !important; } .nav_arrow { vertical-align: top !important; color: var(--v-primary-base) !important; } .choose > .nav_arrow { vertical-align: -30% !important; } </style>