add new node components

This commit is contained in:
axolotle
2021-06-10 13:21:26 +02:00
parent 9c67a871e2
commit 659a16c29c
7 changed files with 619 additions and 0 deletions
+162
View File
@@ -0,0 +1,162 @@
<template>
<article
class="node-view"
:class="['node-view-' + mode, 'node-view-' + type, 'node-view-' + nodeVariant]"
>
<div v-if="!loading" class="node-view-wrapper">
<component
:is="'node-view-header-' + type"
v-bind="{ node, mode }"
class="node-view-header"
/>
<node-view-body v-bind="{ content: node.content, type: nodeType, mode }" />
<node-view-footer
v-bind="{ node, mode, type: nodeType }"
class="node-view-footer"
/>
</div>
<b-overlay
:show="loading"
:spinner-variant="nodeVariant === 'depart' ? 'dark' : 'light'"
no-wrap
/>
<slot name="bottom" />
</article>
</template>
<script>
import {
NodeViewHeaderRef,
NodeViewHeaderProd,
NodeViewBody,
NodeViewFooter
} from '@/components/nodes'
export default {
name: 'NodeView',
components: {
NodeViewHeaderRef,
NodeViewHeaderProd,
NodeViewFooter,
NodeViewBody
},
props: {
node: { type: Object, default: undefined },
variant: { type: String, default: 'dark' },
type: { type: String, default: 'ref' },
mode: { type: String, default: 'view' }
},
computed: {
loading () {
const dataLevel = this.mode === 'view' ? 2 : 1
return this.node === undefined || this.node.dataLevel < dataLevel
},
nodeVariant () {
return this.node !== undefined ? this.node.variant : this.variant
},
nodeType () {
return this.node !== undefined ? this.node.type : this.type
}
}
}
</script>
<style lang="scss" scoped>
.node-view {
position: relative;
&-wrapper {
display: flex;
flex-direction: column;
}
// ╭─╴╭─┐┌─╮┌─╮
// │ ├─┤├┬╯│ │
// ╰─╴╵ ╵╵ ╰└─╯
&-card {
width: 560px;
height: 330px;
max-width: 560px;
max-height: 330px;
box-shadow: .5rem .5rem 1rem rgba($black, .25);
}
&-card &-header {
padding: $node-card-spacer-sm-y $node-card-spacer-sm-x 0;
@include media-breakpoint-up(md) {
padding: $node-card-spacer-y $node-card-spacer-x 0;
}
}
&-card &-footer {
padding: $node-card-spacer-y $node-card-spacer-x $node-card-spacer-y * 2;
@include media-breakpoint-up(md) {
padding: $node-card-spacer-sm-y $node-card-spacer-sm-x $node-card-spacer-sm-y * 2;
}
}
// ╷ ╷╶┬╴┌─╴╷╷╷
// │╭╯ │ ├─╴│││
// ╰╯ ╶┴╴╰─╴╰╯╯
&-view {
width: 100%;
@include media-breakpoint-up(md) {
height: 100%;
overflow-y: auto;
.node-view-wrapper {
min-height: 100%;
}
}
}
&-view &-header {
padding: $node-view-spacer-sm-y $node-view-spacer-sm-x 0;
@include media-breakpoint-up(md) {
padding: $node-view-spacer-y $node-view-spacer-x 0;
position: sticky;
z-index: 1;
top: 0;
}
}
&-view &-footer {
padding: $node-view-spacer-sm-y $node-view-spacer-sm-x;
@include media-breakpoint-up(md) {
padding: $node-view-spacer-y $node-view-spacer-x;
position: sticky;
bottom: 0;
}
}
@each $color, $value in $theme-colors {
&-#{$color} {
background-color: lighten($value, 3.25%);
.node-view-header,
.node-view-footer {
background-color: lighten($value, 3.25%);
}
}
}
}
</style>
+74
View File
@@ -0,0 +1,74 @@
<template>
<div
class="node-view-body"
:class="['node-view-body-' + mode, 'node-view-body-' + type]"
>
<slot name="default">
<div class="node-view-body-wrapper" v-html="content" />
</slot>
</div>
</template>
<script>
export default {
name: 'NodeViewBody',
props: {
content: { type: String, default: null },
type: { type: String, required: true },
mode: { type: String, required: true }
}
}
</script>
<style lang="scss" scoped>
.node-view-body {
width: 100%;
font: {
family: $font-family-text;
line-height: inherit;
}
@include media-breakpoint-up(sm) {
font-size: 2rem;
}
.node-view-view & {
padding: 0 $node-view-spacer-sm-x;
font-size: 1.25rem;
@include media-breakpoint-up(sm) {
&-ref {
font-size: 2.6rem;
}
&-prod {
font-size: 1.5rem !important;
}
}
@include media-breakpoint-up(md) {
padding: 0 $node-view-spacer-x;
}
}
.node-view-card & {
padding: 0 $node-card-spacer-sm-x;
font-size: 1.1rem;
@include media-breakpoint-up(sm) {
font-size: 2rem;
}
@include media-breakpoint-up(md) {
padding: 0 $node-card-spacer-x;
}
}
&-card &-wrapper {
@include line-clamp(3, 1.5em);
}
}
</style>
@@ -0,0 +1,75 @@
<template>
<nav class="node-view-child-list nav-list" :class="{ 'smartphone': smartphone }">
<ul>
<li
v-for="child in children" :key="child.id"
class="node-view-child-list-item"
>
<dot-button
@click="onOpen(child.id)"
:variant="child.variant"
:active="activeNodes.includes(child.id)"
>
{{ $t('variants.' + child.variant) }}
</dot-button>
</li>
</ul>
</nav>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'NodeViewChildList',
props: {
children: { type: Array, required: true },
parentId: { type: Number, required: true },
smartphone: { type: Boolean, default: false }
},
data () {
return {
}
},
computed: {
...mapGetters(['activeNodes']),
},
methods: {
onOpen (childId) {
this.$store.dispatch('OPEN_NODE', [this.parentId, childId])
}
},
mounted () {
}
}
</script>
<style lang="scss" scoped>
.node-view-child-list {
&-item {
margin-right: 20px;
}
&.smartphone {
padding: $node-view-spacer-sm-y $node-view-spacer-sm-x;
background-color: $white;
position: sticky;
top: 0;
z-index: 1;
ul {
justify-content: space-between;
}
@include media-breakpoint-up(md) {
display: none;
}
}
}
</style>
+86
View File
@@ -0,0 +1,86 @@
<template>
<div
class="node-view-footer" :class="'node-view-footer-' + mode"
>
<div class="d-flex w-100">
<div class="tags">
<b-badge
v-for="tag in node.tags" :key="tag.id"
variant="dark" pill
>
{{ tag.name }}
</b-badge>
</div>
<div v-if="mode === 'view' && node.siblings">
<b-button :id="'siblings-' + node.id">
{{ $t('siblings') }}
</b-button>
<b-popover
:target="'siblings-' + node.id" triggers="hover" placement="top"
>
<div v-for="sibling in node.siblings" :key="sibling.id">
<h6>
<a @click.prevent="onOpen(sibling.id)" href="#">
<div class="">
{{ toCommaList(sibling.authors) }},
</div>
<div class="">
{{ sibling.title }},
</div>
<div class="">
{{ sibling.edition ? sibling.edition.name : sibling.edition }}
</div>
</a>
</h6>
</div>
</b-popover>
</div>
<div v-if="mode === 'card'">
<b-button @click="onOpen(node.id)" variant="outline-dark">
{{ $t('text.read') }}
</b-button>
</div>
</div>
</div>
</template>
<script>
import { toCommaList } from '@/helpers/common'
export default {
name: 'NodeViewFooter',
props: {
node: { type: Object, required: true },
type: { type: String, required: true },
mode: { type: String, required: true }
},
computed: {
authors () {
const authors = this.node.authors
if (!authors) return 'Pas d\'auteur⋅rices'
return authors.map(({ name }) => name).join(', ')
}
},
methods: {
toCommaList,
onOpen (nodeId) {
this.$store.dispatch('OPEN_NODE', [nodeId])
}
}
}
</script>
<style lang="scss" scoped>
.node-view-footer {
margin-top: auto;
font-family: $font-family-base;
}
</style>
@@ -0,0 +1,92 @@
<template>
<div
class="node-view-header-prod" :class="'node-view-header-' + mode"
>
<div class="d-flex w-100">
<h4 class="mr-auto">
<div class="node-view-header-type">
{{ $t('variants.' + node.variant) }} {{ mode === 'card' ? $t('from') : '' }}
</div>
<div v-if="mode === 'card'" class="node-view-header-parent">
<!-- FIXME display parent title and authors -->
{{ parent }}
</div>
</h4>
<button-close v-if="mode === 'view'" @click="onClose()" />
</div>
</div>
</template>
<script>
import { trim, toCommaList } from '@/helpers/common'
export default {
name: 'NodeViewHeaderProd',
props: {
node: { type: Object, required: true },
mode: { type: String, required: true }
},
computed: {
parent () {
if (!this.node || !this.node.parents) return
return this.node.parents.find(parent => parent.variant === 'depart')
}
},
methods: {
trim,
toCommaList,
onClose () {
this.$store.dispatch('CLOSE_NODE', this.node.id)
}
}
}
</script>
<style lang="scss" scoped>
.node-view-header {
&-prod {
font: {
family: $font-family-base;
font-weight: $font-weight-bold;
size: inherit;
line-height: inherit;
}
@each $color, $value in $theme-colors {
.node-view-#{$color} & {
color: darken($value, 32%);
}
}
}
&-parent {
font-family: $font-family-text;
}
&-card {
h4 {
font-size: 0.8rem;
@include media-breakpoint-up(sm) {
font-size: 1.25rem;
}
}
}
&-view {
h4 {
font-size: 1rem;
@include media-breakpoint-up(sm) {
font-size: 1.4525rem;
}
}
}
}
</style>
+124
View File
@@ -0,0 +1,124 @@
<template>
<div
class="node-view-header-ref" :class="'node-view-header-' + mode"
>
<div class="d-flex w-100">
<h4 class="mr-auto">
<strong class="d-block">
{{ toCommaList(node.authors) }},
</strong>
<span v-if="node.preTitle" v-html="node.preTitle + ','" />
<em v-html="(trim(node.title) || 'pas de titre ital') + ','" />
<div class="edition">
{{ node.edition.map(ed => ed.name).join(' ') }}
</div>
</h4>
<node-view-child-list v-if="mode === 'view'" :children="node.children" :parent-id="node.id" />
<button-close v-if="mode === 'view'" @click="onClose()" />
</div>
</div>
</template>
<script>
import { trim, toCommaList } from '@/helpers/common'
import { NodeViewChildList } from '@/components/nodes'
export default {
name: 'NodeViewHeaderRef',
components: {
NodeViewChildList
},
props: {
node: { type: Object, required: true },
mode: { type: String, required: true }
},
methods: {
trim,
toCommaList,
onClose () {
this.$store.dispatch('CLOSE_NODE', this.node.id)
}
}
}
</script>
<style lang="scss" scoped>
.node-view-header {
&-ref {
font: {
family: $font-family-text;
size: inherit;
line-height: inherit;
}
h4 {
font-weight: $font-weight-base;
font-size: 32px;
.authors {
font-weight: $font-weight-bold;
}
}
}
// ╭─╴╭─┐┌─╮┌─╮
// │ ├─┤├┬╯│ │
// ╰─╴╵ ╵╵ ╰└─╯
&-card {
h4 {
font-size: 1.1rem;
.edition {
font-size: 0.65rem;
}
@include media-breakpoint-up(sm) {
font-size: 2rem;
.edition {
font-size: 1.15rem;
}
}
}
}
// ╷ ╷╶┬╴┌─╴╷╷╷
// │╭╯ │ ├─╴│││
// ╰╯ ╶┴╴╰─╴╰╯╯
&-view {
h4 {
font-size: 1.25rem;
.edition {
font-size: 0.75rem;
}
@include media-breakpoint-up(sm) {
font-size: 2.625rem;
.edition {
font-size: 1.5rem;
}
}
}
.node-view-child-list {
display: none;
@include media-breakpoint-up(md) {
display: block;
}
}
}
}
</style>
+6
View File
@@ -0,0 +1,6 @@
export { default as NodeViewChildList } from './NodeViewChildList'
export { default as NodeViewHeaderRef } from './NodeViewHeaderRef'
export { default as NodeViewHeaderProd } from './NodeViewHeaderProd'
export { default as NodeViewBody } from './NodeViewBody'
export { default as NodeViewFooter } from './NodeViewFooter'
export { default as NodeView } from './NodeView'