Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
Showing
with 4417 additions and 920 deletions
<template>
<v-app id="app">
<header>
<div class="about-link"><router-link aria-label="Lenke til informasjon om ordbøkene" to="/om" tabindex="2">OM ORDBØKENE</router-link></div>
<h1><a aria-label="Ordbøkene bokmålsordboka og nynorskordboka sitt nettsted" :class="$vuetify.breakpoint.name" href="/" tabindex="1">Ordbøkene</a></h1>
<div class="beta" :title="release">{{version_label}}</div>
<p class="sub-title"><router-link aria-label="Ordbøkene bokmålsordboka og nynorskordboka sitt nettsted" to="/" tabindex="3">Bokmålsordboka og Nynorskordboka</router-link></p>
</header>
<router-view></router-view>
<footer>
<div :class="$vuetify.breakpoint.xs?'sm':'lg'">
<div>
<img id="srlogo" src="./assets/Sprakradet_logo_neg.png" alt="">
</div>
<div>
<img id="uiblogo" src="./assets/uib-logo.svg" alt="">
<router-link ref="skip_link" to="#main" class="skip-link" @click.native="focus_main">{{$t('accessibility.main_content')}}</router-link>
<div>
<TopBar/>
</div>
<div v-if="$route.name!='about'" class = "banner" :class="$vuetify.breakpoint">
<div v-if="show_banner_text">
<a href="/">
<h1 :class="$vuetify.breakpoint.name">Ordbøkene</h1></a>
<p class="sub-title">{{$t("sub_title")}}</p>
</div>
</div>
<router-view ref="router_view"></router-view>
<footer :class="$vuetify.breakpoint.name">
<div id="photo-attribution" aria-hidden="true" v-if="!$route.name && $vuetify.breakpoint.mdAndUp">{{$t('photo')}}</div>
<div class="footer-row">
<div class="logos" :class="$vuetify.breakpoint.xs?'sm':'lg'">
<img id="srlogo" src="./assets/Sprakradet_logo_neg.png" alt="Språkrådet, logo">
<img id="uiblogo" src="./assets/uib-logo.svg" alt="Universitetet i Bergen, logo">
</div>
<div class="footer-text"><em>{{$t('dicts.bm')}}</em>{{$t('and')}}<em>{{$t('dicts.nn')}}</em>{{$t('footer_description')}}
</div>
<div>Bokmålsordboka og Nynorskordboka viser skrivemåte og bøying i tråd med norsk rettskriving. Språkrådet og Universitetet i Bergen står bak ordbøkene. Gi oss gjerne tilbakemelding på <a href="mailto:ordbok-beta@uib.no">ordbok-beta@uib.no</a>.</div>
<FooterMenu />
<div></div></div>
<div class="accessibility-declaration"><a href="https://uustatus.no/nb/erklaringer/publisert/b2a6f8d0-3a16-4716-8bc8-46ac3c161935" target="_blank">{{$t('accessibility_statement')}}</a><v-icon color="white" small>open_in_new</v-icon></div>
</footer>
</v-app>
</template>
<script>
import TopBar from './components/TopBar.vue'
import FooterMenu from './components/FooterMenu.vue'
export default {
metaInfo() {
return { htmlAttrs: {lang: {nob: "nb", nno: "nn", eng: "en", ukr: "uk"}[this.$i18n.locale]}}
},
computed: {
show_banner_text: function() {
return !this.$route.name || (window.innerHeight > 800 && this.$route.name != 'about' )
}
},
components: {
TopBar,
FooterMenu
},
data: function() {
return {
version_label: process.env.VUE_APP_VERSION_LABEL,
release: process.env.VUE_APP_RELEASE
return {
contact_dialog: false,
help_dialog: false,
announcement: localStorage.getItem('app_info') == 'false' ? false : true,
announce_test: localStorage.getItem('beta_info') == 'false' ? false : true
}
},
mounted: function(){
document.title = 'Ordbøkene - ' + this.version_label
methods: {
close_announcement: function() {
localStorage.setItem('app_info', 'false')
this.announcement = false
},
close_test_announcement: function() {
localStorage.setItem('beta_info', 'false')
this.announce_test = false
},
focus_main: function() {
this.$refs.router_view.$refs.main.focus();
}
}
}
</script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inria+Serif:ital,wght@0,400;0,700;1,400;1,700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Inria+Serif:ital,wght@0,400;0,700;1,400;1,700&family=Noto+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap');
@font-face {
font-family: NotoParen;
font-style: italic;
src: url('./assets/NotoSansParen.woff') format('woff');
unicode-range: U+28-29;
}
@import url('https://fonts.googleapis.com/css2?family=Inria+Serif:ital,wght@0,400;0,700;1,400;1,700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Inria+Serif:ital,wght@0,400;0,700;1,400;1,700&family=Noto+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap');
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('./assets/o-0NIpQlx3QUlC5A4PNjXhFVZNyBx2pqPA.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Inria Serif';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('./assets/fC1lPYxPY3rXxEndZJAzN3Srdy0.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Inria Serif';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('./assets/fC14PYxPY3rXxEndZJAzN3wQUjjCjl0.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('./assets/o-0IIpQlx3QUlC5A4PNr5TRA.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Noto Sans';
font-style: italic;
font-weight: 400;
font-display: swap;
src: url('./assets/o-0OIpQlx3QUlC5A4PNr4ARCQ_k.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: NotoParen;
font-style: italic;
src: url('./assets/NotoSansParen.woff') format('woff');
unicode-range: U+28-29;
}
#app {
......@@ -52,123 +141,175 @@
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: var(--v-text-base);
display: flex;
flex-direction: column;
height: 100%;
background-color: var(--v-tertiary-base)
}
html, body {
height: 100%
}
body {
margin: 0px;
main {
flex: 1;
}
a {
text-decoration: none;
}
h1 {
h1, .article h3 {
font-family: Inria Serif;
color: var(--v-primary-base);
padding-left: 15px;
color: var(--v-primary-base)
}
h1 > a.xs, h1 > a.sm {
font-size: 36px;
p a, .article a, .search_notification a, .did_you_mean a, .info-card a {
color: var(--v-text-base) !important;
text-decoration: none;
border-bottom: 1px solid var(--v-anchor-base);
}
p a:hover, .article a:hover, .search_notification a:hover, .info-card a:hover {
color: var(--v-anchor-base) !important;
}
header > h1 > a {
color: var(--v-primary-base) !important;
a.article_header {
border: none;
}
.banner h1 {
font-size: 48px;
margin: 0px;
}
.about-link {
text-align: right;
margin-right: 15px;
float: right;
h1.xs, h1.sm {
font-size: 36px;
}
header > p {
padding-left: 18px;
padding-bottom: 15px;
padding-top: 0;
.banner {
padding-top: 10px;
}
.beta {
position: absolute;
top: 2px;
font-size: 20px;
color: #BBBBBB;
padding-left: 15px;
.v-list-item__title {
word-break: break-all;
overflow-wrap: break-word;
}
.about-link > a{
.banner p {
padding-bottom: 10px;
padding-top: 0;
padding-left: 18px;
}
.banner a {
text-decoration: none;
border-bottom: solid var(--v-primary-base) 2px;
font-size: 12px;
color: var(--v-primary-base);
color: var(--v-primary-base) !important;
}
header {
position: relative;
padding-left: calc((100vw - 1200px) / 2);
padding-right: calc((100vw - 1200px) / 2);
.beta {
font-family: Inria Serif;
font-size: 18px;
color: white;
margin-left: 10px;
}
footer {
position: relative;
padding-left: calc((100vw - 1000px) / 2);
padding-right: calc((100vw - 1000px) / 2);
.language-dialog-title {
font-family: Inria Serif;
color: white;
}
footer a {
text-decoration: underline;
color: #ffffff !important;
.top-bar .v-toolbar__content {
padding-left: calc((100vw - 1200px) / 2) !important;
padding-right: calc((100vw - 1200px) / 2) !important;
}
header {
padding-top: 20px;
.banner {
position: relative;
padding-left: calc((100vw - 1200px) / 2);
padding-right: calc((100vw - 1200px) / 2);
background-color: var(--v-tertiary-base);
}
.banner.xs, .banner.sm {
padding-top: 0px;
}
.banner.xs div, .banner.sm div{
display: none;
}
.sub-title {
font-size: 18px;
margin: 0px;
margin-bottom: 0px !important;
}
.sub-title {
color: var(--v-primary-base) !important;
}
footer a, .notification a {
color: #ffffff !important;
}
footer {
padding-left: calc((100vw - 1200px) / 2);
padding-right: calc((100vw - 1200px) / 2);
font-size: smaller;
display: table;
flex-direction: row;
background-color: var(--v-primary-base);
color: #ffffff;
padding-bottom: 10px;
}
footer > div {
display: table-cell;
vertical-align: middle;
.logos {
display: flex;
align-items: center;
flex-direction: row !important;
gap: 10px;
padding: 10px;
padding-left: 24px;
}
footer > div.lg > div {
padding: 0;
display: table-cell;
vertical-align: middle;
padding: 10px;
.accessibility-declaration {
text-align: center;
padding-top: 10px;
}
footer > div.sm > div {
vertical-align: middle;
text-align: center;
padding: 10px;
.footer-row {
display: flex;
flex-direction: rows;
align-content: center;
align-items:center;
padding-top: 24px;
gap: 10px;
}
footer.sm .footer-row, footer.xs .footer-row{
display: flex;
flex-direction: column;
padding-left: 24px;
gap: 10px;
}
footer.sm div, footer.xs div {
justify-content: center;
justify-items: center;
}
#srlogo {
height: 20px;
}
......@@ -178,4 +319,94 @@ footer > div.sm > div {
}
.v-btn {
font-weight: bold !important;
}
.article .v-btn:focus, .v-dialog .v-btn:focus {
outline: solid 2px var(--v-primary-base) !important;
}
/* all inflection-table css shoud be moved to beta.ordbok */
.infl-table caption {
position: absolute !important;
height: 1px; width: 1px;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
}
td[class="infl-group"] {
background-color: var(--v-button-base) !important;
color: var(--v-text-base) !important;
font-style: normal !important;
font-weight: bold;
}
th[class="infl-group"] {
background-color: var(--v-button-base) !important;
font-style: unset;
}
.v-application .rounded-t-xl {
border-top-left-radius: 28px !important;
border-top-right-radius: 28px !important;
}
.v-application .rounded-xl {
border-radius: 28px !important;
}
.theme--light.v-label, .theme--light.v-subheader, .transparent-text{
color: var(--v-text-base)
}
#photo-attribution {
position: absolute;
right: 0px;
width: 100%;
text-align: right;
padding-right: 6px;
padding-top: 1px;
padding-right: 6px;
color: white;
font-size: smaller;
}
.article h5, .about h3 {
color: var(--v-primary-base);
font-size: 14px;
padding-left: 12px;
padding-top: 6px;
}
.article h4, .monthly-title h2, .about h2, .v-dialog h2 {
font-size: 1.17em;
color: var(--v-primary-base);
font-variant: all-small-caps;
}
.skip-link {
position: absolute;
top: -10
}
.skip-link:focus {
position: unset;
top: unset;
text-align: center;
padding: 10px;
width: 100%;
color: white;
background-color: var(--v-secondary-base)
}
.v-alert {
border-radius: 0px !important;
}
</style>
src/assets/background.jpg

451 KiB

File added
File added
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="64"
height="64"
viewBox="0 0 16.933333 16.933334"
version="1.1"
id="svg8"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
sodipodi:docname="logo-icon.svg"
inkscape:export-filename="/home/has022/Documents/256.png"
inkscape:export-xdpi="170.66667"
inkscape:export-ydpi="170.66667">
<defs
id="defs2">
<linearGradient
inkscape:collect="always"
id="linearGradient4545">
<stop
style="stop-color:#560027;stop-opacity:1;"
offset="0"
id="stop4541" />
<stop
style="stop-color:#560027;stop-opacity:0;"
offset="1"
id="stop4543" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4545"
id="linearGradient4547"
x1="9.9130554"
y1="277.94998"
x2="28.186945"
y2="277.94998"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="3.959798"
inkscape:cx="233.45423"
inkscape:cy="-7.9212896"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="3840"
inkscape:window-height="2084"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-280.06665)">
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:35.27777863px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#bc477b;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="1.4181669"
y="295.72998"
id="text3715"><tspan
sodipodi:role="line"
x="1.4181669"
y="295.72998"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:21.16666603px;font-family:'Inria Serif';-inkscape-font-specification:'Inria Serif';fill:#bc477b;fill-opacity:1;stroke-width:0.26458332"
id="tspan21">O</tspan></text>
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="144"
height="144"
viewBox="0 0 38.099999 38.100001"
version="1.1"
id="svg8"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
sodipodi:docname="anchor-icon.svg"
inkscape:export-filename="/home/has022/Documents/logo-transparent.png"
inkscape:export-xdpi="170.66667"
inkscape:export-ydpi="170.66667">
<defs
id="defs2">
<linearGradient
inkscape:collect="always"
id="linearGradient4545">
<stop
style="stop-color:#560027;stop-opacity:1;"
offset="0"
id="stop4541" />
<stop
style="stop-color:#560027;stop-opacity:0;"
offset="1"
id="stop4543" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4545"
id="linearGradient4547"
x1="9.9130554"
y1="277.94998"
x2="28.186945"
y2="277.94998"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="1.979899"
inkscape:cx="180.06393"
inkscape:cy="31.926411"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="3840"
inkscape:window-height="2084"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-258.89998)">
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:35.27777863px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#bc477b;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="8.4772501"
y="288.745"
id="text3715"><tspan
sodipodi:role="line"
x="8.4772501"
y="288.745"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:31.75px;font-family:'Inria Serif';-inkscape-font-specification:'Inria Serif';fill:#bc477b;fill-opacity:1;stroke-width:0.26458332"
id="tspan21">O</tspan></text>
</g>
</svg>
File added
File added
File added
This diff is collapsed.
<template>
<article v-if="article" :class="dictionary">
<Header :lemmas="article.lemmas" :dictionary="dictionary" :article_id="article.article_id" />
<div class="article_content" :class="$vuetify.breakpoint.name">
<div v-if="article" :lang="{nob:'nb', nno: 'nn', eng: 'en', 'ukr': 'uk'}[dictionary]" class="article-container">
<span :lang="lang_tag_locale" v-if="$vuetify.breakpoint.smAndDown || !$route.name || $route.name == 'lookup'" class="dict-label" role="heading" aria-level="2">{{dict_label}}</span>
<div class="article" v-bind:class="{'expanded': !collapsed && collapsable, 'collapsable': collapsable, 'hide-label': hide_label, 'v-sheet v-card rounded-xl': !$parent.article}" v-if="article">
<div :class="$vuetify.breakpoint.name" v-if="!invalid">
<Header :title_id="title_id" :lemmas="article.lemmas" :dictionary="dictionary" :article_id="article.article_id" @toggle-collapse = "toggle_collapse"/>
<div class="article_content" :class="$vuetify.breakpoint.name" v-show="!collapsed" ref="article_content">
<section v-if="article.body.pronunciation && article.body.pronunciation.length" class="pronunciation">
<h3>Uttale</h3>
<h4 :lang="lang_tag_locale">{{$t('article.headings.pronunciation', content_locale)}}</h4>
<ul>
<DefElement v-for="(element, index) in article.body.pronunciation" :dictionary="dictionary" :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" @error="article_error"/>
</ul>
</section>
<section v-if="article.body.etymology && article.body.etymology.length" class="etymology">
<h3>Opphav</h3>
<h4 :lang="lang_tag_locale">{{$t('article.headings.etymology', content_locale)}}</h4>
<ul>
<DefElement v-for="(element, index) in article.body.etymology" :dictionary="dictionary" :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" @error="article_error"/>
</ul>
</section>
<section class="definitions" v-if="has_content" tabindex="0">
<h3 tabindex="0">{{def_label}}</h3>
<section class="definitions" v-if="has_content">
<h4 :lang="lang_tag_locale">{{$t('article.headings.definitions', content_locale)}}</h4>
<ol>
<Definition v-for="definition in article.body.definitions" :dictionary="dictionary" :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" @error="article_error"/>
</ol>
</section>
<section v-if="sub_articles.length" class="expressions">
<h3>Faste uttrykk</h3>
<h4 :lang="lang_tag_locale">{{$t('article.headings.expressions', content_locale)}}</h4>
<ul>
<SubArticle :body="subart" v-for="(subart, index) in sub_articles" :dictionary="dictionary" :key="index" @article-click="article_link_click" />
<SubArticle :body="subart" v-for="(subart, index) in sub_articles" :dictionary="dictionary" :key="index" @article-click="article_link_click" @error="article_error"/>
</ul>
</section>
<div class="fade">
<router-link :to="link_to_self.ref" @click.native="details_click(link_to_self)">
Velg <img class="nav_arrow" src="../assets/arrow_right.svg">
</router-link>
</div>
</div>
<ArticleFooter :article="article"/>
</article>
<ArticleFooter v-if="!collapsed" :article="article"/>
</div>
<div v-else><v-icon left>warning</v-icon> {{$t('error.article', {no: article.article_id, dict: $t('dicts_inline.'+this.dictionary)})}}</div>
</div>
</div>
</template>
<script src="/dist/vue-social-sharing.js"></script>
<script>
import DefElement from './DefElement.vue'
import Definition from './Definition.vue'
import SubArticle from './SubArticle.vue'
import Header from './Header.vue'
import ArticleFooter from './ArticleFooter.vue'
import entities from '../utils/entities.js'
import Mark from 'mark.js';
function find_sub_articles(definition) {
let sub_art_list = []
let sub_definitions = definition.elements.filter(el => el.type_ == 'definition')
sub_definitions.forEach((subdef, i) => {
sub_art_list = sub_art_list.concat(find_sub_articles(subdef))
})
let sub_articles = definition.elements.filter(el => el.type_ == 'sub_article')
sub_art_list = sub_art_list.concat(sub_articles)
try {
let sub_definitions = definition.elements.filter(el => el.type_ == 'definition')
let sub_articles = definition.elements.filter(el => el.type_ == 'sub_article' && el.lemmas)
sub_definitions.forEach((subdef, i) => {
sub_art_list = sub_art_list.concat(find_sub_articles(subdef))
})
sub_art_list = sub_art_list.concat(sub_articles)
return sub_art_list
}
function find_content(definition) {
let content_list = []
let sub_definitions = definition.elements.filter(el => el.type_ == 'definition')
sub_definitions.forEach((subdef, i) => {
content_list = content_list.concat(find_content(subdef))
})
let content_nodes = definition.elements.filter(el => ['explanation', 'example', 'compound_list'].includes(el.type_))
content_list = content_list.concat(content_nodes)
}
catch(error) {
console.log("find_sub_articles", this.article.article_id, this.dictionary, '"'+error.message+'"')
return content_list
return []
}
}
export default {
name: 'Article',
props: {
article: Object
article: Object,
articleLookup: Boolean,
title_id: String,
queryPattern: String,
scope: String,
},
computed: {
link_to_self: function() {
return {
ref: '/' + this.dictionary + '/' + this.article.article_id + '/' + encodeURIComponent(this.article.lemmas[0].lemma),
article: this.article
data: function() {
return {
is_collapsed: true,
invalid: false
}
},
metaInfo() {
if (this.articleLookup) {
return {title: this.article.lemmas[0].lemma + ' | ' + {"bm,nn": "Bokmålsordboka og Nynorskordboka", "bm": "Bokmålsordboka", "nn": "Nynorskordboka"}[this.dictionary],
meta: [{name: 'description', vmid: 'description', content: this.meta_description}],
link: [{rel: "canonical", href: `https://ordbokene.no/${this.article.dictionary}/${this.article.article_id}`} ]
}
}
},
computed: {
hide_label: function() {
//collapsable || (collapsed && $store.state.collapseArticles != 'never')
if (this.$parent.count_bm || this.$parent.count_nn) {
let two_results = (this.$parent.count_bm + this.$parent.count_nn) < 3
if (two_results) {
return false
}
else if (this.$vuetify.breakpoint.mdAndUp && (this.collapsed || this.$store.state.collapseArticles == 'never')) {
return true
}
else {
return false
}
}
},
dictionary: function() {
return this.article.dictionary
},
def_label: function() {
return this.dictionary == 'bob' ? 'Betydning og bruk' : 'Tyding og bruk'
content_locale: function() {
if (this.$i18n.locale == 'eng') {
return 'eng'
} else if (this.$i18n.locale == 'ukr') {
return 'ukr'
} else {
return {bm: 'nob', nn: 'nno'}[this.dictionary]
}
},
fulltext_highlight: function() {
return this.$store.state.fulltextHighlight
},
collapsable: function() {
if (this.$parent.$options.name != 'SearchResults') {
this.is_collapsed = false
return false
}
let collapsable = this.$store.state.collapseArticles
if (collapsable == 'never') {
this.is_collapsed = false
return false
}
if (collapsable == 'always') {
this.is_collapsed = true
return true
}
if (collapsable == 'auto') {
this.is_collapsed = this.$parent.$options.name == 'SearchResults' && (this.$parent.results_bm.length + this.$parent.results_nn.length > 2)
return this.$parent.$options.name == 'SearchResults' && (this.$parent.results_bm.length + this.$parent.results_nn.length > 2)
}
},
collapsed: {
get() {
if (this.$parent.$options.name != 'SearchResults') {
return false
}
if (!this.collapsable) {
this.is_collapsed = false
}
return this.is_collapsed
},
set(value) {
this.is_collapsed = value
}
},
snippet: function() {
if (this.collapsable && this.article.body.definitions) {
return this.meta_description
}
return null
},
meta_description: function() {
return this.parse_definitions(this.article.body.definitions)
},
link_to_self: function() {
try {
return {
ref: '/' + this.dictionary + '/' + this.article.article_id,
article: this.article
}
} catch(error) {
console.log("link_to_self",this.article.article_id, this.dictionary, '"'+error.message+'"')
this.invalid = true
//console.error(error)
return {ref: "", article: this.article}
}
},
lang_tag_locale: function() {
return {nob: "nb", nno: "nn", eng: "en", ukr: "uk"}[this.content_locale]
},
example_label: function() {
return this.dictionary == 'bob' ? 'Eksempel' : 'Døme'
dict_label: function() {
let label = ''
const dictionary = this?.article?.dictionary
if (dictionary) {
if (this.$route.name) {
label = this.$t(`dicts.${dictionary}`, this.content_locale)
} else {
label = this.$t('from', this.content_locale) + " " + this.$t(`dicts_from.${dictionary}`, this.content_locale)
}
}
return label
},
sub_articles: function() {
return this.article.body.definitions.reduce((acc, val) => acc.concat(find_sub_articles(val)), []).sort((s1, s2) => s1.lemmas[0].localeCompare(s2.lemmas[0]))
},
has_content: function() {
return this.article.body.definitions.reduce((acc, val) => acc.concat(find_content(val)), []).length > 0
for (const definition of this.article.body.definitions) {
for (const element of definition.elements) {
if (['explanation', 'example', 'compound_list', 'definition'].includes(element.type_)) {
return true
}
}
}
return false
}
},
components: {
......@@ -107,11 +213,115 @@ export default {
Header,
ArticleFooter
},
mounted: function() {
if (this.scope && this.scope.includes("f")) {
let instance = new Mark(this.$refs.article_content)
if (/[_%|]/.test(this.queryPattern)) {
instance.markRegExp(new RegExp(this.queryPattern), {acrossElements: true, separateWordSearch:false});
}
else {
instance.mark(this.queryPattern, {acrossElements: true, separateWordSearch:false, accuracy: 'exact', wildcards: 'enabled'});
}
}
if (this.$route.hash == "#"+ this.title_id) {
let focused = document.getElementById(this.title_id)
if (focused) focused.focus()
}
else if (this.$route.hash) {
let focused =document.getElementById(this.$route.hash.replace("#",""))
if (focused) focused.scrollIntoView({block: "center"})
}
},
methods: {
article_error: function(payload) {
console.log("DefElement",payload.location, this.article.article_id, this.dictionary, '"'+payload.message+'"')
},
parse_subitems: function(explanation, text) {
let new_string = ""
let old_parts = text.split(/(\$)/)
let linkIndex = 0
let self = this
old_parts.forEach((item) => {
if (item == '$') {
let subitem = explanation.items[linkIndex]
if (/^\d$/.test(subitem.text)) {
if (subitem.type_ == "superscript") {
new_string += "⁰¹²³⁴⁵⁶⁷⁸⁹"[parseInt(subitem.text)]
}
else if (subitem.type_ == "subscript") {
new_string += "₀₁₂₃₄₅₆₇₈₉"[parseInt(subitem.text)]
}
}
else if (subitem.id) {
new_string += entities[self.dictionary][explanation.items[linkIndex].id].expansion
}
else if (subitem.text) {
if (subitem.text.includes('$')) {
new_string += self.parse_subitems(subitem, subitem.text)
}
else new_string += subitem.text
}
else {
if (explanation.items[linkIndex].lemmas) {
new_string += explanation.items[linkIndex].word_form || explanation.items[linkIndex].lemmas[0].lemma
}
}
linkIndex += 1
}
else {
new_string += item
}
})
return new_string
},
parse_definitions: function(node) {
let definitionTexts = []
let self = this
try {
node.forEach((definition) => {
if (definition.elements) {
if (definition.elements[0].content) {
let new_string = self.parse_subitems(definition.elements[0], definition.elements[0].content)
if (new_string.substring(new_string.length, new_string.length - 1) == ":") {
new_string = new_string.slice(0, -1)
}
definitionTexts.push(new_string)
}
else if (definition.elements[0].elements) {
definitionTexts.push(self.parse_definitions(definition.elements))
}
}
})
} catch(error) {
console.log("parse_definitions",this.article.article_id, this.dictionary, '"'+error.message+'"')
this.invalid = true
definitionTexts = []
}
let snippet = definitionTexts.join("\u00A0•\u00A0")
return snippet
},
toggle_collapse: function() {
if (this.collapsed) {
this.$plausible.trackEvent('expand article', {props: {article: `${this.dictionary} ${this.article.article_id}`}})
}
this.collapsed = !this.collapsed
},
article_link_click: function(item) {
this.$emit('article-click', item)
},
details_click: function(item) {
item.title_id = this.title_id
this.$emit('details-click', item)
}
}
......@@ -119,37 +329,43 @@ export default {
</script>
<style>
article {
.article {
position: relative;
padding: 24px;
margin: 10px;
border-radius: 30px;
border: solid 1px var(--v-border-base);
padding-bottom: 12px;
margin-bottom: 20px;
margin-right: 10px;
margin-left: 10px;
background-color: #ffffff;
}
#single_article_container article {
border: solid 2px var(--v-primary-base);
section.xs .article, section.sm .article {
margin-bottom: 10px !important;
}
.fade {
display: none;
section.md .article, section.lg .article, section.xl .article {
padding-top: 10px;
}
section {
padding-top: 1em;
.welcome .article_footer {
display: block;
}
h3 {
color: var(--v-primary-base);
font-variant: small-caps;
#single_article_container .article {
border: none;
margin-top: 10px;
}
section {
padding-top: 10px;
padding-bottom: 10px
}
section.etymology > h3, section.pronunciation > h3 {
section.etymology > h4, section.pronunciation > h4 {
display: inline;
font-size: 14px;
font-variant: revert;
}
section.etymology ul, section.pronunciation ul, section.etymology li, section.pronunciation li {
......@@ -161,7 +377,7 @@ section.etymology li:not(:first-child):not(:last-child):before, section.pronunci
}
section.etymology li:not(:first-child):last-child:before, section.pronunciation li:not(:first-child):last-child:before {
content: " eller ";
content: "; ";
font-size: smaller;
}
......@@ -212,8 +428,36 @@ ul li.definition {
list-style: disc;
}
.fade .nav_arrow {
vertical-align: sub;
.choose {
color: var(--v-primary-base) !important;
text-decoration: none;
}
.info-card {
padding: 12px;
}
.expanded {
padding-bottom: 34px;
}
.header {
border-radius: 0px !important;
}
.dict-label {
color: var(--v-primary-base) ;
font-weight: bold;
position: absolute;
padding-left: 34px;
margin-top: -2px;
z-index: 2;
font-variant-caps: all-small-caps;
font-size: 1.17em;
}
</style>
<template>
<div class="article_footer">
<span v-if="hasPointer">
<button aria-label="Del ordboksartikkel på Facebook" class="share_button" tabindex="0">
<ShareNetwork network="facebook"
title=""
:url="share_link"
tabindex="-1">
<v-icon dense>$vuetify.icons.facebook</v-icon>
</ShareNetwork>
</button>
<button aria-label="Del del ordboksartikkel på Twitter" class="share_button" tabindex="0">
<ShareNetwork
network="twitter"
:url="share_link"
:title="dict_label"
hashtags="#ordbøkene"
tabindex="-1">
<v-icon dense>$vuetify.icons.twitter</v-icon>
</ShareNetwork>
</button>
</span>
<button v-if="webShareApiSupported" class="share_button" @click="shareViaWebShare">
<span v-if="!hasPointer" class = "share_text">Del ordet</span>
<v-icon dense>$vuetify.icons.share</v-icon>
</button>
<div class = "footer_title" tabindex="0">Ordbøkene.no
</div>
<div :lang="lang_tag_locale" class="article_footer">
<v-snackbar centered
max-width="300px"
min-width="300px"
max-height="36px"
min-height="36px"
rounded="pill"
v-model='copy_popup'
timeout="1000">
<span class="text-center">{{$t(what_copied, content_locale)}}</span>
</v-snackbar>
<v-btn v-if="showLinkCopy"
small text class="toolbar-button" rounded @click="copy_link">
<v-icon small left>link</v-icon><span class = "button-text">{{$t("article.copy_link", content_locale)}}</span>
</v-btn>
<v-btn v-if="webShareApiSupported" text small class="toolbar-button" rounded @click="shareViaWebShare">
<v-icon small left>share</v-icon><span class = "button-text">{{$t("article.share", content_locale)}}</span>
</v-btn>
<v-dialog max-width="600px" v-model="citation_dialog">
<template v-slot:activator="{ on, attrs }">
<v-btn @click="track_citation" text small class="toolbar-button" rounded v-on="on" v-bind="attrs">
<v-icon left small>format_quote</v-icon> <span class = "button-text">{{$t("article.cite", content_locale)}}</span>
</v-btn>
</template>
<v-card>
<v-toolbar elevation="0">
<v-toolbar-title><span role="heading" aria-level="1">{{$t('article.cite_title')}}</span></v-toolbar-title>
<v-spacer></v-spacer><v-toolbar-items><v-btn @click="close_citation_dialog" text>{{$t('close')}}<v-icon right>close</v-icon></v-btn></v-toolbar-items></v-toolbar>
<v-card-text class="text--primary">
{{$t("article.cite_description[0]", content_locale)}}<em>{{$t('dicts.'+$parent.dictionary)}}</em>{{$t("article.cite_description[1]", content_locale)}}
<br/>
<div id = "citation" v-html="this.create_citation()"/>
</v-card-text>
<v-card-actions>
<v-btn depressed small rounded @click="copy_citation"><br>
<v-icon left small icon>content_copy</v-icon> <span class = "button-text">{{$t("article.copy", content_locale)}}</span>
</v-btn>
<v-btn depressed small rounded @click="download_ris"><br>
<v-icon left small icon>get_app</v-icon> <span class = "button-text">{{$t("article.download")}}</span>
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
......@@ -36,62 +64,125 @@ export default {
article: Object
},
computed: {
dict_label: function() { // TODO: reuse code used in header
return {
'bob': 'Fra bokmålsordboka',
'nob': 'Frå nynorskordboka'
}[this.article.dictionary] + ': ' + this.article.lemmas[0].lemma || ''
},
webShareApiSupported() {
return navigator.share
},
showLinkCopy() {
return (!navigator.share || this.$vuetify.breakpoint.mdAndUp) && navigator.clipboard
},
hasPointer() {
return window.matchMedia('(hover: hover) and (pointer: fine)').matches
},
share_link: function() {
let host = window.location.hostname === 'localhost'? 'https://dev.ordbok.uib.no/' : window.location.href
return host + this.article.dictionary + '/' + this.article.article_id + '/' + encodeURIComponent(this.article.lemmas[0].lemma)
content_locale: function() {
return this.$parent.content_locale
},
lang_tag_locale: function() {
return this.$parent.lang_tag_locale
}
},
data: function() {
return {
copy_popup: false,
citation_dialog: false,
what_copied: null
}
},
methods: {
track_citation() {
this.$plausible.trackEvent('citation click', {props: {article: `${this.$parent.dictionary} ${this.$parent.article.article_id}`}})
},
shareViaWebShare() {
this.$plausible.trackEvent('webshare', {props: {article: `${this.$parent.dictionary} ${this.$parent.article.article_id}`}})
navigator.share({
title: "Ordbøkene.no: " + this.article.lemmas[0].lemma,
text: "",
url: "/" + this.article.dictionary + '/' + this.article.article_id + '/' + encodeURIComponent(this.article.lemmas[0].lemma)
url: "/" + this.article.dictionary + '/' + this.article.article_id
})
}
},
create_link() {
return 'https://ordbokene.no/' + this.article.dictionary + '/' + this.article.article_id
},
get_citation_info() {
let date = new Date();
let dd = (date.getDate() < 10? '0' : '') + date.getDate()
let mm = (date.getMonth() < 9? '0' : '') + (date.getMonth()+1)
let yyyy = date.getFullYear()
let link = this.create_link()
let lemma = this.article.lemmas[0].lemma
let dict = this.$t(`dicts.${this.article.dictionary}`, this.content_locale)
return [lemma, dd, mm, yyyy, link, dict]
},
create_citation() {
const [lemma, dd, mm, yyyy, link, dict] = this.get_citation_info()
let citation = this.$t("article.citation", {lemma, link, dd, mm, yyyy, dict})
return citation
},
copy_link() {
let link = this.create_link()
console.log(`${this.$parent.dictionary} ${this.$parent.article.article_id}`)
this.$plausible.trackEvent('copy link', {props: {article: `${this.$parent.dictionary} ${this.$parent.article.article_id}`}})
let self = this
navigator.clipboard.writeText(link).then(() => {
self.what_copied = this.$t("article.link_copied")
self.copy_popup = true
}).catch(err => {
console.log("ERROR COPYING:",err)
})
},
copy_citation() {
let citation = document.getElementById("citation").textContent;
navigator.clipboard.writeText(citation)
this.citation_dialog = false
this.what_copied = this.$t("article.citation_copied")
this.copy_popup = true
},
close_citation_dialog() {
this.citation_dialog = false
},
download_ris() {
const [lemma, dd, mm, yyyy, link] = this.get_citation_info()
const a = document.createElement("a")
a.style = "display: none"
a.setAttribute("download", `${lemma}_${this.article.dictionary}.ris`)
const dict = {"bm":"Bokmålsordboka", "nn": "Nynorskordboka"}[this.article.dictionary]
const text = `TY - DICT\nTI - ${lemma}\nT2 - ${dict}\nPB - Språkrådet og Universitetet i Bergen\nUR - ${link}\nY2 - ${yyyy}/${mm}/${dd}/\nER - `
a.setAttribute('href', 'data:application/x-research-info-systems;charset=utf-8,' + encodeURIComponent(text));
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
},
}
}
</script>
<style scoped>
.share_text {
padding-right: 10px;
vertical-align: middle;
}
.v-icon {
color: var(--v-primary-base) !important;
}
.share_button {
padding-right: 4px;
font-weight: bold;
font-size: 14px;
.toolbar-button {
margin-right: 8px;
margin-top: 8px;
font-size: 12px;
}
.article_footer {
color: var(--v-primary-base);
padding-top: 24px;
}
.footer_title {
font-family: Inria Serif;
font-weight: bold;
font-size: 18px;
float: right;
#citation {
margin-top: 12px;
padding: 12px;
background-color: var(--v-button-base) !important;
margin-bottom: 6px;
}
</style>
<template>
<div class="autocomplete-container" :class="$vuetify.breakpoint.name">
<v-combobox aria-label="søkefelt"
v-model="select"
:loading="loading"
:items="items"
:search-input.sync="search"
item-text="label"
:menu-props="{maxHeight: $vuetify.breakpoint.name === 'xs' ? 190 : 500, transition: 'fade-transition', allowOverflow: true}"
prepend-inner-icon="search"
:append-icon="null"
return-object
rounded
hide-no-data
auto-select-first
no-filter
hide-details
label="Søk..."
solo
full-width
flat
outlined
placeholder="Søk her"
ref="autocomplete"
color="primary"
:dense="$vuetify.breakpoint.smAndDown"
>
<template v-slot:item="data">
<span class="search-hit">
{{data.item.label}}
</span>
({{data.item.lang? data.item.lang[1] ? "bm, nn" : {"bob": "bm", "nob": "nn"}[data.item.lang[0]] : 'fritekstsøk'}})
</template>
</v-combobox>
</div>
</template>
<script>
import axios from "axios"
export default {
props: {
endpoint: String,
},
data: function() {
return {
loading: false,
items: [],
search: null,
select: null,
suggesting: null,
}
},
watch: {
search (val) {
if (! val) {
this.items = []
} else {
this.run_query(val)
}
},
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
self.$emit('submit', item)
setTimeout(() => {
self.$refs.autocomplete.$refs.input.select()
this.items = []
this.suggesting = false
}, 1)
}
// If blurred
else {
this.items = []
}
}
}
},
methods: {
run_query(q) {
this.suggesting = true
// Put full text search in the list while processing suggestions
if (this.items[0]) {
if (this.items[0].lang) {
this.items.unshift({q: q, label: q})
}
else {
this.items[0] = {q: q, label: q}
}
}
let self = this
axios.get(self.endpoint + 'suggest?', { params: {q: q,
dict: self.$parent.lang,
n: 9}} )
.then(
function(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: []}
hit.lang = item[1]
hits.push(hit)
});
// whitespace necessary because duplicates aren't allowed in the dropdown
hits.push({q: q, label: q + ' '})
self.items = hits
}
self.loading = false
})
},
},
}
</script>
<style scoped>
.search-hit {
font-weight: bold;
margin-right: 5px;
color: var(--v-primary-base);
}
.autocomplete-container {
padding-left: 10px;
padding-right: 10px;
}
</style>
......@@ -5,10 +5,10 @@
<li
:key="index"
v-for="(item, index) in body.elements"
><router-link
>{{' '}}<router-link
:to="'/' + dictionary + '/' + item.article_id + (item.definition_id ? '#def'+item.definition_id : '')"
@click.native="article_link_click(item)"
> {{item.lemmas[0].lemma}}</router-link>
>{{item.lemmas[0].lemma}}</router-link>
</li>
</ul>
</li>
......
<template>
<v-list>
<v-list-item>
<div>
<h2>{{$t('contact.content[0]')}}</h2><p>
{{$t('contact.content[2]')}} <a href="mailto:ordbok@uib.no">ordbok@uib.no</a></p>
<h2>{{$t('contact.content[1]')}}</h2><p>
{{$t('contact.content[3]')}} <span style="white-space: nowrap;"><a href="mailto:ordbok-teknisk@uib.no">ordbok-teknisk@uib.no</a></span></p>
</div>
</v-list-item>
<v-list-item>
<v-list-item-title>
<h2>{{$t('contact.faq.title')}}</h2>
</v-list-item-title>
</v-list-item>
<v-list-group dense prepend-icon="help">
<template v-slot:activator>
<v-list-item-content>{{$t('contact.faq.items[0].title')}}</v-list-item-content>
</template>
<v-list-item><p><em>{{$t('dicts.bm')}}</em>{{$t('and')}}<em>{{$t('dicts.nn')}}</em>{{$t('contact.faq.items[0].text[0]')}}<router-link to="/om" @click.native="$emit('close')">{{$t('contact.faq.items[0].text[1]')}}</router-link></p></v-list-item>
</v-list-group>
<v-list-group prepend-icon="help">
<template v-slot:activator>
<v-list-item-content>{{$t('contact.faq.items[1].title')}}</v-list-item-content>
</template>
<v-list-item>{{$t('contact.faq.items[1].text')}}</v-list-item>
</v-list-group>
<v-list-group prepend-icon="help">
<template v-slot:activator>
<v-list-item-content>{{$t('contact.faq.items[2].title')}}</v-list-item-content>
</template>
<v-list-item>{{$t('contact.faq.items[2].text')}}</v-list-item>
</v-list-group>
</v-list>
</template>
<script>
export default {
name: "Contact",
}
</script>
......@@ -2,12 +2,19 @@
<li :is="tag" :class="body.type_"><!--
--><span :is="item.tag || 'span'" v-for="(item, index) in assemble_text"
:class="item.type"
@error="article_error"
:key="index"
v-bind="item.props"><!--
-->{{item.html}}<!--
--><router-link v-if="item.type == 'article_ref'" :to="item.ref" @click.native="article_link_click(item)"><!--
-->{{item.link_text}}{{item.definition_order ? ` (${item.definition_order})` : ''}}<!--
--></router-link><!--
--><router-link class="article_ref" v-if="item.type == 'article_ref'" :to="item.ref" @click.native="article_link_click(item)" :key="index"><!--
--><DefElement tag='span' v-if="item.link_text.type_" :dictionary="dictionary" :key="item.id+'_sub'" :body='item.link_text'/><span v-else>{{item.link_text}}</span><!--
--><span class="homograph" v-if="item.lemmas[0].hgno" :aria-label="`${dictionary=='bm'? 'Betydning': 'Tyding'} ${item.lemmas[0].hgno}`" :title="`${dictionary=='bm'? 'Betydning': 'Tyding'} ${item.lemmas[0].hgno}`" :key="index"><!--
--> ({{roman_hgno(item.lemmas[0])}}{{item.definition_order ? '': ')'}}</span>
<span class="def_order" v-if="item.definition_order" :aria-label="'definisjon '+item.definition_order">{{item.lemmas[0].hgno ? ', ': ' ('}}{{item.definition_order}})</span>
</router-link>
<!--
--><span class="numerator" v-if="item.type == 'fraction'">{{item.num}}</span><!--
-->{{item.type == 'fraction' ? '' : ''}}<!--
--><span class="denominator" v-if="item.type == 'fraction'">{{item.denom}}</span><!--
......@@ -43,34 +50,58 @@ export default {
}
},
computed: {
fulltext_highlight: function() {
return this.$store.state.fulltextHighlight
},
unparsed: function(){
let lang = this.dictionary
let path = this.path
return this.body.items.map(
function(item){
if (item.type_ == 'usage') return {type: item.type_, html: item.text, tag: 'mark'}
else if (item.type_ == 'article_ref') return {
type: item.type_,
html: '',
link_text: item.word_form || item.lemmas[0].lemma,
ref: '/' + lang + '/' + item.article_id + '/' + encodeURIComponent(item.word_form || item.lemmas[0].lemma) + (item.definition_id ? '#def' + item.definition_id : ''),
article_id: item.article_id,
definition_id: item.definition_id,
source: path
}
else if (item.type_ == 'pronunciation') return {type: item.type_, html: item.string}
else if (item.type_ == 'pronunciation_guide') return {type: item.type_, body: item, html: '', tag: 'DefElement', props: {body: item, tag: 'i', dictionary: lang}}
else if (item.type_ == 'superscript') return {type: item.type_, html: item.text, tag: 'sup'}
else if (item.type_ == 'subscript') return {type: item.type_, html: item.text, 'tag': 'sub'}
else if (item.type_ == 'quote_inset') return {type: item.type_, body: item, html: '', tag: 'DefElement', props: {body: item, tag: 'i', dictionary: lang}}
else if (item.type_ == 'fraction') return helpers.fraction(item.numerator, item.denominator)
else if (item.id) return {type: item.type_, html: (entities[lang][item.id] || {})['expansion'] || item.id}
else return {type: item.type_ || 'plain', html: item}
try {
let lang = this.dictionary
let path = this.path
return this.body.items.map(
function(item){
if (item.type_ == 'usage') {
if (item.items) {
item.content = item.text
return {type: item.type_, html: '', tag: 'DefElement', props: {body: item, tag: 'i', dictionary: lang}}
}
else {
return {type: item.type_, html: item.text, tag: 'i'}
}
}
else if (item.type_ == 'article_ref') {
return {
type: item.type_,
html: '',
lemmas: item.lemmas,
link_text: item.word_form || item.lemmas[0].annotated_lemma || item.lemmas[0].lemma,
ref: '/' + lang + '/' + item.article_id + (item.definition_id ? '#def' + item.definition_id : ''),
article_id: item.article_id,
definition_id: item.definition_id,
definition_order: item.definition_order,
source: path
}
}
else if (item.type_ == 'pronunciation') return {type: item.type_, html: item.string}
else if (item.type_ == 'pronunciation_guide') return {type: item.type_, body: item, html: '', tag: 'DefElement', props: {body: item, tag: 'i', dictionary: lang}}
else if (item.type_ == 'superscript') return {type: item.type_, html: item.text, tag: 'sup'}
else if (item.type_ == 'subscript') return {type: item.type_, html: item.text, tag: 'sub'}
else if (item.type_ == 'quote_inset') return {type: item.type_, body: item, html: '', tag: 'DefElement', props: {body: item, tag: 'i', dictionary: lang}}
else if (item.type_ == 'fraction') return helpers.fraction(item.numerator, item.denominator)
else if (item.id) return {type: item.type_, html: (entities[lang][item.id] || {})['expansion'] || item.id}
else return {type: item.type_ || 'plain', html: item}
}
)
}
catch(error) {
this.$emit('error', {location: "unparsed", message: error.message} )
return {type: 'plain', html: item}
}
},
assemble_text: function(){
var old_parts = this.body.content.split(/(\$)/)
try {
var old_parts = this.body.content.split(/(\$)/)
var text_items = this.unparsed.slice(0).reverse()
var new_parts = []
old_parts.forEach(function(item){
......@@ -81,12 +112,23 @@ export default {
}
})
return new_parts
}
catch(error) {
this.$emit('error', {location: "assemble_text", message: error.message} )
return []
}
}
},
methods: {
article_link_click: function(item) {
this.$emit('article-click', item)
}
},
article_error: function(payload) {
this.$emit('error', payload)
},
roman_hgno: helpers.roman_hgno
}
}
</script>
......@@ -123,8 +165,16 @@ i {
font-style: normal;
}
.homograph {
vertical-align: sub;
.link_text {
text-decoration: underline;
}
.homograph, .def_order{
text-decoration: none !important;
color: black
}
q:before {
......
<template>
<li :class="['definition', 'level'+level]" :ref="'def' + body.id" :id="'def' + body.id">
<ul class="explanations" tabindex="0">
<li :class="['definition', 'level'+level]" :ref="level != 9 ? 'def' + body.id : ''" :id="level != 9? 'def' + body.id : ''">
<span v-if="level!=9"/>
<ul class="explanations">
<DefElement :body="explanation" :dictionary="dictionary" :has_article_ref=has_article_ref(explanation) v-for="(explanation, index) in explanations" :key="index" @article-click="article_link_click" />
</ul>
<div v-if="examples.length" tabindex="0">
<h4>{{example_header}}</h4>
<div v-if="examples.length">
<h5 :lang="lang_tag_locale" v-if="level < 3">{{$t('article.headings.examples', content_locale)}}</h5>
<ul class="examples">
<Example :body="example" :dictionary="dictionary" v-for="(example, index) in examples" :key="index" @article-click="article_link_click" />
</ul>
......@@ -12,8 +13,8 @@
<ul class="compound_lists">
<CompoundList :body="compound_list" :dictionary="dictionary" v-for="(compound_list, index) in compund_lists" :key="index" @article-click="article_link_click" />
</ul>
<div :is="level < 3 ? 'ol' : 'ul'" class="sub_definitions" v-if="subdefs.length" tabindex="0">
<Definition :level="level+1" :body="subdef" v-for="(subdef, index) in subdefs" :dictionary="dictionary" :key="index" @article-click="article_link_click" />
<div :is="level < 3 ? 'ol' : 'ul'" class="sub_definitions" v-if="subdefs.length">
<Definition :def_number='index+1' :level="level+1" :body="subdef" v-for="(subdef, index) in subdefs" :dictionary="dictionary" :key="index" @article-click="article_link_click" />
</div>
</li>
</template>
......@@ -28,7 +29,9 @@ var Definition = {
props: {
body: Object,
level: Number,
dictionary: String
dictionary: String,
def_number: Number
},
components: {
DefElement,
......@@ -38,26 +41,54 @@ var Definition = {
},
computed: {
explanations: function() {
try {
return this.body.elements.filter(el => el.type_ == 'explanation')
} catch (error) {
this.$emit('error', {location: "explanations", message: error.message})
return []
}
},
examples: function() {
try {
return this.body.elements.filter(el => el.type_ == 'example')
} catch (error) {
this.$emit('error', {location: "examples", message: error.message})
return []
}
},
compund_lists: function() {
try {
return this.body.elements.filter(el => el.type_ == 'compound_list')
},
example_header: function() {
return this.dictionary == 'bob' ? 'Eksempel' : 'Døme'
} catch (error) {
this.$emit('error', {location: "compound_lists", message: error.message})
return []
}
},
subdefs: function() {
try {
return this.body.elements.filter(el => el.type_ == 'definition').filter(def => def.elements.filter(el => el.type_ != 'sub_article').length > 0)
} catch (error) {
this.$emit('error', {location: "subdefs", message: error.message})
return []
}
// filtrerer bort definisjoner som bare inneholder underartikler
return this.body.elements.filter(el => el.type_ == 'definition').filter(def => def.elements.filter(el => el.type_ != 'sub_article').length > 0)
},
content_locale: function() {
return this.$parent.content_locale
},
lang_tag_locale: function() {
return this.$parent.lang_tag_locale
}
},
},
mounted: function() {
let ref = 'def' + this.body.id
if(location.hash.substring(1) == ref){
this.$refs[ref].scrollIntoView()
this.$refs[ref].scrollIntoView({block: 'center'})
this.$refs[ref].classList.add('highlighted')
}
},
......@@ -78,10 +109,12 @@ var Definition = {
watch:{
$route(to, from) {
let ref = 'def' + this.body.id
if(location.hash.substring(1) == ref){
this.$refs[ref].classList.add('highlighted')
}else{
this.$refs[ref].classList.remove('highlighted')
if (this.$refs[ref]) {
if(location.hash.substring(1) == ref){
this.$refs[ref].classList.add('highlighted')
}else {
this.$refs[ref].classList.remove('highlighted')
}
}
}
}
......@@ -94,18 +127,15 @@ q {
font-style: italic;
}
.highlighted {
.highlighted, mark {
background-color: var(--v-tertiary-darken1);
border-radius: 5px;
}
h4 {
color: var(--v-primary-base);
font-size: 14px;
padding-left: 12px;
padding-top: 6px;
mark {
font-weight: bold;
}
li[has_article_ref="true"] {
margin-top: 8px;
margin-left: -25px;
......
This diff is collapsed.
<template>
<span>
<v-btn v-if="$route.name!='about'" to="/om" small dark text>
<v-icon small left>info</v-icon>{{$t('menu.about')}}
</v-btn>
<v-btn v-if="$route.name=='about'" to="/" small dark text>
<v-icon small left>home</v-icon>{{$t('home')}}
</v-btn>
<v-dialog :fullscreen="$vuetify.breakpoint.smAndDown" max-width="800px" v-model="help_dialog">
<template v-slot:activator="{ on, attrs }">
<v-btn small dark text v-on="on" v-bind="attrs">
<v-icon small left>help</v-icon>{{$t('menu.help')}}
</v-btn>
</template>
<v-card>
<v-toolbar elevation="0" dark color="primary">
<v-toolbar-title>{{$t('menu.help')}}</v-toolbar-title><v-spacer></v-spacer>
<v-toolbar-items><v-btn @click.native="help_dialog=false" text>{{$t('close')}}<v-icon right>close</v-icon></v-btn></v-toolbar-items>
</v-toolbar>
<v-card-text class="text--primary">
<br>
<Help/>
</v-card-text>
</v-card>
</v-dialog>
<v-dialog :fullscreen="$vuetify.breakpoint.smAndDown" max-width="800px" v-model="settings_dialog">
<template v-slot:activator="{ on, attrs }">
<v-btn small dark text v-on="on" v-bind="attrs">
<v-icon small left>settings</v-icon><span>{{$t('settings.title')}}</span>
</v-btn>
</template>
<v-card>
<v-toolbar elevation="0" dark color="primary">
<v-toolbar-title><span role="heading" aria-level="1">{{$t('settings.title')}}</span></v-toolbar-title><v-spacer></v-spacer>
<v-toolbar-items><v-btn @click.native="settings_dialog=false" text>{{$t('close')}}<v-icon right>close</v-icon></v-btn></v-toolbar-items>
</v-toolbar>
<Settings/>
</v-card>
</v-dialog>
<v-dialog :fullscreen="$vuetify.breakpoint.smAndDown" max-width="800px" v-model="contact_dialog">
<template v-slot:activator="{ on, attrs }">
<v-btn small dark text v-on="on" v-bind="attrs">
<v-icon small left>mail</v-icon><span>{{$t('contact.title')}}</span>
</v-btn>
</template>
<v-card>
<v-toolbar elevation="0" dark color="primary">
<v-toolbar-title><span role="heading" aria-level="1">{{$t('contact.title')}}</span></v-toolbar-title><v-spacer></v-spacer>
<v-toolbar-items><v-btn @click.native="contact_dialog=false" text>{{$t('close')}}<v-icon right>close</v-icon></v-btn></v-toolbar-items>
</v-toolbar>
<Contact @close="contact_dialog = false"/>
</v-card>
</v-dialog>
</span>
</div>
</template>
<script>
import Help from './Help.vue'
import Contact from './Contact.vue'
import Settings from './Settings.vue'
export default {
name: "FooterMenu",
components: {
Help,
Contact,
Settings
},
data: function() {
return {
contact_dialog: false,
help_dialog: false,
settings_dialog: false
}
}
}
</script>
\ No newline at end of file
This diff is collapsed.