<template> <main> <div class="search_container"> <div class="top_bar" fluid > </div> <Autocomplete v-on:submit="select_result" :api="get_search_endpoint"> </Autocomplete> <SearchToolbar v-if="$store.state.showSearchToolbar" @updatePos="update_pos"/> </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 == 'bm' ? '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> <p v-if="error[0]">{{error[0]}}</p> <p v-if="error[1]">{{error[1]}}</p> </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 SearchToolbar from './SearchToolbar.vue' import { setup } from 'axios-cache-adapter' const SEARCH_ENDPOINT = process.env.VUE_APP_SEARCH_ENDPOINT const ARTICLE_ENDPOINT= process.env.VUE_APP_ARTICLE_ENDPOINT const dicts = {'nn': 'Nynorskorboka', 'bm': 'Bokmålsordboka', 'bm,nn': 'ordbøkene'} const api = setup({ baseURL: SEARCH_ENDPOINT, cache: { maxAge: 15 * 60 * 1000, exclude: { query: false, paths: ["articles"] // Disable caching for articles } } }) function navigate_to_article(self, source) { self.article = null self.waiting_for_articles = true /* if ((self.$route.query.nocache || '').toLowerCase() == 'true') { config.headers.cachebuster = Date.now() }*/ const lang = self.$route.params.lang axios.get(ARTICLE_ENDPOINT + lang + '/article/' + self.$route.params.id + ".json") .then(function(response){ self.article = Object.assign(response.data, {'dictionary': lang, 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 = [] if (self.lang !== 'bm') self.error.push(`Noko gjekk gale...`) if (self.lang !== 'nn') self.error.push(`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_query(self, word) { self.waiting_for_articles = true let query = self.event ? self.event : {q: word} // Get article IDs api.get('articles?', {params: {w: query.match || query.q, dict: self.lang, scope: "w", meta: 'n', wc: self.pos_selected}}).then((response) => { let article_ids = response.data let unwrapped = [] for (const d in article_ids) { article_ids[d].forEach(i => unwrapped.push({ dictionary: d, id: i })) } // Get individual articles Promise.all(unwrapped.map((article) => { return axios.get(`${ARTICLE_ENDPOINT}${article.dictionary}/article/${article.id}.json`) })) .then((response) => { self.search_results = response.map((element, index) => { return Object.assign(element.data, { dictionary: unwrapped[index].dictionary }) }) self.article = null if (! self.search_results.length) { if (query.match) { self.error = [] if (self.lang !== 'bm') { self.error.push(`Ordet «${query.match}» finst ikkje i Nynorskordboka. Bruk knappen «bokmål (bm)» om du har søkt i feil ordbok.`) } if (self.lang !== 'nn') self.error.push(`Ordet «${query.match}» finnes ikke i Bokmålsordboka. Bruk knappen «nynorsk (nn)» om du har søkt i feil ordbok.`) } else { self.error = [`Søk på «${query.q}» gir ingen treff i ${dicts[self.lang]}.`] // If not advanced search if (query.search != 2) self.error[0] += " Søk med * eller % dersom du er usikker på skrivemåten. Søketips kan du finne i «OM ORDBØKENE»." if (self.lang == "bm") self.error[0] += ` Bruk knappen «nynorsk (nn)» om du har søkt i feil ordbok.` if (self.lang == "nn") self.error[0] += ` Bruk knappen «bokmål (bm)» om du har søkt i feil ordbok.` } } else { self.error = null } }) .catch(error => { self.connection_error(error) }) .then(() => { self.waiting_for_articles = false history.replaceState({ article: self.article, search_results: self.search_results, lang: self.lang, error: self.error }, '') }) }).catch(error =>{ self.connection_error(error) }) } export default { name: 'DictionaryView', data: function() { return { article_key: 0, search_results: [], lang: 'bm,nn', posSelected: {text: "alle ordklasser", value: null}, waiting_for_articles: true, waiting_for_metadata: true, article: null, error: null, monthly_bm: null, monthly_nn: null, event: null, previous: this.$route.fullPath, fulltext_search: false } }, computed: { waiting: function() { return (this.waiting_for_articles || this.waiting_for_metadata) && this.$route.name != 'root' }, get_search_endpoint: function() { return api }, }, components: { Article, Autocomplete, SearchResults, SearchToolbar }, methods: { connection_error: function(error) { this.search_results = [] if (error.response) { this.error = [] if (this.lang !== 'bm') this.error.push(`Noko gjekk gale på serversida`) if (this.lang !== 'nn') this.error.push(`Noe gikk galt på serversiden"`) } else { this.error = [] if (this.lang !== 'bm') this.error.push(`Nettverksproblem, prøv igjen`) if (this.lang !== 'nn') this.error.push(`Nettverksproblemer, prøv igjen`) } }, select_result: function (event) { this.event = event let path = `/${this.lang}/search` let query = {q: event.match || event.q, pos: this.pos_param(), scope: this.scope} this.$router.push({path, query}) navigate_to_query(this) // Tracking let track_props = {query: event.q} if (event.match) track_props.match = event.match this.$plausible.trackEvent(event.update_lang ? "language" : 'dropdown selection', { props: track_props }) }, pos_param: function() { if (this.pos_selected) return this.pos_selected.toLowerCase() }, update_lang_form: function (lang) { this.lang = lang let name let query if (this.$route.name == 'query') { let path = `/${this.lang}/search` query = {q: this.$route.query.q, pos: this.pos_param(), scope: this.scope} console.log(query.q) this.$router.push(path, query) navigate_to_query(this, query.q) } else if(this.$route.name == 'word') { name = "w" query = this.$route.params.word this.$router.push(`/${this.lang}/${name}/${query+this.pos_param()}`) navigate_to_query(this, query) } else if (this.$route.name == 'search') { name = "search" query = this.$route.params.query this.$router.push(`/${this.lang}/${name}/${query+this.pos_param()}`) navigate_to_query(this, query) } }, update_pos: function (pos) { this.pos_selected = pos let name = null let query = null if(this.$route.name == 'query') { let path = `/${this.lang}/search` query = {q: this.$route.query.q, pos: this.pos_param(), scope: this.scope} this.$router.push({path, query}) navigate_to_query(this, query.q) } else if(this.$route.name == 'word') { name = "w" query = this.$route.params.word this.$router.push(`/${this.lang}/${name}/${query+this.pos_param()}`) navigate_to_query(this, query) } else if (this.$route.name == 'search') { name = "search" query = this.$route.params.query this.$router.push(`/${this.lang}/${name}/${query+this.pos_param()}`) navigate_to_query(this, query) } }, 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{ navigate_to_article(this, item.source) } }, details_click: function(item) { item.article.source = this.previous this.article = item.article history.replaceState({article: this.article, search_results: [], lang: this.lang, error: null}, '') }, return_to_results: function() { this.article = null } }, watch: { $route(to, from) { this.previous = from.fullPath } }, mounted: function(){ let self = this this.lang = 'bm,nn' Promise.all([ axios.get(ARTICLE_ENDPOINT + 'bm/concepts.json').then(function(response){ let concepts = response.data.concepts entities.bm = concepts }), axios.get(ARTICLE_ENDPOINT + 'nn/concepts.json').then(function(response){ let concepts = response.data.concepts entities.nn = concepts }) ]).then(function(_) { self.waiting_for_metadata = false if (self.$route.name == 'query') { self.lang = self.$route.params.lang if (self.$route.query.pos) { self.pos_selected = self.$route.query.pos.toUpperCase() } navigate_to_query(self, self.$route.query.q) } else if(self.$route.name == 'search') { self.lang = self.$route.params.lang if (self.$route.params.pos ) { self.pos_selected = self.$route.params.pos } navigate_to_query(self, self.$route.params.query) } else if(self.$route.name == 'word') { self.lang = self.$route.params.lang if (self.$route.params.pos ) { self.pos_selected = self.$route.params.pos } navigate_to_query(self, self.$route.params.word) } else if(self.$route.name == 'lookup'){ navigate_to_article(self, self.$route.path) } else { self.lang = self.$route.params.lang || self.lang 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(ARTICLE_ENDPOINT + 'bm/article/5607.json').then(function(response){ self.monthly_bm = Object.assign(response.data, {dictionary: 'bm'}) }) axios.get(ARTICLE_ENDPOINT + 'nn/article/78569.json').then(function(response){ self.monthly_nn = Object.assign(response.data, {dictionary: 'nn'}) }) }).catch(function(_){ self.error = [] if (self.lang !== 'bm') self.error.push(`Eit nettverksproblem hindra lasting av sida. Prøv å laste sida på nytt`) if (self.lang !== 'nn') self.error.push(`Et nettverksproblem hindret lasting av siden. Prøv å laste siden på nytt`) self.waiting_for_metadata = false self.waiting_for_articles = false }) }, 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 { margin-top: 1em; 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; } #spinner { margin: auto; } #search_results, #spinner, #single_article_container, div.welcome, div.search_container, .error { padding-left: calc((100vw - 1200px) / 2); padding-right: calc((100vw - 1200px) / 2); } .error > p { margin-left: 15px; } #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.bm .dict-label::before { content: "fra "; } div.monthly article.nn .dict-label::before { content: "frå "; } div.monthly details, div.monthly h3 { display: none; } .v-label span { color: var(--v-primary-base); } .top_bar { display: -webkit-box; -webkit-box-orient: horizontal; flex-direction: row; flex-wrap: wrap; vertical-align: bottom !important; padding-bottom: 10px; } .lang_select_container { padding-left: 10px; } .pos_select_container { padding-left: 10px; padding-right: 10px; padding-bottom: 0px; padding-top: 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 { padding-top: 10px; display: table-cell; } .return_to_results a { color: var(--v-primary-base) !important; text-decoration: none; } .nav_arrow { vertical-align: top !important; color: var(--v-primary-base) !important; } </style>