diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e4d78b877dc981a7a5bac364c29e4cfa433e05d5..387808a768c44c6db7d7a10fbfc59ff38e0864f2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,6 +5,8 @@ stages: build: image: node:latest stage: build + only: + - master script: - npm install --progress=false - npm run build @@ -16,6 +18,8 @@ build: deploy: image: uibit/awscli:latest stage: deploy + only: + - master script: - rm -rf ~/.aws - mv .aws ~/ diff --git a/src/App.vue b/src/App.vue index 6db69bde6d5c9d3ef6eff9f3bf26e927fdc2133b..dcff51aa986526367b705fd0d85d649f47fc10ef 100644 --- a/src/App.vue +++ b/src/App.vue @@ -5,23 +5,26 @@ <span class="top">EID AV UNIVERSITETET I BERGEN OG SPRÅKRÅDET</span> <p class="mission-statement">Bokmålsordboka og Nynorskordboka er dei einaste fritt tilgjengelege ordbøkene som gjer svar på korleis ord skal skrivast og bøyast i norsk i tråd med gjeldande rettskriving.</p> - <autocomplete :debounceTime="100" :auto-select="true" :search="search" @submit="select_result" placeholder="søk..." ref="search"> - <template #result="{result, props}"> - <Preview v-bind="props" :searchHit="result"> - </Preview> - </template> - </autocomplete> - <span class="lang-select-intro">SØK PÅ</span> - <input type="radio" id="radio_both" value="bob,nob" v-model="lang"> - <label for="radio_both">Begge</label> - <input type="radio" id="radio_bob" value="bob" v-model="lang"> - <label for="radio_bob">Bokmål</label> - <input type="radio" id="radio_nob" value="nob" v-model="lang"> - <label for="radio_nob">Nynorsk</label> + <div class="search_container"> + <autocomplete :debounceTime="100" :auto-select="true" :search="search" @submit="select_result" placeholder="søk..." ref="search"> + <template #result="{result, props}"> + <li class="suggestion" v-bind="props">{{result.label}}</li> + </template> + </autocomplete> + <div class="lang_select_container"> + <select class="lang_select" name="lang" v-model="lang"> + <option value="bob,nob">Begge målformer </option> + <option value="bob">Bokmål</option> + <option value="nob">Nynorsk</option> + </select> + </div> + </div> </header> <img id="spinner" :class="waiting ? 'show' : 'hide'" src="./assets/spinner.gif" alt="Venter på innhold" /> - <SearchResults :hits="search_results" :lang="lang" @search-hit-click="search_hit_click" v-show="! waiting" /> - <Article :key="article_key" :article="article" @article-click="article_link_click" /> + <SearchResults :hits="search_results" :lang="lang" @article-click="article_link_click" v-show="! waiting" /> + <div id="single_article_container"> + <Article :key="article_key" :article="article" @article-click="article_link_click" /> + </div> <div class="welcome" v-show="! (article.error || article.lemmas.length || search_results.length || waiting)"> <p>Bør ordet <em>crew</em> inn i dei norske standardordbøkene? Korleis definerar vi <em>c-kjendis</em>, og korleis bøyar vi eigentleg <em>abaya</em> på nynorsk - er det fleire <em>abayaer</em> eller <em>abayaar</em>? Blir <em>en vegetar</em> @@ -75,14 +78,11 @@ window.onpopstate = function (event) { } } -function navigate_to_article(self, article_id, origin_article, origin_lemma) { - if (origin_article) { - self.$plausible.trackEvent('internal link incoming', {props: {origin: `/${self.$route.params.lang}/${origin_article}/${origin_lemma || '_'}`}}) - } - - axios.get(self.api_pref + '' + article_id) +function navigate_to_article(self) { + axios.get(api_endpoint + '/' + self.$route.params.lang + '/article/' + self.$route.params.id) .then(function(response){ self.article = response.data + self.search_results = [] }) .catch(function(error){ if (error.response && error.response.status == 404) { @@ -100,7 +100,7 @@ function navigate_to_article(self, article_id, origin_article, origin_lemma) { }) .then(function(response){ self.waiting_for_articles = false - history.replaceState({article: self.article, search_results: self.search_results, lang: self.lang}, '') + history.replaceState({article: self.article, search_results: [], lang: self.lang}, '') }) } @@ -113,6 +113,15 @@ function navigate_to_search(self, query) { }) } +function navigate_to_word(self, word) { + axios.get(self.api_pref + 'suggest?q=' + word) + .then(function(response){ + self.search_results = response.data.filter(result => result.match.length == word.length) + self.waiting_for_articles = false + history.replaceState({article: self.article, search_results: self.search_results, lang: self.lang}, '') + }) +} + export default { name: 'app', data: function() { @@ -139,9 +148,15 @@ export default { return new Promise(resolve => { return axios.get(self.api_pref + 'suggest?q=' + q).then( function(response) { - let hits = q.length ? [{}] : [] - hits = hits.concat(response.data) - resolve(hits.map(h => Object.assign(h, {q: q}))) + let hits = q.length ? [{q: q, label: q + ' (alt)'}] : [] + response.data.forEach((item, i) => { + if (hits[0].label != item.match) { + hits.splice(0, 0, {q: q, label: item.match, articles: []}) + } + hits[0].articles.push(item) + }); + hits.reverse() + resolve(hits) }) }) } @@ -157,10 +172,10 @@ export default { select_result: function(event) { this.$refs.search.value = '' document.activeElement.blur() - if(event.body){ - this.$router.push('/' + event.dictionary + '/' + event.article_id + '/' + event.match) - this.search_results = [] - this.article = event + if(event.articles){ + this.$router.push('/' + this.lang + '/w/' + event.label) + this.search_results = event.articles + this.article = {lemmas: [], body:{pronunciation: [], definitions: [], etymology: []}} history.replaceState({article: this.article, search_results: this.search_results, lang: this.lang}, '') this.$plausible.trackEvent('dropdown selection', {props: {query: event.q, dictionary: event.dictionary, match: event.match}}) }else{ @@ -175,11 +190,9 @@ export default { this.article_key++ history.replaceState({article: this.article, search_results: this.search_results, lang: this.lang}, '') }else{ - let current_art_id = this.article.article_id - let current_lemma = this.article.lemmas[0].lemma this.article = {lemmas: [], body:{pronunciation: [], definitions: [], etymology: []}} this.waiting_for_articles = true - navigate_to_article(this, item.article_id, current_art_id, current_lemma) + navigate_to_article(this) } }, search_hit_click: function(article){ @@ -202,22 +215,18 @@ export default { self.waiting_for_nob_metadata = false }) - this.lang = this.$route.params.lang || this.$route.query.lang || 'bob,nob' - if(this.$route.query.q) { - navigate_to_search(this, this.$route.query.q) + this.lang = 'bob,nob' + + if(this.$route.name == 'word') { + navigate_to_word(this, this.$route.params.word) } - else if(this.$route.params.id){ + else if(this.$route.name == 'lookup'){ navigate_to_article(this, this.$route.params.id) } else { this.waiting_for_articles = false history.replaceState({article: this.article, search_results: this.search_results, lang: this.lang}, '') } - }, - watch: { - $route() { - this.lang = this.$route.params.lang || this.$route.query.lang || 'bob,nob' - } } } </script> @@ -249,7 +258,7 @@ main { background-attachment: fixed; } -header, #search_results, #spinner, article, footer, div.welcome { +header, #search_results, #spinner, #single_article_container, footer, div.welcome { padding-left: calc((100vw - 1000px) / 2); padding-right: calc((100vw - 1000px) / 2); background-image: url('./assets/beta.png'); @@ -333,6 +342,15 @@ footer { padding-bottom: 20px; } +.search_container { + display: flex; + flex-direction: row; +} + +li.suggestion { + font-weight: bold; +} + footer p { padding-left: 20px; padding-right: 20px; @@ -350,4 +368,24 @@ footer p { padding: 10px; padding-left: 20px; } + +select.lang_select { + appearance: none; + background-color: transparent; + border: none; + padding: 0 1em 0 0; + margin: 0; + width: 100%; + height: 100%; + font-family: inherit; + font-size: inherit; + font-weight: bold; + cursor: inherit; + line-height: inherit; + background-image: url('./assets/down_arrow.png'); + background-position: 98% center; + background-repeat: no-repeat; + background-size: 10%; +} + </style> diff --git a/src/assets/down_arrow.png b/src/assets/down_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..d07a042e5c09a86a29b3bee864adde459964aa6f Binary files /dev/null and b/src/assets/down_arrow.png differ diff --git a/src/components/Article.vue b/src/components/Article.vue index 5a458211a7758b0c94d5b3c5c5893788ba185080..9dbf0af9bd611d7428e3ca72bc96b7560c2bccdb 100644 --- a/src/components/Article.vue +++ b/src/components/Article.vue @@ -1,22 +1,22 @@ <template> <article v-show="article.lemmas.length || article.error"> - <Header :lemmas="article.lemmas" /> + <Header :lemmas="article.lemmas" :dictionary="dictionary" /> <section v-if="! article.error && article.body.pronunciation && article.body.pronunciation.length"> <h3>Uttale</h3> <ul> - <DefElement v-for="(element, index) in article.body.pronunciation" :key="index" :body='element' @article-click="article_link_click" /> + <DefElement v-for="(element, index) in article.body.pronunciation" :dictionary="dictionary" :key="index" :body='element' @article-click="article_link_click" /> </ul> </section> <section v-if="! article.error && article.body.etymology && article.body.etymology.length"> <h3>Etymologi</h3> <ul> - <DefElement v-for="(element, index) in article.body.etymology" :key="index" :body='element' @article-click="article_link_click" /> + <DefElement v-for="(element, index) in article.body.etymology" :dictionary="dictionary" :key="index" :body='element' @article-click="article_link_click" /> </ul> </section> <section v-if="! article.error"> <h3>Definisjoner</h3> <ol> - <Definition v-for="definition in article.body.definitions" :level="1" :key="definition.id" :body='definition' @article-click="article_link_click" /> + <Definition v-for="definition in article.body.definitions" :dictionary="dictionary" :level="1" :key="definition.id" :body='definition' @article-click="article_link_click" /> </ol> </section> <section v-if="article.error"> @@ -36,6 +36,11 @@ export default { props: { article: Object }, + computed: { + dictionary: function() { + return this.article.dictionary || this.$route.params.lang + } + }, components: { DefElement, Definition, @@ -72,6 +77,10 @@ li.level1.definition { list-style: upper-alpha; } +li.level2.definition { + list-style: decimal; +} + li.level3.definition { list-style: lower-alpha; } diff --git a/src/components/CompoundList.vue b/src/components/CompoundList.vue index d5cd17b11d252074af01fea3bf425c7619c0aa9c..fd29e97609a195f2b6445ab40b27961746c69009 100644 --- a/src/components/CompoundList.vue +++ b/src/components/CompoundList.vue @@ -1,9 +1,9 @@ <template> <li class="compound_list"> <ul> - <DefElement :body="body.intro" v-if="body.intro" /> + <DefElement :body="body.intro" v-if="body.intro" :dictionary="dictionary" /> <li :key="index" v-for="(item, index) in body.elements"> - <router-link :to="item.article_id + (item.definition_id ? '#def'+item.definition_id : '')" @click.native="article_link_click(item)"> + <router-link :to="'/' + dictionary + '/' + item.article_id + (item.definition_id ? '#def'+item.definition_id : '')" @click.native="article_link_click(item)"> <span class="homograph" :key="index">{{item.lemmas[0].hgno ? String.fromCharCode(0x215f + item.lemmas[0].hgno) : ''}}</span> {{item.lemmas[0].lemma}}; </router-link> @@ -18,7 +18,8 @@ import DefElement from './DefElement.vue' export default { name: 'CompoundList', props: { - body: Object + body: Object, + dictionary: String }, components: { DefElement diff --git a/src/components/DefElement.vue b/src/components/DefElement.vue index 416257a1f3c2bef35afc77c34573d85036023a7d..d1e40f4045d0b3a4ed4b75d39827d372386a644a 100644 --- a/src/components/DefElement.vue +++ b/src/components/DefElement.vue @@ -21,11 +21,12 @@ export default { tag: { type: String, default: 'li' - } + }, + dictionary: String }, computed: { unparsed: function(){ - let lang = this.$route.params.lang + let lang = this.dictionary let path_lemma = this.$route.params.lemma return this.body.items.map( function(item){ @@ -34,7 +35,7 @@ export default { type: item.type_, html: '', link_text: item.word_form || item.lemmas[0].lemma, - ref: (path_lemma ? '../' : '') + item.article_id + '/' + (item.word_form || item.lemmas[0].lemma) + (item.definition_id ? '#def' + item.definition_id : ''), + ref: '/' + lang + '/' + item.article_id + '/' + (item.word_form || item.lemmas[0].lemma) + (item.definition_id ? '#def' + item.definition_id : ''), article_id: item.article_id, definition_id: item.definition_id, definition_order: item.definition_order diff --git a/src/components/Definition.vue b/src/components/Definition.vue index d1b2c2d3b926a64005b261c557cf3a897b23683e..1d8d1574d38e01d96e2c468ba64cdd53e9fbd194 100644 --- a/src/components/Definition.vue +++ b/src/components/Definition.vue @@ -1,13 +1,13 @@ <template> <li :class="['definition', 'level'+level]" :ref="'def' + body.id" :id="'def' + body.id"> <ul class="explanation"> - <li :is="element_wrapper.template" :body="element_wrapper.element" v-for="(element_wrapper, index) in template_name_added" :key="index" @article-click="article_link_click">{{element_wrapper.element}}</li> + <li :is="element_wrapper.template" :body="element_wrapper.element" :dictionary="element_wrapper.dictionary" v-for="(element_wrapper, index) in template_name_added" :key="index" @article-click="article_link_click">{{element_wrapper.element}}</li> </ul> <div :is="level < 3 ? 'ol' : 'ul'" class="sub_definitions" v-if="subdefs.length"> - <Definition :level="level+1" :body="subdef" v-for="(subdef, index) in subdefs" :key="index" @article-click="article_link_click" /> + <Definition :level="level+1" :body="subdef" v-for="(subdef, index) in subdefs" :dictionary="dictionary" :key="index" @article-click="article_link_click" /> </div> <ul class="sub_articles" v-if="subArticles.length"> - <SubArticle :body="subart" v-for="(subart, index) in subArticles" :key="index" @article-click="article_link_click" /> + <SubArticle :body="subart" v-for="(subart, index) in subArticles" :dictionary="dictionary" :key="index" @article-click="article_link_click" /> </ul> </li> </template> @@ -22,7 +22,8 @@ var Definition = { name: 'Definition', props: { body: Object, - level: Number + level: Number, + dictionary: String }, components: { DefElement, @@ -33,6 +34,7 @@ var Definition = { }, computed: { template_name_added: function(){ + let dictionary = this.dictionary return this.body.elements.filter(el => ! ['definition', 'sub_article'].includes(el.type_)).map( function(element){ return { @@ -41,7 +43,8 @@ var Definition = { 'example': 'Example', 'compound_list': 'CompoundList' }[element.type_] || 'li', - 'element': element + 'element': element, + 'dictionary': dictionary } }) }, diff --git a/src/components/Example.vue b/src/components/Example.vue index 9ec0e1926f6ee583e588ccee8ffddc900baae0eb..809ce182c6cbdc9a8d54de97a9e0f1db953a7f24 100644 --- a/src/components/Example.vue +++ b/src/components/Example.vue @@ -1,7 +1,7 @@ <template> <li class="example"> - <DefElement tag="q" :body="body.quote" @article-click="article_link_click" /><span v-if="body.explanation && body.explanation.content.length"> - </span> - <DefElement tag="span" :body="body.explanation" v-if="body.explanation && body.explanation.content.length" @article-click="article_link_click" /> + <DefElement tag="q" :body="body.quote" @article-click="article_link_click" :dictionary="dictionary" /><span v-if="body.explanation && body.explanation.content.length"> - </span> + <DefElement tag="span" :body="body.explanation" v-if="body.explanation && body.explanation.content.length" @article-click="article_link_click" :dictionary="dictionary" /> </li> </template> @@ -11,7 +11,8 @@ import DefElement from './DefElement.vue' export default { name: 'Example', props: { - body: Object + body: Object, + dictionary: String }, components: { DefElement diff --git a/src/components/Header.vue b/src/components/Header.vue index 8b7c326b6dc9fd28148a3c559e41dca756f62880..6dc316f3f8fc5e3201db4fbeb585c3c86d2c0eec 100644 --- a/src/components/Header.vue +++ b/src/components/Header.vue @@ -11,6 +11,7 @@ <component v-for="grp in Object.keys(lemma.inflection_groups)" :key="grp.replace('/', '_')" :is="grp.replace('/', '_')" + :dictionary="dictionary" :standardisations="lemma.inflection_groups[grp]"></component> </div> </div> @@ -36,7 +37,8 @@ import VERB_sPass from './inflection/VerbSPass.vue' export default { name: 'Header', props: { - lemmas: Array + lemmas: Array, + dictionary: String }, computed: { group_list: function() { diff --git a/src/components/SearchResults.vue b/src/components/SearchResults.vue index 31aa2ce4ce90e6ac1ce47572a9cbc811645bbb63..d1c3cc73e443aed1bd5f0efe5aaa26487652d99a 100644 --- a/src/components/SearchResults.vue +++ b/src/components/SearchResults.vue @@ -4,25 +4,24 @@ <div class="flex-container"> <ul class="hits" v-if="results_bob.length"> <li><h4>Bokmål</h4></li> - <Preview v-for="(result, index) in results_bob" :key="index" :searchHit="result" @click.native="article_link_click(result)"> - </Preview> + <li class="article_container" v-for="(result, index) in results_bob" :key="index"> + <Article :article="result" @article-click="article_link_click"> + </Article> + </li> </ul> <ul class="hits" v-if="results_nob.length"> <li><h4>Nynorsk</h4></li> - <Preview v-for="(result, index) in results_nob" :key="index" :searchHit="result" @click.native="article_link_click(result)"> - </Preview> - </ul> - <ul class="hits" v-if="results_norsk.length"> - <li><h4>Norsk Ordbok</h4></li> - <Preview v-for="(result, index) in results_norsk" :key="index" :searchHit="result" @click.native="article_link_click(result)"> - </Preview> + <li class="article_container" v-for="(result, index) in results_nob" :key="index"> + <Article :article="result" @article-click="article_link_click"> + </Article> + </li> </ul> </div> </section> </template> <script> -import Preview from './Preview.vue' +import Article from './Article.vue' export default { name: 'SearchResults', @@ -36,19 +35,15 @@ export default { }, results_nob: function(){ return this.hits.filter(hit => hit.dictionary == 'nob') - }, - results_norsk: function(){ - return this.hits.filter(hit => hit.dictionary == 'norsk_ordbok') } }, methods: { - article_link_click: function(result) { - this.$router.push('/' + result.dictionary + '/' + result.article_id) - this.$emit('search-hit-click', result) + article_link_click: function(item) { + this.$emit('article-click', item) } }, components: { - Preview + Article } } @@ -71,6 +66,8 @@ export default { .hits { margin-top:0px; + margin-left: 20px; + margin-right: 20px; } .flex-container { @@ -84,4 +81,8 @@ export default { .flex-container h4 { margin: 0px; } + + li.article_container { + border-bottom: solid 2px #560027; + } </style> diff --git a/src/components/SubArticle.vue b/src/components/SubArticle.vue index af1bb69779cb95926111f950c5daa6ad1cdb822f..8d499eee2976e816980c96276228830058b10e62 100644 --- a/src/components/SubArticle.vue +++ b/src/components/SubArticle.vue @@ -1,13 +1,13 @@ <template> <li class="sub_article"> <span class="sub_article_header"> - <router-link :to="link_prefix + body.article_id" @click.native="article_link_click(body)"> + <router-link :to="'/' + dictionary + '/' + body.article_id" @click.native="article_link_click(body)"> {{body.lemmas[0]}} </router-link> </span> <ul> - <DefElement :body="body.intro" v-if="body.intro" @article-click="article_link_click" /> - <Definition :level="9" :body="body.article.body.definitions[0]" @article-click="article_link_click" /> + <DefElement :body="body.intro" v-if="body.intro" :dictionary="dictionary" @article-click="article_link_click" /> + <Definition :level="9" :body="body.article.body.definitions[0]" :dictionary="dictionary" @article-click="article_link_click" /> </ul> </li> </template> @@ -18,7 +18,8 @@ import DefElement from './DefElement.vue' export default { name: 'SubArticle', props: { - body: Object + body: Object, + dictionary: String }, components: { DefElement diff --git a/src/components/inflection/Adjective.vue b/src/components/inflection/Adjective.vue index cbe6d50b5e0d5f34c3bef0070e3d56c6803a4d55..88ace29cc80159b9b758dd375ccc5f04321fec82 100644 --- a/src/components/inflection/Adjective.vue +++ b/src/components/inflection/Adjective.vue @@ -32,14 +32,15 @@ export default { name: "ADJ", props: { - standardisations: Array + standardisations: Array, + dictionary: String }, computed: { i18n: function() { return { bob: ['Entall', 'Flertall', 'Hun', 'Intet', 'estemt'], nob: ['Eintal', 'Fleirtal', 'Ho', 'Inkje', 'unden'] - }[this.$route.params.lang] + }[this.dictionary] } } } diff --git a/src/components/inflection/AdjectiveMF.vue b/src/components/inflection/AdjectiveMF.vue index f0d7712d09e7e9ac5065667be82dbe05fc408fdf..daea6517278ec2d66ddf4858ca8fb13e1903bd1b 100644 --- a/src/components/inflection/AdjectiveMF.vue +++ b/src/components/inflection/AdjectiveMF.vue @@ -34,14 +34,15 @@ export default { name: "ADJ_masc_fem", props: { - standardisations: Array + standardisations: Array, + dictionary: String }, computed: { i18n: function() { return { bob: ['Entall', 'Flertall', 'Hun', 'Intet', 'estemt'], nob: ['Eintal', 'Fleirtal', 'Ho', 'Inkje', 'unden'] - }[this.$route.params.lang] + }[this.dictionary] } } } diff --git a/src/components/inflection/Noun.vue b/src/components/inflection/Noun.vue index 32fa3bfaaab3125ee3d1b1d254d2875c811eba76..78328bd0d4db67f6382ee044c72c1df3e4e98a97 100644 --- a/src/components/inflection/Noun.vue +++ b/src/components/inflection/Noun.vue @@ -20,14 +20,15 @@ export default { name: "NOUN", props: { - standardisations: Array + standardisations: Array, + dictionary: String }, computed: { i18n: function() { return { bob: ['Entall', 'Flertall', 'Ubestemt', 'Bestemt', {Masc: ['mask.', 'en'], Fem: ['fem.', 'ei'], Neuter: ['nøytr.', 'et']}], nob: ['Eintal', 'Fleirtal', 'Ubunden', 'Bunden', {Masc: ['mask.', 'ein'], Fem: ['fem.', 'ei'], Neuter: ['nøytr.', 'eit']}] - }[this.$route.params.lang] + }[this.dictionary] } } } diff --git a/src/components/inflection/Verb.vue b/src/components/inflection/Verb.vue index 4f5b0c3c69a66c5cd55e34d1f68c9d4466a7c00d..c14cb6346f7bc95dd66b3d4d898af8310407a11a 100644 --- a/src/components/inflection/Verb.vue +++ b/src/components/inflection/Verb.vue @@ -36,14 +36,15 @@ export default { name: "VERB", props: { - standardisations: Array + standardisations: Array, + dictionary: String }, computed: { i18n: function() { return { bob: ['hun', 'Intet', 'Bestemt', 'Flertall'], nob: ['ho', 'Inkje', 'Bunden', 'Fleirtal'] - }[this.$route.params.lang] + }[this.dictionary] } } } diff --git a/src/main.js b/src/main.js index 03ca4fcc842fa2f444a34bf7a66b431be9fe2395..64591997e9f80b6103f6e9159227630bfa1af3f7 100644 --- a/src/main.js +++ b/src/main.js @@ -19,6 +19,10 @@ const router = new VueRouter({ name: 'root', path: '/', component: App }, // No props, no nothing + { + name: 'word', + path: '/:lang/w/:word' + }, { name: 'lookup', path: '/:lang/:id(\\d+)/:lemma?',