Merge branch 'master' into prod
This commit is contained in:
Generated
+1716
-1871
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.21.1",
|
||||
"bootstrap-vue": "^2.21.2",
|
||||
"core-js": "^3.6.5",
|
||||
"d3": "^6.5.0",
|
||||
"vue": "^2.6.11",
|
||||
"vue-meta": "^2.4.0",
|
||||
"vue-router": "^3.2.0",
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title>En Françai(s)</title>
|
||||
<title>En françaiS au pluriel</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
|
||||
+43
-17
@@ -1,18 +1,38 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<header>
|
||||
<h1>
|
||||
<router-link :to="{ name: 'home' }">En Français</router-link>
|
||||
</h1>
|
||||
<b-navbar>
|
||||
<b-dropdown variant="link" no-caret>
|
||||
<template #button-content>
|
||||
<span class="navbar-toggler-icon" />
|
||||
<span class="sr-only">Menu</span>
|
||||
</template>
|
||||
|
||||
<b-dropdown-item v-for="name in subRoutes" :key="name" :to="{ name }">
|
||||
{{ $t('sections.' + name) }}
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
|
||||
<b-navbar-brand :to="{ name: 'home' }">
|
||||
{{ $t('title') }}
|
||||
</b-navbar-brand>
|
||||
|
||||
<b-nav class="ml-auto" pills>
|
||||
<b-nav-item
|
||||
v-for="name in mainRoutes" :key="name"
|
||||
:to="{ name }" :active="$route.name === name"
|
||||
>
|
||||
{{ $t('sections.' + name) }}
|
||||
</b-nav-item>
|
||||
</b-nav>
|
||||
</b-navbar>
|
||||
|
||||
<router-view name="options" />
|
||||
</header>
|
||||
|
||||
<main id="main">
|
||||
<router-view />
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>footer</p>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -20,18 +40,24 @@
|
||||
export default {
|
||||
name: 'App',
|
||||
|
||||
metaInfo: {
|
||||
// if no subcomponents specify a metaInfo.title, this title will be used
|
||||
title: 'Home',
|
||||
// all titles will be injected into this template
|
||||
titleTemplate: '%s | En Français',
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
|
||||
]
|
||||
metaInfo () {
|
||||
return {
|
||||
// if no subcomponents specify a metaInfo.title, try to get one from the route name.
|
||||
title: this.$t('sections.' + this.$route.name, ''),
|
||||
// all titles will be injected into this template
|
||||
titleTemplate: '%s | ' + this.$t('title')
|
||||
}
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
mainRoutes: ['library', 'kit', 'gallery'],
|
||||
subRoutes: ['home', 'introduction', 'blog', 'contact']
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style lang="scss">
|
||||
@import '@/assets/scss/main.scss';
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
fragment TextCardFields on TextInterface {
|
||||
id
|
||||
bundle
|
||||
title
|
||||
content: texte
|
||||
images {
|
||||
id
|
||||
url
|
||||
alt
|
||||
}
|
||||
videos {
|
||||
url
|
||||
}
|
||||
files: fichiers {
|
||||
fid
|
||||
filename
|
||||
filemime
|
||||
url
|
||||
}
|
||||
families: familles {
|
||||
id
|
||||
uuid
|
||||
name
|
||||
}
|
||||
# field_titre
|
||||
authors: auteurs {
|
||||
id
|
||||
uuid
|
||||
name
|
||||
}
|
||||
edition {
|
||||
id
|
||||
uuid
|
||||
name
|
||||
}
|
||||
type: __typename
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
fragment TextTreeFields on TextInterface {
|
||||
id
|
||||
title
|
||||
families: familles {
|
||||
id
|
||||
name
|
||||
}
|
||||
type: __typename
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#import "../fragments/TextTreeFields.gql"
|
||||
|
||||
fragment TextrefTreeFields on Textref {
|
||||
...TextTreeFields
|
||||
children: text_produits {
|
||||
...TextTreeFields
|
||||
parents: text_de_depart {
|
||||
...TextTreeFields
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#import "../fragments/TextCardFields.gql"
|
||||
|
||||
query TextRef ($id: Int!) {
|
||||
text: texte (id: $id) {
|
||||
...TextCardFields
|
||||
|
||||
... on Textref {
|
||||
children: text_produits {
|
||||
id
|
||||
}
|
||||
siblings: text_en_rebond {
|
||||
id
|
||||
title
|
||||
authors: auteurs {
|
||||
name
|
||||
}
|
||||
edition {
|
||||
name
|
||||
}
|
||||
}
|
||||
tags {
|
||||
id
|
||||
name
|
||||
}
|
||||
# degres_detrangement: Int
|
||||
notes {
|
||||
note
|
||||
number: numero
|
||||
}
|
||||
}
|
||||
|
||||
... on Textprod {
|
||||
tags: tagsprod {
|
||||
id
|
||||
name
|
||||
}
|
||||
notes {
|
||||
note
|
||||
number: numero
|
||||
links: liens {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
query TextRef ($id: Int!) {
|
||||
textref (id: $id) {
|
||||
title
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#import "../fragments/TextrefTreeFields.gql"
|
||||
|
||||
query TextdepartRecursive($id: Int!) {
|
||||
textref(id: $id) {
|
||||
...TextrefTreeFields
|
||||
siblings: text_en_rebond {
|
||||
...TextrefTreeFields
|
||||
siblings: text_en_rebond {
|
||||
...TextrefTreeFields
|
||||
siblings: text_en_rebond {
|
||||
...TextrefTreeFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#import "../fragments/TextrefTreeFields.gql"
|
||||
|
||||
query TextdepartRecursive($id: Int!) {
|
||||
textref(id: $id) {
|
||||
INPUT
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
query TextsDepart {
|
||||
textsdepart {
|
||||
id,
|
||||
title
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default as TextsDepart } from './TextsDepart.gql'
|
||||
export { default as TextCard } from './TextCard.gql'
|
||||
export { default as TextdepartRecursive } from './TextdepartRecursive.gql'
|
||||
@@ -0,0 +1,60 @@
|
||||
/*!
|
||||
* Import of all needed bootstrap files except variables, functions and mixins
|
||||
*/
|
||||
|
||||
|
||||
// ┌─╮╭─╮╭─╮╶┬╴╭─╴╶┬╴┌─╮╭─┐┌─╮
|
||||
// │╶┤│ ││ │ │ ╰─╮ │ ├┬╯├─┤├─╯
|
||||
// └─╯╰─╯╰─╯ ╵ ╶─╯ ╵ ╵ ╰╵ ╵╵
|
||||
|
||||
// TODO OPTI: import only used
|
||||
|
||||
@import "~bootstrap/scss/root";
|
||||
@import "~bootstrap/scss/reboot";
|
||||
@import "~bootstrap/scss/type";
|
||||
@import "~bootstrap/scss/images";
|
||||
@import "~bootstrap/scss/code";
|
||||
@import "~bootstrap/scss/grid";
|
||||
@import "~bootstrap/scss/tables";
|
||||
@import "~bootstrap/scss/forms";
|
||||
@import "~bootstrap/scss/buttons";
|
||||
@import "~bootstrap/scss/transitions";
|
||||
@import "~bootstrap/scss/dropdown";
|
||||
@import "~bootstrap/scss/button-group";
|
||||
@import "~bootstrap/scss/input-group";
|
||||
@import "~bootstrap/scss/custom-forms";
|
||||
@import "~bootstrap/scss/nav";
|
||||
@import "~bootstrap/scss/navbar";
|
||||
@import "~bootstrap/scss/card";
|
||||
@import "~bootstrap/scss/breadcrumb";
|
||||
@import "~bootstrap/scss/pagination";
|
||||
@import "~bootstrap/scss/badge";
|
||||
@import "~bootstrap/scss/jumbotron";
|
||||
@import "~bootstrap/scss/alert";
|
||||
@import "~bootstrap/scss/progress";
|
||||
@import "~bootstrap/scss/media";
|
||||
@import "~bootstrap/scss/list-group";
|
||||
@import "~bootstrap/scss/close";
|
||||
@import "~bootstrap/scss/toasts";
|
||||
@import "~bootstrap/scss/modal";
|
||||
@import "~bootstrap/scss/tooltip";
|
||||
@import "~bootstrap/scss/popover";
|
||||
@import "~bootstrap/scss/carousel";
|
||||
@import "~bootstrap/scss/spinners";
|
||||
@import "~bootstrap/scss/utilities";
|
||||
@import "~bootstrap/scss/print";
|
||||
|
||||
|
||||
// ┌─╮╭─╮╭─╮╶┬╴╭─╴╶┬╴┌─╮╭─┐┌─╮ ╷ ╷╷ ╷┌─╴
|
||||
// │╶┤│ ││ │ │ ╰─╮ │ ├┬╯├─┤├─╯╶─╴│╭╯│ │├─╴
|
||||
// └─╯╰─╯╰─╯ ╵ ╶─╯ ╵ ╵ ╰╵ ╵╵ ╰╯ ╰─╯╰─╴
|
||||
|
||||
// TODO OPTI: import only used
|
||||
|
||||
@import "~bootstrap-vue/src/utilities";
|
||||
|
||||
// General styling needed for special form controls
|
||||
@import "~bootstrap-vue/src/custom-controls";
|
||||
|
||||
// Include custom SCSS for components
|
||||
@import "~bootstrap-vue/src/components/index";
|
||||
@@ -0,0 +1,28 @@
|
||||
// Texte de départ (id: 9)
|
||||
$color-starting: white;
|
||||
// Texte critique (id: 22)
|
||||
$color-critical: #ffba7d;
|
||||
// Constellation en écho (id: 63)
|
||||
$color-echo: #e191ff;
|
||||
// Réflexion théorique (id: 6)
|
||||
$color-thinking: #ffeb91;
|
||||
// Lecture rapprochée (id: 7)
|
||||
$color-reading: #a5a5ff;
|
||||
// Expérience sensible (id: 8)
|
||||
$color-experience: #cdae87;
|
||||
// Kit de désapprentissage (id: 23)
|
||||
$color-kit: #ff8873;
|
||||
|
||||
$families: (
|
||||
9: $color-starting,
|
||||
22: $color-critical,
|
||||
63: $color-echo,
|
||||
6: $color-thinking,
|
||||
7: $color-reading,
|
||||
8: $color-experience,
|
||||
23: $color-kit
|
||||
);
|
||||
|
||||
@function setColorFromId($id) {
|
||||
@return map-get($families, $id);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +0,0 @@
|
||||
$bleuroi: rgb(61,82,102);
|
||||
$bleuroilight: lighten(rgb(61,82,102), 30%);
|
||||
$gris: rgb(200,204,204);
|
||||
$grisclair: rgb(230,234,234);
|
||||
$grisfonce: rgb(57,57,64);
|
||||
$or: rgb(179,161,125);
|
||||
$rouge: rgb(204,61,82);
|
||||
@@ -1,93 +0,0 @@
|
||||
@import '../fonts/libertinus/libertinus.css';
|
||||
@import '../fonts/notosans/notosans.css';
|
||||
// @import '../fonts/notosans/notosans-semicondensed.css';
|
||||
// @import '../fonts/notosans/notosans-condensed.css';
|
||||
// @import '../fonts/notosans/notosans-extracondensed.css';
|
||||
|
||||
@mixin fontsans {
|
||||
font-family: "noto_sans";
|
||||
}
|
||||
@mixin fontserif {
|
||||
font-family: 'libertinus_serif';
|
||||
}
|
||||
|
||||
body{
|
||||
@include fontserif;
|
||||
font-weight: 400;
|
||||
|
||||
// font-family: 'noto_sans';
|
||||
// font-weight: 900;
|
||||
// font-style: italic;
|
||||
}
|
||||
|
||||
#header-menu,
|
||||
#footer-tabs,
|
||||
#search,
|
||||
footer[role="tools"] .row>header,
|
||||
footer[role="tools"] .row>nav{
|
||||
@include fontsans;
|
||||
}
|
||||
@mixin title1blue {
|
||||
@include fontserif;
|
||||
font-size: 2.268em;
|
||||
color: $bleuroi;
|
||||
font-weight: 400;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
@mixin title1black {
|
||||
@include fontserif;
|
||||
font-size: 1.512em;
|
||||
color: $grisfonce;
|
||||
font-weight: 400;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
@mixin title2black {
|
||||
@include fontserif;
|
||||
font-size: 1.134em;
|
||||
color: $grisfonce;
|
||||
font-weight: 400;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
@mixin title3black {
|
||||
@include fontserif;
|
||||
font-size: 1em;
|
||||
color: $grisfonce;
|
||||
font-weight: 400;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
@mixin title4black {
|
||||
@include fontserif;
|
||||
font-size: 0.882em;;
|
||||
color: $gris;
|
||||
font-weight: 400;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
// TEI
|
||||
|
||||
@mixin teititlefrontblue {
|
||||
@include fontserif;
|
||||
font-size: 2.6em;
|
||||
color: $bleuroi;
|
||||
font-weight: 400;
|
||||
margin:0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@mixin teititle1blue {
|
||||
@include fontserif;
|
||||
font-size: 1.8em;
|
||||
color: $bleuroi;
|
||||
font-weight: 400;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
@mixin fontcaption {
|
||||
@include fontsans;
|
||||
font-size: 0.643em;
|
||||
line-height: 1.1em;
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
// http://www.thesassway.com/intermediate/simple-grid-mixins
|
||||
|
||||
|
||||
@mixin row() {
|
||||
display:flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-items: stretch;
|
||||
@media only screen and (max-width: $small-bp) {
|
||||
flex-wrap:wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.row{
|
||||
@include row;
|
||||
}
|
||||
.row-rl{
|
||||
@include row;
|
||||
}
|
||||
|
||||
// small
|
||||
.small-row {
|
||||
@media only screen and (max-width: $small-bp) {
|
||||
@include row;
|
||||
}
|
||||
}
|
||||
|
||||
// medium
|
||||
.med-row {
|
||||
@media only screen and (min-width: $small-bp+1) and (max-width: $med-bp) {
|
||||
@include row;
|
||||
}
|
||||
}
|
||||
|
||||
// large
|
||||
.large-row {
|
||||
@media only screen and (min-width: $med-bp+1) {
|
||||
@include row;
|
||||
}
|
||||
}
|
||||
|
||||
%col-reset {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@mixin col($col, $offset: 0, $sum: $default_sum, $gap: $default_gap, $align: top) {
|
||||
@extend %col-reset;
|
||||
padding-left: $gap*$offset;
|
||||
@if $col == $default_sum {
|
||||
// if last col, then no gap
|
||||
padding-right: 0;
|
||||
}@else{
|
||||
padding-right: $gap;
|
||||
}
|
||||
&:last-child{padding-right: 0;}
|
||||
|
||||
// no offset with flex ??
|
||||
// margin-left: percentage(($col/$sum)*$offset);
|
||||
|
||||
// col width
|
||||
flex-basis: percentage($col/$sum);
|
||||
}
|
||||
|
||||
@for $c from 1 through $default_sum {
|
||||
.col-#{$c} {
|
||||
@include col($c);
|
||||
}
|
||||
|
||||
// small
|
||||
.small-col-#{$c} {
|
||||
@media only screen and (max-width: $small-bp) {
|
||||
@include col($c);
|
||||
}
|
||||
}
|
||||
|
||||
// medium
|
||||
.med-col-#{$c} {
|
||||
@media only screen and (min-width: $small-bp+1) and (max-width: $med-bp) {
|
||||
@include col($c);
|
||||
}
|
||||
}
|
||||
|
||||
// large
|
||||
.large-col-#{$c} {
|
||||
@media only screen and (min-width: $med-bp+1) {
|
||||
@include col($c);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@for $c from 1 through $default_sum - 1 {
|
||||
@for $o from 1 through $default_sum - $c {
|
||||
.col-#{$c}-offset-#{$o} {
|
||||
@include col($c, $o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: replace with align-self:flex-start or flex-end
|
||||
// .col.float-right{
|
||||
// float: right;
|
||||
// padding-right: 0;
|
||||
// padding-left: $default_gap;
|
||||
// }
|
||||
@@ -1,92 +0,0 @@
|
||||
// http://www.thesassway.com/intermediate/simple-grid-mixins
|
||||
|
||||
|
||||
@mixin row() {
|
||||
// font-size: 0;
|
||||
// white-space: nowrap;
|
||||
position: relative;
|
||||
// >*{
|
||||
// font-size: 16px;
|
||||
// }
|
||||
&:after{
|
||||
content:"";
|
||||
clear:both;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
%col-reset {
|
||||
width: 100%;
|
||||
// display: inline-block;
|
||||
// white-space:normal;
|
||||
// font-size: 16px;
|
||||
float:left;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@mixin col($col, $offset: 0, $sum: $default_sum, $gap: $default_gap, $align: top) {
|
||||
@extend %col-reset;
|
||||
padding-left: $gap*$offset;
|
||||
@if $col == $default_sum {
|
||||
padding-right: 0;
|
||||
}@else{
|
||||
padding-right: $gap;
|
||||
}
|
||||
&:last-child{padding-right: 0;}
|
||||
|
||||
margin-left: percentage(($col/$sum)*$offset);
|
||||
|
||||
// @media only screen and (min-width: 768px) {
|
||||
width: percentage($col/$sum);
|
||||
// vertical-align: $align;
|
||||
// }
|
||||
}
|
||||
|
||||
.row{
|
||||
@include row;
|
||||
// html:not(.js) &{
|
||||
// overflow-y: auto;
|
||||
// }
|
||||
}
|
||||
|
||||
@for $c from 1 through $default_sum {
|
||||
.col-#{$c} {
|
||||
@include col($c);
|
||||
}
|
||||
|
||||
// small
|
||||
.small-col-#{$c} {
|
||||
@media only screen and (max-width: $small-bp) {
|
||||
@include col($c);
|
||||
}
|
||||
}
|
||||
|
||||
// medium
|
||||
.med-col-#{$c} {
|
||||
@media only screen and (min-width: $small-bp+1) and (max-width: $med-bp) {
|
||||
@include col($c);
|
||||
}
|
||||
}
|
||||
|
||||
// large
|
||||
.large-col-#{$c} {
|
||||
@media only screen and (min-width: $med-bp+1) {
|
||||
@include col($c);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@for $c from 1 through $default_sum - 1 {
|
||||
@for $o from 1 through $default_sum - $c {
|
||||
.col-#{$c}-offset-#{$o} {
|
||||
@include col($c, $o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.col.float-right{
|
||||
float: right;
|
||||
padding-right: 0;
|
||||
padding-left: $default_gap;
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
$side-padding:3em;
|
||||
|
||||
body, html{
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height:100%;
|
||||
font-family: sans-serif;
|
||||
font-style: normal;
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
body{
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
#root{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100vw;
|
||||
height:100vh;
|
||||
%layout-element{
|
||||
width:100vw;
|
||||
box-sizing:border-box;
|
||||
}
|
||||
header[role="banner"]{
|
||||
z-index:10;
|
||||
flex: 0 0 auto;
|
||||
@extend %layout-element;
|
||||
padding:1em $side-padding 1em $side-padding;
|
||||
@media only screen and (max-width: $small-bp) {
|
||||
padding:1em $side-padding/2 0 $side-padding/2;
|
||||
}
|
||||
}
|
||||
section[role="main-content"]{
|
||||
display: flex;
|
||||
flex:1 1 auto;
|
||||
@extend %layout-element;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
>.wrapper{
|
||||
position: relative;
|
||||
padding:0 $side-padding 0 $side-padding;
|
||||
// height:100%; max-height:100%;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow-y: hidden;
|
||||
overflow-x: hidden;
|
||||
>*{
|
||||
@include fade-transition;
|
||||
}
|
||||
@media only screen and (max-width: $small-bp) {
|
||||
overflow-y: auto;
|
||||
padding:0 $side-padding/2 0 $side-padding/2;
|
||||
}
|
||||
}
|
||||
.main-content-layout{
|
||||
position: relative;
|
||||
// https://stackoverflow.com/a/33644245
|
||||
display: flex;
|
||||
flex: 1;
|
||||
>section{
|
||||
max-height: 100%;
|
||||
}
|
||||
>header,
|
||||
>section>.wrapper,
|
||||
>nav{
|
||||
box-sizing: border-box;
|
||||
max-height: 100%;
|
||||
padding-top:1em;
|
||||
}
|
||||
>section>.wrapper,
|
||||
>nav{
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
}
|
||||
@media only screen and (max-width: $small-bp) {
|
||||
.main-content-layout{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
footer[role="tools"]{
|
||||
flex:0 0 auto;
|
||||
@extend %layout-element;
|
||||
// padding-bottom: 1em;
|
||||
// >*{
|
||||
// padding:0.5em 1em;
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
html, body{
|
||||
font-size:16px;
|
||||
line-height: 0.9;
|
||||
margin:0; padding:0;
|
||||
background: white;
|
||||
}
|
||||
|
||||
a{
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
a, a:focus, a:active { outline: none; }
|
||||
a:focus{ -moz-outline-style: none; }
|
||||
|
||||
ul {
|
||||
padding:0;
|
||||
margin:0;
|
||||
}
|
||||
li{
|
||||
padding:0;
|
||||
margin:0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.visualy-hidden {
|
||||
position: absolute !important;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
overflow: hidden;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
word-wrap: normal;
|
||||
}
|
||||
.visualy-hidden:active,
|
||||
.visualy-hidden:focus {
|
||||
position: static !important;
|
||||
clip: auto;
|
||||
overflow: visible;
|
||||
height: auto;
|
||||
width: auto;
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
|
||||
// _ _ _ _
|
||||
// | |_ _ _ __ _ _ _ __(_) |_(_)___ _ _ ___
|
||||
// | _| '_/ _` | ' \(_-< | _| / _ \ ' \(_-<
|
||||
// \__|_| \__,_|_||_/__/_|\__|_\___/_||_/__/
|
||||
@mixin accordeon-transition($h){
|
||||
&.accordeon-enter,
|
||||
&.accordeon-leave-to{
|
||||
// opacity: 0;
|
||||
height:0.01vh;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
// transform: translateY(100%);
|
||||
// transform: translate3d(0, 100%, 0);
|
||||
}
|
||||
&.accordeon-enter-to,
|
||||
&.accordeon-leave{
|
||||
// opacity:1;
|
||||
height:$h;
|
||||
// transform: translateY(0%);
|
||||
// transform: translate3d(0, 0, 0);
|
||||
}
|
||||
&.accordeon-enter-active{
|
||||
transition: all 300ms ease-in-out;
|
||||
}
|
||||
&.accordeon-leave-active{
|
||||
transition: all 200ms ease-in-out;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@mixin fade-transition() {
|
||||
&.fade-enter,
|
||||
&.fade-leave-to{
|
||||
opacity: 0;
|
||||
}
|
||||
&.fade-enter-to,
|
||||
&.fade-leave{
|
||||
opacity:1;
|
||||
}
|
||||
&.fade-enter-active{
|
||||
transition: all 300ms ease-in-out;
|
||||
}
|
||||
&.fade-leave-active{
|
||||
transition: all 200ms ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-enter-active{
|
||||
transition: all 300ms ease-in-out;
|
||||
}
|
||||
.fade-leave-active{
|
||||
transition: all 200ms ease-in-out;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-active {
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
// .edition-texts-enter-active, .edition-texts-leave-active {
|
||||
// transition: all 1s;
|
||||
// }
|
||||
// .edition-texts-enter, .edition-texts-leave-to /* .list-leave-active below version 2.1.8 */ {
|
||||
// max-height: 0;
|
||||
// // transform: translateY(30px);
|
||||
// }
|
||||
@@ -1,10 +0,0 @@
|
||||
$base_font_size:16px;
|
||||
|
||||
// grid
|
||||
$default_gap: 1em;
|
||||
$default_sum: 12; // total number of columns
|
||||
|
||||
$small-bp:768px;
|
||||
$med-bp:1080px;
|
||||
$ipad-bp: 1536px;
|
||||
$large-bp:1900px;
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
╭─────────────────────────────────────────────────────────────────╮
|
||||
│ │
|
||||
│ /!\ DO NOT IMPORT OR DEFINE ACTUAL RULES INTO THIS FILE /!\ │
|
||||
│ │
|
||||
│ Only things that disappear after scss compilation is allowed. │
|
||||
│ │
|
||||
╰─────────────────────────────────────────────────────────────────╯
|
||||
|
||||
This file is magically imported into every components so that scss variables and
|
||||
mixins can be accessed.
|
||||
But if some rules are defined here, they will be copied into the final build as many
|
||||
times as there are components…
|
||||
|
||||
*/
|
||||
|
||||
|
||||
// Custom variables must be imported first.
|
||||
@import 'bootstrap-overrides/variables';
|
||||
@import 'variables';
|
||||
|
||||
|
||||
// ┌─╮╭─╮╭─╮╶┬╴╭─╴╶┬╴┌─╮╭─┐┌─╮
|
||||
// │╶┤│ ││ │ │ ╰─╮ │ ├┬╯├─┤├─╯
|
||||
// └─╯╰─╯╰─╯ ╵ ╶─╯ ╵ ╵ ╰╵ ╵╵
|
||||
|
||||
@import '~bootstrap/scss/functions';
|
||||
@import '~bootstrap/scss/variables';
|
||||
@import '~bootstrap/scss/mixins';
|
||||
|
||||
|
||||
// ┌─╮╭─╮╭─╮╶┬╴╭─╴╶┬╴┌─╮╭─┐┌─╮ ╷ ╷╷ ╷┌─╴
|
||||
// │╶┤│ ││ │ │ ╰─╮ │ ├┬╯├─┤├─╯╶─╴│╭╯│ │├─╴
|
||||
// └─╯╰─╯╰─╯ ╵ ╶─╯ ╵ ╵ ╰╵ ╵╵ ╰╯ ╰─╯╰─╴
|
||||
|
||||
@import "~bootstrap-vue/src/variables";
|
||||
|
||||
|
||||
// mixins and functions overides must be imported after bootstrap's definitions.
|
||||
@import 'bootstrap-overrides/mixins';
|
||||
@@ -0,0 +1,41 @@
|
||||
@import 'globals';
|
||||
|
||||
@import 'bootstrap';
|
||||
|
||||
|
||||
html, body, #app {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// Layouts
|
||||
|
||||
.view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.split-screen {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
& > * {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.container-fill {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<svg
|
||||
width="100%" height="100%"
|
||||
ref="svg" :id="id"
|
||||
>
|
||||
<template v-if="ready">
|
||||
<path
|
||||
v-for="(item, index) in links"
|
||||
:key="`link_${index}`"
|
||||
:d="lineGenerator([item.source, item.target])"
|
||||
stroke="black"
|
||||
/>
|
||||
|
||||
<template v-for="(node, index) in nodes">
|
||||
<circle
|
||||
:key="'circle' + index"
|
||||
:cx="node.x"
|
||||
:cy="node.y"
|
||||
:class="node.data.class + (node.data.first ? ' first' : '')"
|
||||
:r="node.children ? 7 : 5"
|
||||
tabindex="0"
|
||||
@click="onNodeClick(node.data)"
|
||||
@keyup.enter="onNodeClick(node.data)"
|
||||
>
|
||||
<title>{{ node.data.title }}</title>
|
||||
</circle>
|
||||
|
||||
<text
|
||||
v-if="showId"
|
||||
:key="'text' + index" :x="node.x + 7" :y="node.y-7"
|
||||
>{{ node.data.id }}</text>
|
||||
</template>
|
||||
</template>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { line } from 'd3-shape'
|
||||
import { selectAll } from 'd3-selection'
|
||||
import { drag } from 'd3-drag'
|
||||
import {
|
||||
forceSimulation, forceLink, forceManyBody,
|
||||
forceX, forceY, forceCenter
|
||||
} from 'd3-force'
|
||||
|
||||
|
||||
export default {
|
||||
/*
|
||||
This component uses vue as the svg renderer and the rest is handle with d3 functions
|
||||
(events, data computing, etc.)
|
||||
*/
|
||||
name: 'NodeMap',
|
||||
|
||||
props: {
|
||||
nodes: { type: Array, required: true },
|
||||
links: { type: Array, required: true },
|
||||
id: { type: String, default: 'node-map' },
|
||||
showId: { type: Boolean, default: true }
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
ready: false,
|
||||
width: 100,
|
||||
height: 100,
|
||||
simulation: forceSimulation(),
|
||||
lineGenerator: line().x(node => node.x).y(node => node.y)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateSize () {
|
||||
const { width, height } = this.$el.getBoundingClientRect()
|
||||
Object.assign(this.$data, { width, height })
|
||||
},
|
||||
|
||||
init () {
|
||||
this.updateSize()
|
||||
|
||||
this.nodes.forEach(node => {
|
||||
this.$set(node, 'x', this.width * 0.5)
|
||||
this.$set(node, 'y', this.height * 0.5)
|
||||
})
|
||||
|
||||
this.setupForces()
|
||||
// Wait for DOM update so d3 can select
|
||||
this.$nextTick(this.initListeners)
|
||||
this.ready = true
|
||||
},
|
||||
|
||||
initListeners () {
|
||||
// TODO replace with vue events ?
|
||||
|
||||
selectAll(`#${this.id} circle`)
|
||||
.data(this.nodes)
|
||||
.call(drag()
|
||||
.on('start', this.onNodeDragStart)
|
||||
.on('drag', this.onNodeDrag)
|
||||
.on('end', this.onNodeDragEnd)
|
||||
)
|
||||
},
|
||||
|
||||
setupForces () {
|
||||
this.simulation
|
||||
.nodes(this.nodes)
|
||||
.force('link', forceLink(this.links).id(d => d.id).distance(50))
|
||||
.force('charge', forceManyBody().strength(-150))
|
||||
.force('x', forceX())
|
||||
.force('y', forceY())
|
||||
.force('center', forceCenter(this.width * 0.5, this.height * 0.5))
|
||||
},
|
||||
|
||||
// D3 DRAG EVENT HANDLERS
|
||||
|
||||
onNodeDragStart (e, node) {
|
||||
if (!e.active) {
|
||||
this.simulation.alphaTarget(0.8).restart()
|
||||
}
|
||||
node.fx = node.x
|
||||
node.fy = node.y
|
||||
},
|
||||
|
||||
onNodeDrag (e, node) {
|
||||
node.fx = e.x
|
||||
node.fy = e.y
|
||||
},
|
||||
|
||||
onNodeDragEnd (e, node) {
|
||||
if (!e.active) {
|
||||
this.simulation.alphaTarget(0)
|
||||
}
|
||||
node.fx = null
|
||||
node.fy = null
|
||||
},
|
||||
|
||||
// BASIC HANDLERS
|
||||
|
||||
onNodeClick (nodeData) {
|
||||
const { id, parents } = nodeData
|
||||
if (parents) {
|
||||
this.$emit('open', parents[0].id, id)
|
||||
} else {
|
||||
this.$emit('open', id)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async mounted () {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
path {
|
||||
stroke: grey;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 0.7rem;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@each $id in map-keys($families){
|
||||
.family-#{$id} {
|
||||
fill: setColorFromId($id);
|
||||
@if $id == 9 {
|
||||
stroke: black;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.first {
|
||||
stroke-width: 2px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<b-form-tags
|
||||
v-bind="$attrs" v-on="$listeners"
|
||||
:value="value"
|
||||
no-outer-focus
|
||||
>
|
||||
<template v-slot="{ tags, disabled, addTag, removeTag }">
|
||||
<b-dropdown
|
||||
:text="buttonText" size="sm"
|
||||
variant="outline-secondary" class="d-block rounded-0"
|
||||
>
|
||||
<b-dropdown-item-button
|
||||
v-for="option in availableOptions" :key="option"
|
||||
@click="addTag(option)"
|
||||
>
|
||||
{{ option }}
|
||||
</b-dropdown-item-button>
|
||||
</b-dropdown>
|
||||
|
||||
<ul v-if="tags.length > 0" class="list-inline d-inline-block">
|
||||
<li v-for="tag in tags" :key="tag" class="list-inline-item">
|
||||
<b-form-tag
|
||||
:title="tag" :disabled="disabled" pill
|
||||
@remove="removeTag(tag)"
|
||||
>
|
||||
{{ tag }}
|
||||
</b-form-tag>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</b-form-tags>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MultipleTagsSelect',
|
||||
|
||||
props: {
|
||||
value: { type: Array, required: true },
|
||||
options: { type: Array, required: true },
|
||||
buttonText: { type: String, required: true }
|
||||
},
|
||||
|
||||
computed: {
|
||||
availableOptions () {
|
||||
return this.options.filter(opt => this.value.indexOf(opt) === -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div class="border p-3">
|
||||
<div>
|
||||
<span class="font-weight-bold">{{ component.$options.name }}</span>
|
||||
PROPS: {{ component.$props }}
|
||||
</div>
|
||||
<slot name="default" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ComponentDebug',
|
||||
|
||||
props: {
|
||||
component: { type: Object, required: true }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<text-card-base v-bind="$attrs">
|
||||
<template v-slot:header-extra="{ text }">
|
||||
<b-nav class="ml-auto flex-nowrap" pills>
|
||||
<template v-if="children">
|
||||
<b-nav-item
|
||||
v-for="child in text.children" :key="child.id"
|
||||
@click="$emit('open', text.id, child.id)"
|
||||
:active="children.includes(child.id)"
|
||||
>
|
||||
{{ child.id }}
|
||||
</b-nav-item>
|
||||
</template>
|
||||
|
||||
<b-nav-item>
|
||||
<b-button-close @click="$emit('close', text.id)" />
|
||||
</b-nav-item>
|
||||
</b-nav>
|
||||
</template>
|
||||
|
||||
<template v-slot:footer-extra="{ text, toCommaList }">
|
||||
<b-button
|
||||
v-if="text.siblings"
|
||||
:id="'siblings-' + text.id"
|
||||
>
|
||||
{{ $t('siblings') }}
|
||||
</b-button>
|
||||
<b-popover
|
||||
v-if="text.siblings"
|
||||
:target="'siblings-' + text.id" triggers="click" placement="top"
|
||||
>
|
||||
<div v-for="sibling in text.siblings" :key="sibling.id">
|
||||
<h6>
|
||||
<span>{{ toCommaList(sibling.authors) }},</span>
|
||||
<span>{{ sibling.title }},</span>
|
||||
<span>{{ sibling.edition }}</span>
|
||||
</h6>
|
||||
</div>
|
||||
</b-popover>
|
||||
</template>
|
||||
</text-card-base>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TextCardBase from './TextCardBase'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'TextCard',
|
||||
|
||||
components: {
|
||||
TextCardBase
|
||||
},
|
||||
|
||||
props: {
|
||||
children: { type: Array, default: null }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<b-card no-body tag="article" class="text-card">
|
||||
<b-overlay class="h-100" :show="loading">
|
||||
<div v-if="text" class="container-fill">
|
||||
<b-card-header header-tag="header">
|
||||
<h4>
|
||||
<template v-if="text.type === 'Textref'">
|
||||
<span>{{ toCommaList(text.authors) }},</span>
|
||||
<span>{{ text.title }},</span>
|
||||
<span>{{ text.edition }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ text.title }}
|
||||
</template>
|
||||
</h4>
|
||||
|
||||
<slot name="header-extra" :text="text" />
|
||||
</b-card-header>
|
||||
|
||||
<b-card-body v-html="text.content" :class="ellipsis ? 'ellipsis' : 'overflow-auto'" />
|
||||
|
||||
<b-card-footer>
|
||||
<b-badge
|
||||
v-for="tag in text.tags" :key="tag.id"
|
||||
variant="dark" pill
|
||||
>
|
||||
{{ tag.name }}
|
||||
</b-badge>
|
||||
|
||||
<slot name="footer-extra" v-bind="{ text, toCommaList }" />
|
||||
</b-card-footer>
|
||||
</div>
|
||||
</b-overlay>
|
||||
</b-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TextCardBase',
|
||||
|
||||
props: {
|
||||
id: { type: Number, required: true },
|
||||
textData: { type: Object, default: null },
|
||||
ellipsis: { type: Boolean, default: false }
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
loading: this.textData === null,
|
||||
text: this.textData
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
toCommaList (arr) {
|
||||
// FIXME TEMP some texts doesn't have authors
|
||||
try {
|
||||
return arr.map(({ name }) => name).join(', ')
|
||||
} catch {
|
||||
return arr
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
if (this.text !== null) return
|
||||
this.$store.dispatch('GET_TEXT', { id: this.id }).then((text) => {
|
||||
this.text = text
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
article {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
|
||||
h4 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
max-height: 10rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<text-card-base v-bind="$attrs" ellipsis>
|
||||
</text-card-base>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TextCardBase from './TextCardBase'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'TextMiniCard',
|
||||
|
||||
components: {
|
||||
TextCardBase
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
Vendored
+82
@@ -0,0 +1,82 @@
|
||||
import { hierarchy } from 'd3-hierarchy'
|
||||
|
||||
function flatten (arr, accumulator = []) {
|
||||
return arr.reduce((acc, child) => {
|
||||
acc.push(child)
|
||||
return child.children ? flatten(child.children, acc) : acc
|
||||
}, accumulator)
|
||||
}
|
||||
|
||||
function getLinked (text) {
|
||||
const types = ['siblings', 'children', 'parents']
|
||||
return types.reduce((acc, type) => {
|
||||
// Handle `null` and `undefined`
|
||||
return text[type] ? [...acc, ...text[type]] : acc
|
||||
}, [])
|
||||
}
|
||||
|
||||
export function toSingleManyData (rawData) {
|
||||
rawData.first = true
|
||||
const h = hierarchy(rawData, d => getLinked(d))
|
||||
h.each(node => {
|
||||
if (node.parent && node.children) {
|
||||
const id = node.parent.data.id
|
||||
node.children = node.children.filter(child => child.data.id !== id)
|
||||
}
|
||||
})
|
||||
|
||||
const nodes = h.descendants()
|
||||
const links = h.links()
|
||||
nodes.forEach(node => {
|
||||
Object.assign(node.data, {
|
||||
type: node.data.type.toLowerCase(),
|
||||
class: 'family-' + node.data.families[0].id
|
||||
})
|
||||
})
|
||||
return { nodes, links }
|
||||
}
|
||||
|
||||
|
||||
export function toManyManyData (rawData) {
|
||||
rawData.first = true
|
||||
const h = hierarchy(rawData, d => getLinked(d))
|
||||
h.each(node => {
|
||||
if (node.parent && node.children) {
|
||||
const id = node.parent.data.id
|
||||
// Remove reference of parent in child's children.
|
||||
node.children = node.children.filter(child => child.data.id !== id)
|
||||
}
|
||||
})
|
||||
|
||||
const links = []
|
||||
const nodes = flatten(h.descendants()).reduce((nodes, node) => {
|
||||
const sameNode = nodes.find(n => node.data.id === n.data.id)
|
||||
if (sameNode) {
|
||||
if (!node.children) return nodes
|
||||
node.children.forEach(child => {
|
||||
if (!sameNode.children.find(c => child.data.id === c.data.id)) {
|
||||
sameNode.children.push(child)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (!node.children) node.children = []
|
||||
nodes.push(node)
|
||||
}
|
||||
return nodes
|
||||
}, []).map(({ data, children, depth }) => {
|
||||
if (children) {
|
||||
children.forEach(child => {
|
||||
links.push({ source: data.id, target: child.data.id })
|
||||
})
|
||||
}
|
||||
return {
|
||||
id: data.id,
|
||||
data: {
|
||||
...data,
|
||||
type: data.type.toLowerCase(),
|
||||
class: 'family-' + data.families[0].id
|
||||
}
|
||||
}
|
||||
})
|
||||
return { nodes, links }
|
||||
}
|
||||
+20
-2
@@ -1,14 +1,32 @@
|
||||
import Vue from 'vue'
|
||||
import BootstrapVue from 'bootstrap-vue'
|
||||
import Meta from 'vue-meta'
|
||||
import Vue2TouchEvents from 'vue2-touch-events'
|
||||
import VueMessages from './messages'
|
||||
import App from './App'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
|
||||
import App from './App'
|
||||
|
||||
|
||||
Vue.use(BootstrapVue, {
|
||||
BButton: { pill: true },
|
||||
BCardHeader: { headerBgVariant: 'white' },
|
||||
BCardFooter: { footerBgVariant: 'white' }
|
||||
})
|
||||
Vue.use(Meta)
|
||||
Vue.use(Vue2TouchEvents)
|
||||
Vue.use(VueMessages)
|
||||
|
||||
|
||||
// Register global components
|
||||
const requireComponent = require.context('@/components/globals', true, /\.(js|vue)$/i)
|
||||
// For each matching file name...
|
||||
requireComponent.keys().forEach((fileName) => {
|
||||
// Get the component
|
||||
const component = requireComponent(fileName).default
|
||||
// Globally register the component
|
||||
Vue.component(component.name, component)
|
||||
})
|
||||
|
||||
|
||||
new Vue({
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"title": "En françaiS au pluriel",
|
||||
"sections": {
|
||||
"home": "Accueil",
|
||||
"introduction": "Présentation",
|
||||
"contact": "Contact",
|
||||
"library": "Bibliothèque",
|
||||
"kit": "Kit de désapprentissage",
|
||||
"gallery": "Créations numériques",
|
||||
"blog": "Blog"
|
||||
},
|
||||
"options": {
|
||||
"display": {
|
||||
"title": "Gestion de l'affichage",
|
||||
"choices": {
|
||||
"tree-map": "Arborescent",
|
||||
"card-map": "Aléatoire",
|
||||
"card-list": "Alphabétique"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"title": "Filtres",
|
||||
"choices": {
|
||||
"tags": "Tags",
|
||||
"strangeness": "Degré d'étrangement"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"title": "Rechercher"
|
||||
}
|
||||
},
|
||||
"siblings": "Textes rebonds"
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import messages from './fr.json'
|
||||
|
||||
|
||||
function getMessage (keyedPath, alt) {
|
||||
let message
|
||||
try {
|
||||
message = keyedPath.split('.').reduce((parent, key) => {
|
||||
return parent[key]
|
||||
}, messages)
|
||||
} catch {
|
||||
// Silence errors like accessing a key on `undefined`.
|
||||
}
|
||||
return message === undefined
|
||||
? alt === undefined ? keyedPath : alt
|
||||
: message
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Allows separtion of static text content from its templates.
|
||||
*/
|
||||
export default class VueMessages {
|
||||
static install (Vue) {
|
||||
// Adds `this.$t('path.to.string', 'alt')` to every instances.
|
||||
Vue.prototype.$t = getMessage
|
||||
|
||||
// Adds `v-t="'path.to.string'"` directive to every elements.
|
||||
Vue.directive('t', {
|
||||
bind (el, { value }) {
|
||||
el.textContent = getMessage(value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<component-debug :component="this" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Blog'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<component-debug :component="this" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Contact'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<component-debug :component="this" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Gallery'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
+3
-16
@@ -1,23 +1,10 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
{{ text }}
|
||||
</div>
|
||||
<component-debug :component="this">
|
||||
</component-debug>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Home',
|
||||
|
||||
data () {
|
||||
return {
|
||||
text: null
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
this.$store.dispatch('GET_TEXT', 1).then(({ data }) => {
|
||||
this.text = data.data.textref
|
||||
})
|
||||
}
|
||||
name: 'Home'
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<component-debug :component="this" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Introduction'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<component-debug :component="this" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Kit'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="h-100 position-relative">
|
||||
<!-- BACKGROUND (mode) -->
|
||||
<component :is="mode" @open="openText" />
|
||||
|
||||
<!-- FOREGROUND (texts) -->
|
||||
<section v-for="parent in parents" :key="parent.id" class="split-screen">
|
||||
<text-card
|
||||
:id="parent.id"
|
||||
:children="parent.children"
|
||||
@open="openText" @close="closeText"
|
||||
/>
|
||||
<div>
|
||||
<text-card
|
||||
v-for="id in parent.children" :key="id"
|
||||
:id="id"
|
||||
@close="closeText(parent.id, $event)"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { CardList, CardMap, TreeMap } from './library'
|
||||
import TextCard from '@/components/text/TextCard'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'Library',
|
||||
|
||||
components: {
|
||||
CardList,
|
||||
CardMap,
|
||||
TreeMap,
|
||||
TextCard
|
||||
},
|
||||
|
||||
props: {
|
||||
mode: { type: String, required: true },
|
||||
texts: { type: Array, required: true }
|
||||
},
|
||||
|
||||
computed: {
|
||||
parents () {
|
||||
return this.texts.map(text => {
|
||||
const [id, ...children] = text
|
||||
return { id, children }
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
openText (id, childId) {
|
||||
const parent = this.parents.find(p => p.id === id)
|
||||
if (parent && (childId === undefined || parent.children.includes(childId))) return
|
||||
|
||||
if (!parent) {
|
||||
this.parents.push({ id, children: childId ? [childId] : [] })
|
||||
} else if (childId) {
|
||||
parent.children.push(childId)
|
||||
}
|
||||
this.updateQuery(this.parents)
|
||||
},
|
||||
|
||||
closeText (id, childId) {
|
||||
const parent = this.parents.find(p => p.id === id)
|
||||
if (!childId) {
|
||||
this.updateQuery(this.parents.filter(p => p !== parent))
|
||||
} else {
|
||||
parent.children = parent.children.filter(childId_ => childId_ !== childId)
|
||||
this.updateQuery(this.parents)
|
||||
}
|
||||
},
|
||||
|
||||
updateQuery (parents) {
|
||||
// Update the route's query (will not reload the page) and let vue determine what changed.
|
||||
this.$router.push({
|
||||
query: {
|
||||
mode: this.mode,
|
||||
texts: parents.map(parent => [parent.id, ...parent.children])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<b-overlay :show="loading" rounded="sm" class="h-100">
|
||||
<div class="d-flex flex-column h-100 px-4 py-3">
|
||||
<div id="maps">
|
||||
<div>
|
||||
<h4>Carte avec duplication</h4>
|
||||
<div class="node-map">
|
||||
<node-map
|
||||
v-if="data"
|
||||
v-bind="mapSingle"
|
||||
:show-id="showId"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Carte avec liens multiples</h4>
|
||||
<div class="node-map">
|
||||
<node-map
|
||||
v-if="data"
|
||||
v-bind="mapMany"
|
||||
:show-id="showId"
|
||||
id="map-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<b-form class="mt-4">
|
||||
<b-form-group label="Texte de départ :" label-cols="2" class="mb-2">
|
||||
<b-form-select
|
||||
v-model="textId"
|
||||
@input="query"
|
||||
:options="textsDepartOptions"
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group label="Profondeur :" label-cols="2" class="mb-2">
|
||||
<b-form-spinbutton
|
||||
v-model="depth"
|
||||
@input="query"
|
||||
min="0" max="6"
|
||||
inline
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-checkbox v-model="showId" name="check-button" switch>
|
||||
Afficher les numéros
|
||||
</b-form-checkbox>
|
||||
</b-form>
|
||||
</div>
|
||||
</b-overlay>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import NodeMap from '@/components/NodeMap'
|
||||
import { toSingleManyData, toManyManyData } from '@/helpers/d3Data'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'Home',
|
||||
|
||||
components: {
|
||||
NodeMap
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
textId: undefined,
|
||||
showId: true,
|
||||
depth: 3,
|
||||
data: null,
|
||||
mapSingle: { nodes: null, links: null },
|
||||
mapMany: { nodes: null, links: null }
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['textsDepartOptions'])
|
||||
},
|
||||
|
||||
methods: {
|
||||
query () {
|
||||
const { textId: id, depth } = this
|
||||
this.data = null
|
||||
this.loading = true
|
||||
this.$store.dispatch('GET_TREE_WITH_DEPTH', { id, depth }).then((data) => {
|
||||
this.mapSingle = toSingleManyData(data)
|
||||
this.mapMany = toManyManyData(data)
|
||||
this.data = data
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
this.loading = true
|
||||
this.$store.dispatch('GET_TEXTS_DEPART').then(textsDepart => {
|
||||
this.textId = textsDepart[0].id
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#maps {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
|
||||
& > div {
|
||||
flex-basis: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& + div .node-map {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
|
||||
.node-map {
|
||||
height: 100%;
|
||||
border: 1px solid grey;
|
||||
}
|
||||
|
||||
}
|
||||
.col > * {
|
||||
width: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div class="h-100 position-absolute overflow-auto">
|
||||
<div v-for="(parents, char) in orderedParents" :key="char">
|
||||
<h3>{{ char }}</h3>
|
||||
<b-card-group deck>
|
||||
<text-mini-card
|
||||
v-for="parent in parents" :key="parent.id"
|
||||
:id="parent.id"
|
||||
:text-data="parent"
|
||||
@click.native="$emit('open', parent.id)"
|
||||
/>
|
||||
</b-card-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import TextMiniCard from '@/components/text/TextMiniCard'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'CardList',
|
||||
|
||||
components: {
|
||||
TextMiniCard
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['orderedParents'])
|
||||
},
|
||||
|
||||
created () {
|
||||
this.$store.dispatch('GET_TEXTS_DEPART')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card-deck {
|
||||
.card {
|
||||
flex-basis: auto;
|
||||
@include media-breakpoint-up(md) {
|
||||
flex-basis: 50%;
|
||||
max-width: calc(50% - 30px);
|
||||
}
|
||||
@include media-breakpoint-up(lg) {
|
||||
flex-basis: 33%;
|
||||
max-width: calc(33.3% - 30px);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<component-debug :component="this" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CardMap'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div v-if="show" class="d-flex justify-content-between">
|
||||
<b-form-group
|
||||
:label="$t('options.display.title')"
|
||||
v-slot="{ ariaDescribedby }"
|
||||
>
|
||||
<b-form-radio-group
|
||||
id="mode-select"
|
||||
v-model="currentMode" :options="modes"
|
||||
name="mode-select" :aria-describedby="ariaDescribedby" buttons
|
||||
size="sm"
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group
|
||||
v-if="currentMode !== 'tree-map'"
|
||||
:label="$t('options.filters.title')"
|
||||
>
|
||||
<b-form-group
|
||||
:label="$t('options.filters.choices.tags')"
|
||||
label-for="tags-select"
|
||||
>
|
||||
<multiple-tags-select
|
||||
id="tags-select" :button-text="$t('options.filters.choices.tags')"
|
||||
v-model="selectedTags" :options="tags"
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group
|
||||
:label="$t('options.filters.choices.strangeness')"
|
||||
label-for="strangeness-input"
|
||||
>
|
||||
<b-form-input
|
||||
id="strangeness-input"
|
||||
type="range" min="0" max="5"
|
||||
v-model="strangeness"
|
||||
/>
|
||||
</b-form-group>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group
|
||||
:label="$t('options.search.title')"
|
||||
label-for="search-input"
|
||||
>
|
||||
<b-input-group size="sm" append="search-icon">
|
||||
<b-form-input v-model="search" id="search-input" trim />
|
||||
</b-input-group>
|
||||
</b-form-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MultipleTagsSelect from '@/components/formItems/MultipleTagsSelect'
|
||||
|
||||
export default {
|
||||
name: 'LibraryOptions',
|
||||
|
||||
components: {
|
||||
MultipleTagsSelect
|
||||
},
|
||||
|
||||
props: {
|
||||
show: { type: Boolean, required: true },
|
||||
mode: { type: String, required: true },
|
||||
tags: { type: Array, default: () => ([]) }
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
modes: [
|
||||
{ text: this.$t('options.display.choices.tree-map'), value: 'tree-map' },
|
||||
{ text: this.$t('options.display.choices.card-map'), value: 'card-map' },
|
||||
{ text: this.$t('options.display.choices.card-list'), value: 'card-list' }
|
||||
],
|
||||
currentMode: this.mode,
|
||||
selectedTags: [],
|
||||
strangeness: 0,
|
||||
search: ''
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
currentMode (mode) {
|
||||
this.$router.push({ query: { mode } })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep fieldset > div {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<b-overlay :show="loading" class="h-100 position-absolute">
|
||||
<node-map
|
||||
v-if="!loading"
|
||||
v-bind="mapData"
|
||||
:show-id="true"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
</b-overlay>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NodeMap from '@/components/NodeMap'
|
||||
import { toManyManyData } from '@/helpers/d3Data'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'TreeMap',
|
||||
|
||||
components: {
|
||||
NodeMap
|
||||
},
|
||||
|
||||
props: {
|
||||
id: { type: Number, default: 1 }
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
loading: true,
|
||||
depth: 3,
|
||||
mapData: { nodes: null, links: null }
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
const { id, depth } = this
|
||||
this.$store.dispatch('GET_TREE_WITH_DEPTH', { id, depth }).then((data) => {
|
||||
this.mapData = toManyManyData(data)
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default as CardList } from './CardList'
|
||||
export { default as CardMap } from './CardMap'
|
||||
export { default as TreeMap } from './TreeMap'
|
||||
+2
-15
@@ -1,23 +1,10 @@
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import Home from '../pages/Home.vue'
|
||||
import routes from './routes'
|
||||
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
const routes = [
|
||||
{
|
||||
name: 'home',
|
||||
path: '/',
|
||||
component: Home
|
||||
},
|
||||
{
|
||||
name: 'notfound',
|
||||
path: '/404',
|
||||
alias: '*',
|
||||
component: () => import(/* webpackChunkName: "404" */ '../pages/NotFound.vue')
|
||||
}
|
||||
]
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
base: process.env.BASE_URL,
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import Home from '@/pages/Home'
|
||||
|
||||
|
||||
export default [
|
||||
{
|
||||
name: 'home',
|
||||
path: '/',
|
||||
component: Home
|
||||
},
|
||||
|
||||
{
|
||||
name: 'introduction',
|
||||
path: '/intro',
|
||||
component: () => import(/* webpackChunkName: "intro" */ '@/pages/Introduction')
|
||||
},
|
||||
|
||||
{
|
||||
name: 'contact',
|
||||
path: '/contact',
|
||||
component: () => import(/* webpackChunkName: "contact" */ '@/pages/Contact')
|
||||
},
|
||||
|
||||
{
|
||||
name: 'library',
|
||||
path: '/library',
|
||||
components: {
|
||||
default: () => import(/* webpackChunkName: "library" */ '@/pages/Library'),
|
||||
options: () => import(/* webpackChunkName: "library" */ '@/pages/library/LibraryOptions')
|
||||
},
|
||||
props: {
|
||||
default: ({ query }) => {
|
||||
let { mode = 'tree-map', texts = [] } = query
|
||||
if (typeof texts === 'string') texts = [texts]
|
||||
// In case of a reload or direct link, vue-router doesn't turn the query string into an array.
|
||||
if (texts && texts.length && typeof texts[0] === 'string') {
|
||||
texts = texts.map(text => text.split(',').map(id => parseInt(id)))
|
||||
}
|
||||
return { mode, texts }
|
||||
},
|
||||
options: ({ query }) => ({
|
||||
show: !('texts' in query && query.texts.length),
|
||||
mode: query.mode || 'tree-map'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'kit',
|
||||
path: '/kit',
|
||||
component: () => import(/* webpackChunkName: "kit" */ '@/pages/Kit')
|
||||
},
|
||||
|
||||
{
|
||||
name: 'gallery',
|
||||
path: '/gallery',
|
||||
component: () => import(/* webpackChunkName: "gallery" */ '@/pages/Gallery')
|
||||
},
|
||||
|
||||
{
|
||||
name: 'blog',
|
||||
path: '/blog',
|
||||
component: () => import(/* webpackChunkName: "blog" */ '@/pages/Blog')
|
||||
},
|
||||
|
||||
// TEMP
|
||||
{
|
||||
name: 'map',
|
||||
path: '/map',
|
||||
component: () => import(/* webpackChunkName: "test" */ '@/pages/Map')
|
||||
},
|
||||
|
||||
{
|
||||
name: 'notfound',
|
||||
path: '/404',
|
||||
alias: '*',
|
||||
component: () => import(/* webpackChunkName: "404" */ '@/pages/NotFound.vue')
|
||||
}
|
||||
]
|
||||
+2
-16
@@ -1,27 +1,13 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
import api from '@/api'
|
||||
import { print } from 'graphql/language/printer'
|
||||
import TextRef from '@/api/queries/TextRef.gql'
|
||||
import texts from './modules/texts'
|
||||
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
|
||||
export default new Vuex.Store({
|
||||
modules: {
|
||||
},
|
||||
|
||||
state: {
|
||||
},
|
||||
|
||||
mutations: {
|
||||
},
|
||||
|
||||
actions: {
|
||||
'GET_TEXT' ({ state }, id) {
|
||||
return api.post('', { query: print(TextRef), variables: { id } })
|
||||
}
|
||||
texts
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
// initial state
|
||||
state: {
|
||||
// items: [],
|
||||
},
|
||||
// getters
|
||||
getters: {
|
||||
|
||||
},
|
||||
// mutations
|
||||
mutations: {
|
||||
|
||||
},
|
||||
// actions
|
||||
actions: {
|
||||
|
||||
},
|
||||
// methods
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import api from '@/api'
|
||||
import { print } from 'graphql/language/printer'
|
||||
import {
|
||||
TextsDepart, TextCard, TextdepartRecursive
|
||||
} from '@/api/queries'
|
||||
import TextdepartRecursiveWithDepth from '@/api/queries/TextdepartRecursiveWithDepth.gql'
|
||||
|
||||
export default {
|
||||
state: {
|
||||
textsDepart: undefined
|
||||
},
|
||||
|
||||
mutations: {
|
||||
'SET_TEXTS_DEPART' (state, texts) {
|
||||
state.textsDepart = texts
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
'GET_TEXTS_DEPART' ({ state, commit }) {
|
||||
return api.post('', { query: print(TextsDepart) }).then(({ data }) => {
|
||||
commit('SET_TEXTS_DEPART', data.data.textsdepart)
|
||||
return state.textsDepart
|
||||
})
|
||||
},
|
||||
|
||||
'GET_TEXT' (store, { id }) {
|
||||
return api.post('', { query: print(TextCard), variables: { id } })
|
||||
.then(data => (data.data.data.text))
|
||||
},
|
||||
|
||||
'GET_TREE' (store, id) {
|
||||
return api.post('', { query: print(TextdepartRecursive), variables: { id } })
|
||||
.then(({ data }) => (data.data.textref))
|
||||
},
|
||||
|
||||
'GET_TREE_WITH_DEPTH' (store, { id, depth }) {
|
||||
const baseQuery = print(TextdepartRecursiveWithDepth)
|
||||
function formatQuery (str, depth) {
|
||||
if (depth > 0) {
|
||||
return formatQuery(
|
||||
str.replace('INPUT', '...TextrefTreeFields\nsiblings: text_en_rebond {\nINPUT\n}'),
|
||||
--depth
|
||||
)
|
||||
} else {
|
||||
return str.replace('INPUT', '...TextrefTreeFields')
|
||||
}
|
||||
}
|
||||
return api.post('', { query: formatQuery(baseQuery, depth), variables: { id } })
|
||||
.then(({ data }) => (data.data.textref))
|
||||
}
|
||||
},
|
||||
|
||||
getters: {
|
||||
textsDepartOptions: state => {
|
||||
if (!state.textsDepart) return undefined
|
||||
return state.textsDepart.map(({ id, title }) => ({
|
||||
value: id,
|
||||
text: `(${id}) ${title}`
|
||||
}))
|
||||
},
|
||||
|
||||
orderedParents: state => {
|
||||
// FIXME duplicates references if multiple authors ?
|
||||
if (!state.textsDepart) return undefined
|
||||
return state.textsDepart.sort((a, b) => {
|
||||
if (!b.authors) return -1
|
||||
if (!a.authors) return +1
|
||||
if (a.authors[0].name < b.authors[0].name) return -1
|
||||
if (a.authors[0].name > b.authors[0].name) return 1
|
||||
return 0
|
||||
}).reduce((dict, text) => {
|
||||
const firstChar = text.authors ? text.authors[0].name[0] : '&'
|
||||
if (!(firstChar in dict)) dict[firstChar] = []
|
||||
dict[firstChar].push(text)
|
||||
return dict
|
||||
}, {})
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -8,7 +8,13 @@ module.exports = {
|
||||
.loader('graphql-tag/loader')
|
||||
.end()
|
||||
},
|
||||
|
||||
css: {
|
||||
loaderOptions: {
|
||||
sass: {
|
||||
prependData: '@import "@/assets/scss/globals.scss";'
|
||||
}
|
||||
}
|
||||
},
|
||||
publicPath: '/',
|
||||
devServer: {
|
||||
clientLogLevel: 'warning',
|
||||
|
||||
Reference in New Issue
Block a user