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 4275 additions and 634 deletions
# Disable search engine indexing # Disable search engine indexing
User-agent: * User-agent: *
Disallow: / Disallow: /
\ No newline at end of file
<template> <template>
<v-app id="app"> <v-app id="app">
<header> <router-link ref="skip_link" to="#main" class="skip-link" @click.native="focus_main">{{$t('accessibility.main_content')}}</router-link>
<div class="about-link"><router-link to="/om">OM ORDBØKENE</router-link></div>
<h1><a :class="$vuetify.breakpoint.name" href="/">Ordbøkene</a></h1> <div>
<div class="beta" :title="release">{{version_label}}</div> <TopBar/>
<p class="sub-title"><router-link to="/">Bokmålsordboka og Nynorskordboka</router-link></p> </div>
</header>
<router-view></router-view> <div v-if="$route.name!='about'" class = "banner" :class="$vuetify.breakpoint">
<footer> <div v-if="show_banner_text">
<div :class="$vuetify.breakpoint.xs?'sm':'lg'"> <a href="/">
<div> <h1 :class="$vuetify.breakpoint.name">Ordbøkene</h1></a>
<img id="srlogo" src="./assets/Sprakradet_logo_neg.png" alt=""> <p class="sub-title">{{$t("sub_title")}}</p>
</div>
<div>
<img id="uiblogo" src="./assets/uib-logo.svg" alt="">
</div> </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>
<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> </footer>
</v-app> </v-app>
</template> </template>
<script> <script>
import TopBar from './components/TopBar.vue'
import FooterMenu from './components/FooterMenu.vue'
export default { 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() { data: function() {
return { return {
version_label: process.env.VUE_APP_VERSION_LABEL, contact_dialog: false,
release: process.env.VUE_APP_RELEASE help_dialog: false,
announcement: localStorage.getItem('app_info') == 'false' ? false : true,
announce_test: localStorage.getItem('beta_info') == 'false' ? false : true
} }
}, },
mounted: function(){ methods: {
document.title = 'Ordbøkene - ' + this.version_label 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> </script>
<style> <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&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'); @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-face {
font-family: NotoParen; font-family: 'Noto Sans';
font-style: italic; font-style: normal;
src: url('./assets/NotoSansParen.woff') format('woff'); font-weight: 700;
unicode-range: U+28-29; 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 { #app {
font-family: NotoParen, 'Noto Sans', Helvetica, Arial, sans-serif; font-family: NotoParen, 'Noto Sans', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
color: #2c3e50; color: var(--v-text-base);
display: flex;
flex-direction: column; background-color: var(--v-tertiary-base)
height: 100%;
} }
html, body { html, body {
height: 100% height: 100%
} }
body { main {
margin: 0px; flex: 1;
} }
a {
text-decoration: none;
}
h1 {
h1, .article h3 {
font-family: Inria Serif; font-family: Inria Serif;
color: var(--v-primary-base);
padding-left: 15px; padding-left: 15px;
color: var(--v-primary-base)
} }
h1 > a.xs, h1 > a.sm { p a, .article a, .search_notification a, .did_you_mean a, .info-card a {
font-size: 36px; 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 { a.article_header {
color: var(--v-primary-base) !important; border: none;
}
.banner h1 {
font-size: 48px; font-size: 48px;
margin: 0px;
} }
.about-link { h1.xs, h1.sm {
text-align: right; font-size: 36px;
margin-right: 15px;
float: right;
} }
header > p { .banner {
padding-left: 18px; padding-top: 10px;
padding-bottom: 15px;
padding-top: 0;
} }
.beta {
position: absolute;
top: 2px; .v-list-item__title {
font-size: 20px; word-break: break-all;
color: #BBBBBB; overflow-wrap: break-word;
padding-left: 15px;
} }
.about-link > a{
.banner p {
padding-bottom: 10px;
padding-top: 0;
padding-left: 18px;
}
.banner a {
text-decoration: none; text-decoration: none;
border-bottom: solid var(--v-primary-base) 2px; color: var(--v-primary-base) !important;
font-size: 12px;
color: var(--v-primary-base);
} }
header { .beta {
position: relative; font-family: Inria Serif;
padding-left: calc((100vw - 1200px) / 2); font-size: 18px;
padding-right: calc((100vw - 1200px) / 2); color: white;
margin-left: 10px;
} }
footer { .language-dialog-title {
position: relative; font-family: Inria Serif;
padding-left: calc((100vw - 1000px) / 2); color: white;
padding-right: calc((100vw - 1000px) / 2);
} }
footer a {
text-decoration: underline; .top-bar .v-toolbar__content {
color: #ffffff !important; padding-left: calc((100vw - 1200px) / 2) !important;
padding-right: calc((100vw - 1200px) / 2) !important;
} }
header { .banner {
padding-top: 20px; position: relative;
padding-left: calc((100vw - 1200px) / 2);
padding-right: calc((100vw - 1200px) / 2);
background-color: var(--v-tertiary-base); background-color: var(--v-tertiary-base);
} }
.banner.xs, .banner.sm {
padding-top: 0px;
}
.banner.xs div, .banner.sm div{
display: none;
}
.sub-title { .sub-title {
font-size: 18px; font-size: 18px;
margin: 0px; margin: 0px;
margin-bottom: 0px !important; margin-bottom: 0px !important;
}
.sub-title {
color: var(--v-primary-base) !important;
}
footer a, .notification a {
color: #ffffff !important;
} }
footer { footer {
padding-left: calc((100vw - 1200px) / 2);
padding-right: calc((100vw - 1200px) / 2);
font-size: smaller; font-size: smaller;
display: table;
flex-direction: row;
background-color: var(--v-primary-base); background-color: var(--v-primary-base);
color: #ffffff; 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: 10px;
padding-left: 24px;
} }
footer > div.lg > div { .accessibility-declaration {
padding: 0; text-align: center;
display: table-cell; padding-top: 10px;
vertical-align: middle;
padding: 10px;
} }
footer > div.sm > div { .footer-row {
vertical-align: middle; display: flex;
text-align: center; flex-direction: rows;
padding: 10px; 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 { #srlogo {
height: 20px; height: 20px;
} }
...@@ -178,4 +319,94 @@ footer > div.sm > div { ...@@ -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> </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> <template>
<article v-if="article" :class="dictionary"> <div v-if="article" :lang="{nob:'nb', nno: 'nn', eng: 'en', 'ukr': 'uk'}[dictionary]" class="article-container">
<Header :lemmas="article.lemmas" :dictionary="dictionary" :article_id="article.article_id" /> <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_content" :class="$vuetify.breakpoint.name"> <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"> <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> <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> </ul>
</section> </section>
<section v-if="article.body.etymology && article.body.etymology.length" class="etymology"> <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> <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> </ul>
</section> </section>
<section class="definitions" v-if="has_content"> <section class="definitions" v-if="has_content">
<h3>{{def_label}}</h3> <h4 :lang="lang_tag_locale">{{$t('article.headings.definitions', content_locale)}}</h4>
<ol> <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> </ol>
</section> </section>
<section v-if="sub_articles.length" class="expressions"> <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> <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> </ul>
</section> </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> </div>
</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> </template>
<script> <script>
...@@ -40,74 +43,285 @@ import DefElement from './DefElement.vue' ...@@ -40,74 +43,285 @@ import DefElement from './DefElement.vue'
import Definition from './Definition.vue' import Definition from './Definition.vue'
import SubArticle from './SubArticle.vue' import SubArticle from './SubArticle.vue'
import Header from './Header.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) { function find_sub_articles(definition) {
let sub_art_list = [] let sub_art_list = []
let sub_definitions = definition.elements.filter(el => el.type_ == 'definition') try {
let sub_definitions = definition.elements.filter(el => el.type_ == 'definition')
sub_definitions.forEach((subdef, i) => { let sub_articles = definition.elements.filter(el => el.type_ == 'sub_article' && el.lemmas)
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)
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 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)) catch(error) {
}) console.log("find_sub_articles", this.article.article_id, this.dictionary, '"'+error.message+'"')
let content_nodes = definition.elements.filter(el => ['explanation', 'example', 'compound_list'].includes(el.type_))
content_list = content_list.concat(content_nodes)
return content_list return []
}
} }
export default { export default {
name: 'Article', name: 'Article',
props: { props: {
article: Object article: Object,
articleLookup: Boolean,
title_id: String,
queryPattern: String,
scope: String,
},
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: { computed: {
link_to_self: function() { hide_label: function() {
return { //collapsable || (collapsed && $store.state.collapseArticles != 'never')
ref: '/' + this.dictionary + '/' + this.article.article_id + '/' + encodeURIComponent(this.article.lemmas[0].lemma), if (this.$parent.count_bm || this.$parent.count_nn) {
article: this.article 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() { dictionary: function() {
return this.article.dictionary return this.article.dictionary
}, },
def_label: function() { content_locale: function() {
return this.dictionary == 'bob' ? 'Betydning og bruk' : 'Tyding og bruk' if (this.$i18n.locale == 'eng') {
return 'eng'
} else if (this.$i18n.locale == 'ukr') {
return 'ukr'
} else {
return {bm: 'nob', nn: 'nno'}[this.dictionary]
}
}, },
example_label: function() { fulltext_highlight: function() {
return this.dictionary == 'bob' ? 'Eksempel' : 'Døme' 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]
},
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() { 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])) 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() { 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: { components: {
DefElement, DefElement,
Definition, Definition,
SubArticle, SubArticle,
Header 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: { 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) { article_link_click: function(item) {
this.$emit('article-click', item) this.$emit('article-click', item)
}, },
details_click: function(item) { details_click: function(item) {
item.title_id = this.title_id
this.$emit('details-click', item) this.$emit('details-click', item)
} }
} }
...@@ -115,46 +329,58 @@ export default { ...@@ -115,46 +329,58 @@ export default {
</script> </script>
<style> <style>
article { .article {
position: relative; position: relative;
padding: 24px; padding: 24px;
margin: 10px; padding-bottom: 12px;
border-radius: 30px; margin-bottom: 20px;
border: solid 1px var(--v-primary-base); margin-right: 10px;
background-color: var(--v-tertiary-base); margin-left: 10px;
background-color: #ffffff;
} }
#single_article_container article { section.xs .article, section.sm .article {
border-style: none; margin-bottom: 10px !important;
} }
.fade { section.md .article, section.lg .article, section.xl .article {
display: none; padding-top: 10px;
} }
section {
padding-top: 1em; .welcome .article_footer {
display: block;
}
#single_article_container .article {
border: none;
margin-top: 10px;
} }
h3 {
color: #560027; section {
font-variant: small-caps; padding-top: 10px;
padding-bottom: 10px
} }
section.etymology > h3, section.pronunciation > h3 {
section.etymology > h4, section.pronunciation > h4 {
display: inline; display: inline;
font-size: 14px;
font-variant: revert;
} }
section.etymology ul, section.pronunciation ul, section.etymology li, section.pronunciation li { section.etymology ul, section.pronunciation ul, section.etymology li, section.pronunciation li {
display: inline; display: inline;
} }
section.etymology li:not(:first-child):before, section.pronunciation li:not(:first-child):before { section.etymology li:not(:first-child):not(:last-child):before, section.pronunciation li:not(:first-child):not(:last-child):before {
content: ", "; content: ", ";
} }
section.etymology li:not(:first-child):last-child:before, section.pronunciation li:not(:first-child):last-child:before {
content: "; ";
font-size: smaller;
}
li { li {
padding-bottom: 4px; padding-bottom: 4px;
} }
...@@ -202,8 +428,36 @@ ul li.definition { ...@@ -202,8 +428,36 @@ ul li.definition {
list-style: disc; 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> </style>
<template>
<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>
<script>
export default {
name: 'ArticleFooter',
props: {
article: Object
},
computed: {
webShareApiSupported() {
return navigator.share
},
showLinkCopy() {
return (!navigator.share || this.$vuetify.breakpoint.mdAndUp) && navigator.clipboard
},
hasPointer() {
return window.matchMedia('(hover: hover) and (pointer: fine)').matches
},
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
})
},
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>
.v-icon {
color: var(--v-primary-base) !important;
}
.toolbar-button {
margin-right: 8px;
margin-top: 8px;
font-size: 12px;
}
.article_footer {
color: var(--v-primary-base);
padding-top: 24px;
}
#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-autocomplete
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'}"
prepend-inner-icon="search"
:append-icon="null"
return-object
rounded
hide-no-data
no-filter
hide-details
label="Søk..."
solo
full-width
flat
outlined
auto-select-first
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_set ? Array.from(data.item.lang_set).sort().join(', ') : 'fritekstsøk'}})
</template>
</v-autocomplete>
</div>
</template>
<script>
import axios from "axios"
import debounce from "debounce"
export default {
props: {
endpoint: String
},
data: function() {
return {
loading: false,
items: [],
search: null,
select: null,
debounced: debounce(function(q, self) {
self.loading = true
return axios.get(self.endpoint + 'suggest?q=' + encodeURIComponent(q))
.then(
function(response) {
let hits = []
if (q == self.search) {
response.data.forEach((item, i) => {
let match = encodeURIComponent(item.match)
if (! hits[0] || hits[0].word != match) {
hits.splice(0, 0, {q: encodeURIComponent(q), lang_set: new Set(), word: match, articles: []})
}
hits[0].lang_set.add(item.dictionary == 'bob' ? 'bm' : 'nn')
hits[0].articles.push(item)
});
hits.forEach(function (hit) {
if (hit.lang_set) {
hit.label = decodeURIComponent(hit.word)
}
});
hits.reverse()
hits = hits.slice(0, 9)
}
hits.sort( (h1, h2) => {
let val1 = h1.label.length * 10 + (h1.label[0].toLowerCase() === h1.label[0] ? 0 : 1)
let val2 = h2.label.length * 10 + (h2.label[0].toLowerCase() === h2.label[0] ? 0 : 1)
return val1 - val2
})
if (q) {
hits.push({q: encodeURIComponent(q), label: q + ' '})
}
self.items = hits
self.loading = false
})
}, 100)
}
},
watch: {
search (val) {
if (! val) {
this.items = []
} else {
this.run_query(val)
}
},
select(item) {
this.$emit('submit', item)
let self = this
setTimeout(() => self.$refs.autocomplete.$refs.input.select(), 1)
}
},
methods: {
run_query(q) {
this.debounced(q, this)
}
},
}
</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 @@ ...@@ -5,10 +5,10 @@
<li <li
:key="index" :key="index"
v-for="(item, index) in body.elements" v-for="(item, index) in body.elements"
><router-link >{{' '}}<router-link
:to="'/' + dictionary + '/' + item.article_id + (item.definition_id ? '#def'+item.definition_id : '')" :to="'/' + dictionary + '/' + item.article_id + (item.definition_id ? '#def'+item.definition_id : '')"
@click.native="article_link_click(item)" @click.native="article_link_click(item)"
> {{item.lemmas[0].lemma}}</router-link> >{{item.lemmas[0].lemma}}</router-link>
</li> </li>
</ul> </ul>
</li> </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 @@ ...@@ -2,12 +2,19 @@
<li :is="tag" :class="body.type_"><!-- <li :is="tag" :class="body.type_"><!--
--><span :is="item.tag || 'span'" v-for="(item, index) in assemble_text" --><span :is="item.tag || 'span'" v-for="(item, index) in assemble_text"
:class="item.type" :class="item.type"
@error="article_error"
:key="index" :key="index"
v-bind="item.props"><!-- v-bind="item.props"><!--
-->{{item.html}}<!-- -->{{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 class="article_ref" v-if="item.type == 'article_ref'" :to="item.ref" @click.native="article_link_click(item)" :key="index"><!--
--></router-link><!-- --><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><!-- --><span class="numerator" v-if="item.type == 'fraction'">{{item.num}}</span><!--
-->{{item.type == 'fraction' ? '' : ''}}<!-- -->{{item.type == 'fraction' ? '' : ''}}<!--
--><span class="denominator" v-if="item.type == 'fraction'">{{item.denom}}</span><!-- --><span class="denominator" v-if="item.type == 'fraction'">{{item.denom}}</span><!--
...@@ -43,34 +50,58 @@ export default { ...@@ -43,34 +50,58 @@ export default {
} }
}, },
computed: { computed: {
fulltext_highlight: function() {
return this.$store.state.fulltextHighlight
},
unparsed: function(){ unparsed: function(){
let lang = this.dictionary try {
let path = this.path let lang = this.dictionary
return this.body.items.map( let path = this.path
function(item){ return this.body.items.map(
if (item.type_ == 'usage') return {type: item.type_, html: item.text, tag: 'mark'} function(item){
else if (item.type_ == 'article_ref') return { if (item.type_ == 'usage') {
type: item.type_, if (item.items) {
html: '', item.content = item.text
link_text: item.word_form || item.lemmas[0].lemma, return {type: item.type_, html: '', tag: 'DefElement', props: {body: item, tag: 'i', dictionary: lang}}
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, else {
definition_id: item.definition_id, return {type: item.type_, html: item.text, tag: 'i'}
source: path }
} }
else if (item.type_ == 'pronunciation') return {type: item.type_, html: item.string} else if (item.type_ == 'article_ref') {
else if (item.type_ == 'pronunciation_guide') return {type: item.type_, body: item, html: '', tag: 'DefElement', props: {body: item, tag: 'i', dictionary: lang}} return {
else if (item.type_ == 'superscript') return {type: item.type_, html: item.text, tag: 'sup'} type: item.type_,
else if (item.type_ == 'subscript') return {type: item.type_, html: item.text, 'tag': 'sub'} html: '',
else if (item.type_ == 'quote_inset') return {type: item.type_, body: item, html: '', tag: 'DefElement', props: {body: item, tag: 'i', dictionary: lang}} lemmas: item.lemmas,
else if (item.type_ == 'fraction') return helpers.fraction(item.numerator, item.denominator) link_text: item.word_form || item.lemmas[0].annotated_lemma || item.lemmas[0].lemma,
else if (item.id) return {type: item.type_, html: (entities[lang][item.id] || {})['expansion'] || item.id} ref: '/' + lang + '/' + item.article_id + (item.definition_id ? '#def' + item.definition_id : ''),
else return {type: item.type_ || 'plain', html: item} 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(){ 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 text_items = this.unparsed.slice(0).reverse()
var new_parts = [] var new_parts = []
old_parts.forEach(function(item){ old_parts.forEach(function(item){
...@@ -81,22 +112,37 @@ export default { ...@@ -81,22 +112,37 @@ export default {
} }
}) })
return new_parts return new_parts
}
catch(error) {
this.$emit('error', {location: "assemble_text", message: error.message} )
return []
}
} }
}, },
methods: { methods: {
article_link_click: function(item) { article_link_click: function(item) {
this.$emit('article-click', item) this.$emit('article-click', item)
} },
article_error: function(payload) {
this.$emit('error', payload)
},
roman_hgno: helpers.roman_hgno
} }
} }
</script> </script>
<!-- Add "scoped" attribute to limit CSS to this component only --> <!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped> <style scoped>
.usage, .pronunciation_guide { .usage {
font-style: italic; font-style: italic;
} }
.pronunciation_guide {
font-size: smaller;
}
.numerator{ .numerator{
vertical-align: super; vertical-align: super;
padding-right: 2px; padding-right: 2px;
...@@ -119,8 +165,16 @@ i { ...@@ -119,8 +165,16 @@ i {
font-style: normal; font-style: normal;
} }
.homograph {
vertical-align: sub; .link_text {
text-decoration: underline;
}
.homograph, .def_order{
text-decoration: none !important;
color: black
} }
q:before { q:before {
......
<template> <template>
<li :class="['definition', 'level'+level]" :ref="'def' + body.id" :id="'def' + body.id"> <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"> <ul class="explanations">
<DefElement :body="explanation" :dictionary="dictionary" v-for="(explanation, index) in explanations" :key="index" @article-click="article_link_click" /> <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> </ul>
<div v-if="examples.length"> <div v-if="examples.length">
<h4>{{example_header}}</h4> <h5 :lang="lang_tag_locale" v-if="level < 3">{{$t('article.headings.examples', content_locale)}}</h5>
<ul class="examples"> <ul class="examples">
<Example :body="example" :dictionary="dictionary" v-for="(example, index) in examples" :key="index" @article-click="article_link_click" /> <Example :body="example" :dictionary="dictionary" v-for="(example, index) in examples" :key="index" @article-click="article_link_click" />
</ul> </ul>
...@@ -13,7 +14,7 @@ ...@@ -13,7 +14,7 @@
<CompoundList :body="compound_list" :dictionary="dictionary" v-for="(compound_list, index) in compund_lists" :key="index" @article-click="article_link_click" /> <CompoundList :body="compound_list" :dictionary="dictionary" v-for="(compound_list, index) in compund_lists" :key="index" @article-click="article_link_click" />
</ul> </ul>
<div :is="level < 3 ? 'ol' : 'ul'" class="sub_definitions" v-if="subdefs.length"> <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" :dictionary="dictionary" :key="index" @article-click="article_link_click" /> <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> </div>
</li> </li>
</template> </template>
...@@ -28,7 +29,9 @@ var Definition = { ...@@ -28,7 +29,9 @@ var Definition = {
props: { props: {
body: Object, body: Object,
level: Number, level: Number,
dictionary: String dictionary: String,
def_number: Number
}, },
components: { components: {
DefElement, DefElement,
...@@ -38,41 +41,80 @@ var Definition = { ...@@ -38,41 +41,80 @@ var Definition = {
}, },
computed: { computed: {
explanations: function() { explanations: function() {
try {
return this.body.elements.filter(el => el.type_ == 'explanation') return this.body.elements.filter(el => el.type_ == 'explanation')
} catch (error) {
this.$emit('error', {location: "explanations", message: error.message})
return []
}
}, },
examples: function() { examples: function() {
try {
return this.body.elements.filter(el => el.type_ == 'example') return this.body.elements.filter(el => el.type_ == 'example')
} catch (error) {
this.$emit('error', {location: "examples", message: error.message})
return []
}
}, },
compund_lists: function() { compund_lists: function() {
try {
return this.body.elements.filter(el => el.type_ == 'compound_list') return this.body.elements.filter(el => el.type_ == 'compound_list')
}, } catch (error) {
example_header: function() { this.$emit('error', {location: "compound_lists", message: error.message})
return this.dictionary == 'bob' ? 'Eksempel' : 'Døme' return []
}
}, },
subdefs: function() { 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 // 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() { mounted: function() {
let ref = 'def' + this.body.id let ref = 'def' + this.body.id
if(location.hash.substring(1) == ref){ if(location.hash.substring(1) == ref){
this.$refs[ref].scrollIntoView() this.$refs[ref].scrollIntoView({block: 'center'})
this.$refs[ref].classList.add('highlighted') this.$refs[ref].classList.add('highlighted')
} }
}, },
methods: { methods: {
article_link_click: function(item) { article_link_click: function(item) {
this.$emit('article-click', item) this.$emit('article-click', item)
} },
has_article_ref: function(item){
if(item.items.length && item.items[0].type_ == "article_ref" && item.items[0].definition_id === undefined)
{
return "true";
}
else{
return "false";
}
}
}, },
watch:{ watch:{
$route(to, from) { $route(to, from) {
let ref = 'def' + this.body.id let ref = 'def' + this.body.id
if(location.hash.substring(1) == ref){ if (this.$refs[ref]) {
this.$refs[ref].classList.add('highlighted') if(location.hash.substring(1) == ref){
}else{ this.$refs[ref].classList.add('highlighted')
this.$refs[ref].classList.remove('highlighted') }else {
this.$refs[ref].classList.remove('highlighted')
}
} }
} }
} }
...@@ -85,15 +127,18 @@ q { ...@@ -85,15 +127,18 @@ q {
font-style: italic; font-style: italic;
} }
.highlighted { .highlighted, mark {
background-color: var(--v-tertiary-darken1); background-color: var(--v-tertiary-darken1);
border-radius: 5px;
} }
h4 { mark {
color: var(--v-primary-base); font-weight: bold;
font-size: 14px;
padding-left: 12px;
padding-top: 6px;
} }
li[has_article_ref="true"] {
margin-top: 8px;
margin-left: -25px;
}
</style> </style>
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