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
  • websites/monnaie-libre-fr
  • midiland/monnaie-libre-fr
  • websites/moneda-libre-org
  • atlasan/g1-monetalibera-it
  • Processus42/monnaie-libre-fr
  • diablade/monnaie-libre-fr
  • Spiranne/monnaie-libre-fr
7 results
Show changes
Showing
with 1607 additions and 279 deletions
<template>
<div class="prose dark:prose-dark">
<slot />
<slot v-if="Array.isArray(data)" name="items" :items="data">
<ul class="list">
<li v-for="(item, i) in data" :key="i">
<nuxt-link :to="item.path.replace(/^\/pages\//, '/')">
<div v-if="title">{{ item.title }}</div>
</nuxt-link>
<span v-if="description">{{ item.description }}</span>
</li>
</ul>
</slot>
<slot v-else-if="data" name="item">
<h2 v-if="title">{{ data.title }}</h2>
<p v-if="description">{{ data.description }}</p>
</slot>
</div>
</template>
<script>
export default {
name: 'List',
props: {
content: {
type: String,
default: null,
},
only: {
type: [String, Array],
default: null,
},
where: {
type: Object,
default: null,
},
sortBy: {
type: String,
default: null,
},
direction: {
type: String,
default: 'asc',
validator(value) {
return ['asc', 'desc'].includes(value)
},
},
skip: {
type: [Number, String],
default: null,
},
search: {
type: String,
default: null,
},
searchField: {
type: String,
default: null,
},
limit: {
type: [Number, String],
default: null,
},
title: {
type: Boolean,
default: true,
},
description: {
type: Boolean,
default: false,
},
readingTime: {
type: Boolean,
default: false,
},
},
data() {
return {
data: null,
}
},
async fetch() {
if (this.content) {
const content = this.$content(this.content)
if (this.only) content.only(this.only)
if (this.where) content.where(this.where)
if (this.sortBy) content.sortBy(this.sortBy, this.direction)
if (this.skip) content.skip(+this.skip)
if (this.search) {
this.searchField
? content.search(this.searchField, this.search)
: content.search(this.search)
}
if (this.limit) content.limit(+this.limit)
this.data = await content.fetch()
}
},
}
</script>
<style lang="postcss" scoped></style>
<template>
<div class="pt-8">
<t-button
class="flex items-center rounded-2xl"
variant="success"
@click="$modal.show('videoIntro')"
>
<fa :icon="['fab', 'youtube']" class="text-4xl mr-4" />
<div class="text-left pr-1">
<div class="text-xl font-bold">Introduction</div>
<div class="-mt-1">3 min en vidéo</div>
</div>
</t-button>
<t-modal name="videoIntro" variant="video">
<div style="padding: 56.25% 0 0 0; position: relative">
<iframe
src="https://player.vimeo.com/video/514975135?h=7f6183d68c&portrait=0&autoplay=1"
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%"
frameborder="0"
allow="autoplay; fullscreen; picture-in-picture"
allowfullscreen
></iframe>
</div>
</t-modal>
</div>
</template>
<script>
export default {}
</script>
<style lang="postcss"></style>
<template>
<div class="hero container flex items-center justify-evenly px-20">
<div class="w-full lg:items-start max-w-2xl">
<nuxt-content :document="hero" />
<div class="hero container flex lg:items-center">
<div class="max-w-2xl 2xl:max-w-3xl w-full">
<nuxt-content :document="document" />
<HomeCTA />
</div>
<HeroSvg
class="hero-svg w-full h-2/3 xl:w-3/5 py-6 overflow-y-hidden hidden lg:block"
<SuperHero
class="
superhero
absolute
h-40
hidden
lg:h-full lg:relative lg:right-0 lg:w-full
md:block
right-6
w-1/3
"
/>
</div>
</template>
<script>
import HeroSvg from '~/static/img/hero.svg?inline'
import SuperHero from '~/static/img/superhero.svg?inline'
export default {
components: { HeroSvg },
components: {
SuperHero,
},
props: {
hero: {
document: {
type: Object,
required: true,
},
......@@ -26,82 +39,127 @@ export default {
<style lang="postcss" scoped>
.hero {
min-height: 50vh;
padding: max(8vh, 4rem) 1.5rem;
}
.hero-svg >>> svg path:first-child {
background: green;
animation: zou 1s cubic-bezier(0.27, -0.5, 0.89, 1.74);
animation-delay: 2s;
}
@keyframes zou {
@keyframes floatHero {
0% {
transform: rotate(370deg);
transform: translate(0px, 0px);
}
80% {
transform: rotate(359deg);
}
90% {
transform: rotate(361deg);
50% {
transform: translate(0px, -20px);
}
100% {
transform: rotate(360deg);
transform: translate(0px, 0px);
}
}
.hero >>> .svg-inline--fa {
animation: shake 10s cubic-bezier(0.36, 0.07, 0.19, 0.97) infinite;
animation-delay: 5s;
@keyframes floatDots {
0% {
transform: translate(0px, 0px);
}
50% {
transform: translate(-30px, -20px);
}
100% {
transform: translate(0px, 0px);
}
}
.hero >>> .link-contribute svg {
animation-delay: 5.5s;
@keyframes floatZigzag {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(15deg) translate(10px);
}
100% {
transform: rotate(0deg);
}
}
@keyframes shake {
@keyframes floatCape {
0% {
-webkit-transform: translate(2px, 1px) rotate(0deg);
transform: skewY(0deg);
}
1% {
-webkit-transform: translate(-1px, -2px) rotate(-3deg);
50% {
transform: skewY(2deg);
}
2% {
-webkit-transform: translate(-3px, 0px) rotate(3deg);
100% {
transform: skewY(0deg);
}
3% {
-webkit-transform: translate(0px, 2px) rotate(0deg);
}
@keyframes floatBall {
0% {
transform: rotate(0deg);
}
4% {
-webkit-transform: translate(1px, -1px) rotate(3deg);
50% {
transform: rotate(5deg);
}
5% {
-webkit-transform: translate(-1px, 2px) rotate(-3deg);
100% {
transform: rotate(0deg);
}
6% {
-webkit-transform: translate(-3px, 1px) rotate(0deg);
}
@keyframes pulseBreak {
0% {
transform: scale(1);
}
7% {
-webkit-transform: translate(2px, 1px) rotate(-3deg);
50% {
transform: scale(1.3);
}
8% {
-webkit-transform: translate(-1px, -1px) rotate(3deg);
100% {
transform: scale(1);
}
9% {
-webkit-transform: translate(2px, 2px) rotate(0deg);
}
@keyframes wind {
0% {
transform: translateX(-2000px);
}
10% {
-webkit-transform: translate(1px, -2px) rotate(-3deg);
100% {
transform: translateX(2000px);
}
}
11% {
transform: translate(0, 0) rotate(0deg);
}
.superhero #man {
animation: floatHero 4s ease-in-out infinite;
}
.superhero #cape {
animation: floatCape 3s ease-in-out infinite;
}
.superhero #break_chain {
animation: pulseBreak 3s ease-in-out infinite;
transform-box: fill-box;
transform-origin: center;
}
.superhero #chain_ball {
animation: floatBall 3s ease-in-out infinite;
transform-origin: top left;
transform-box: fill-box;
}
/* red particles */
.superhero #dots path[fill='#DC2626']:nth-child(odd) {
animation: floatDots 4s ease-in-out infinite;
}
.superhero #dots path[fill='#DC2626']:nth-child(even) {
animation: floatDots 3s ease-in-out infinite;
}
/* blue particles */
.superhero #dots path[fill='#5222D0']:nth-child(odd) {
animation: floatDots 5s ease-in-out infinite;
}
.superhero #dots path[fill='#5222D0']:nth-child(even) {
animation: floatDots 2s ease-in-out infinite;
}
/* zigzags */
.superhero #zigzag path {
animation: floatZigzag 5s ease-in-out infinite;
transform-box: fill-box;
}
/* gray lines */
.superhero #wind *:nth-child(3n + 1) {
animation: wind 6s linear infinite;
}
.superhero #wind *:nth-child(3n + 2) {
animation: wind 10s linear infinite;
}
.superhero #wind *:nth-child(3n + 3) {
animation: wind 12s linear infinite;
}
</style>
<template>
<section id="map">
<div class="container flex items-end mb-3 pl-8">
<FaviconMap class="w-12 h-12 mr-3 fill-current dark:text-gray-100" />
<a
href="https://carte.monnaie-libre.fr"
target="_blank"
class="
group
bg-clip-text bg-gradient-to-r
font-extrabold
from-purple-800
hover:underline
text-4xl text-transparent
to-blue-600
uppercase
"
rel="noopener noreferrer"
>
Carte
<fa
icon="external-link-alt"
class="w-3 ml-1.5 text-gray-500 opacity-0 group-hover:opacity-75"
/>
</a>
</div>
<iframe
:src="intersected ? $config.map_url : null"
class="w-full bg-gray-200"
style="height: 50vh; min-height: 600px"
title="Carte monnaie-libre"
/>
</section>
</template>
<script>
import FaviconMap from '~/static/img/favicon-map-g1.svg?inline'
export default {
components: {
FaviconMap,
},
data() {
return {
observer: null,
intersected: false,
}
},
mounted() {
this.observer = new IntersectionObserver(
(entries) => {
const item = entries[0]
if (item.isIntersecting) {
this.intersected = true
this.observer.disconnect()
}
},
{
rootMargin: '500px',
}
)
this.observer.observe(this.$el)
},
destroyed() {
this.observer.disconnect()
},
}
</script>
<template>
<aside>
<h1 class="text-3xl uppercase text-gray-400 font-bold ml-2 mb-2 md:ml-1.5">
Événements à venir...
</h1>
<template v-if="!loading">
<section id="agenda" class="container py-12">
<div class="border-b-2 container flex items-end mb-3 pb-2 pl-1">
<JuneCalendar class="w-12 mr-3 fill-current dark:text-gray-100" />
<a
v-for="(event, index) in events"
:key="index"
:href="`https://forum.monnaie-libre.fr/t/${event.slug}/${event.id}`"
targe="_blank"
class="block dark-hover:bg-gray-700 hover:bg-gray-100 p-2 mt-1 rounded-lg transition-colors"
:href="`${$config.forum_url}/calendar`"
target="_blank"
class="
group
bg-clip-text bg-gradient-to-r
font-extrabold
from-purple-800
hover:underline
text-4xl text-transparent
to-blue-600
uppercase
"
rel="noopener noreferrer"
>
<div class="text-sm text-gray-500">
{{ $d(Date.parse(event.event.start), 'long') }}
</div>
<div>{{ event.title }}</div>
Agenda
<fa
icon="external-link-alt"
class="w-3 ml-1.5 text-gray-500 opacity-0 group-hover:opacity-75"
/>
</a>
</template>
</div>
<aside v-if="!loading" class="lg:flex">
<div
v-for="(column, num) in columns"
:key="num"
class="flex-1"
:class="num === 1 && 'lg:ml-4'"
>
<a
v-for="(event, index) in column"
:key="index"
:href="`${$config.forum_url}${event.post.url}`"
target="_blank"
class="
block
hover:bg-hover-light
dark-hover:text-gray-800
p-2
mt-1
rounded-lg
transition-colors
"
rel="noopener noreferrer"
>
<div class="event-date text-sm text-gray-500">
{{ prettyDate(event.starts_at) }}
<!-- poka: I don't find tags anymore with new api, so this line is actually unused -->
<div v-for="(tag, i) in event.tag" :key="i">{{ tag }}</div>
</div>
<div v-html="emojify(event.post.topic.title)" />
</a>
</div>
</aside>
<div v-else class="h-80 flex items-center">
<span class="loading-state h-12 w-12 scale-150 transform mx-auto" />
</div>
</aside>
</section>
</template>
<script>
import JuneCalendar from '~/static/img/june-calendar.svg?inline'
import { fetchNextEvents } from '~/libs/api-forum'
import { performEmojiUnescape } from '~/libs/emoji'
export default {
name: 'HomeNextEvents',
components: {
JuneCalendar,
},
data() {
return {
events: [],
loading: false,
cols: 2,
}
},
computed: {
columns() {
const columns = []
const mid = Math.ceil(this.events.length / this.cols)
for (let col = 0; col < this.cols; col++) {
columns.push(this.events.slice(col * mid, col * mid + mid))
}
return columns
},
},
async mounted() {
this.loading = true
this.events = await fetchNextEvents()
this.events = await fetchNextEvents(`?start=${this.formatDateForParams()}`)
this.loading = false
},
methods: {
formatDateForParams(date = new Date()) {
const offset = date.getTimezoneOffset()
date = new Date(date.getTime() - offset * 60 * 1000)
return date.toISOString().split('T')[0]
},
prettyDate(date) {
const formatter = new Intl.DateTimeFormat(
this.$i18n.locale,
this.$i18n.dateTimeFormats[this.$i18n.locale].full
)
return formatter
.formatToParts(Date.parse(date))
.map(({ type, value }) => {
switch (type) {
case 'literal':
return value === ', ' ? ' à ' : value === ':' ? 'h' : value
default:
return value
}
})
.reduce((string, part) => string + part)
},
emojify(text) {
return performEmojiUnescape(text, {
emojiSet: 'images/emoji/twitter',
emojiCDNUrl: 'https://forum.monnaie-libre.fr',
getURL: (url) => url,
})
},
},
}
</script>
<style lang="postcss" scoped>
.event-date:first-letter {
text-transform: uppercase;
}
</style>
<template>
<section class="relative">
<div
class="absolute inset-0 z-0 opacity-70 bg-gradient-to-br from-purple-100 to-blue-200 pt-10 pb-12 dark:from-purple-900 dark:to-blue-900 -z-1"
/>
<div class="container pt-10 pb-12 relative">
<h1
class="font-bold mb-8 text-3xl text-center text-gray-700 dark:text-white dark:text-opacity-50"
>
Chercher...
</h1>
<div class="flex justify-evenly">
<TInputIcon
v-model="querySite"
type="search"
:placeholder="$t('home.searchSitePlaceholder')"
class="w-full"
input-class="text-xl rounded-full"
icon="search"
icon-class="text-2xl text-blue-100 dark:text-gray-600"
@keyup.enter="$router.push(`/recherche?q=${querySite}`)"
/>
<TInputIcon
v-model="queryRessources"
type="search"
:placeholder="$t('home.searchRessourcesPlaceholder')"
class="w-full mx-8"
input-class="text-xl rounded-full"
icon="search"
icon-class="text-2xl text-blue-100 dark:text-gray-600"
@keyup.enter="$router.push(`/ressources?q=${queryRessources}`)"
/>
</div>
</div>
</section>
</template>
<script>
export default {
data() {
return {
querySite: null,
queryRessources: null,
}
},
methods: {
searchOnSite() {
this.$router.push(`/recherche?q=${this.querySite}`)
},
},
}
</script>
<style lang="scss" scoped></style>
<template>
<section class="bg-gray-100 dark:bg-gray-800">
<div class="container py-12 relative prose">
<div class="flex justify-evenly">
<TInputIcon
ref="search"
v-model="query"
type="search"
:placeholder="placeholder"
class="w-full"
:input-class="inputClass"
icon="search"
:icon-class="iconClass"
icon-wrapper-class="top-0.5"
@keyup.enter="search()"
/>
<t-button :class="buttonClass" :text="$t('search')" @click="search()" />
</div>
</div>
</section>
</template>
<script>
export default {
props: {
inputClass: {
type: String,
default: 'text-2xl rounded-full',
},
iconClass: {
type: String,
default: 'text-2xl text-blue-100 dark:text-gray-600',
},
buttonClass: {
type: String,
default: 'text-xl ml-6 rounded-full',
},
},
data() {
return {
query: null,
placeholder: '',
timeout: null,
typeSpeed: 50,
backDelay: 1000,
backSpeed: 5,
strPos: 0,
arrayPos: 0,
curString: null,
curStrPos: null,
strings: [
'Chercher sur le site...',
'Chercher un site sur la monnaie libre...',
'Chercher un groupe local, une asso...',
"Besoin d'aide ? Une question ? (FAQ)",
"Besoin d'un tutoriel ? Une vidéo ?",
'Un terme à expliquer ? (Lexique)',
],
smartBackspace: true,
}
},
mounted() {
this.typewrite(this.strings[this.arrayPos], this.strPos)
},
methods: {
search() {
if (this.query) {
this.$router.push(`/recherche?q=${this.query}`)
} else {
this.$refs.search.focus()
}
},
typewrite(curString, curStrPos) {
const numChars = 1
// skip over any HTML chars
curStrPos = this.typeHtmlChars(curString, curStrPos)
this.timeout = setTimeout(() => {
// We're done with this sentence!
if (curStrPos >= curString.length) {
this.doneTyping(curString, curStrPos)
} else {
this.keepTyping(curString, curStrPos, numChars)
}
}, this.typeSpeed)
},
keepTyping(curString, curStrPos, numChars) {
// start typing each new char into existing string
// curString: arg, this.el.html: original text inside element
curStrPos += numChars
const nextString = curString.substr(0, curStrPos)
this.placeholder = nextString
// loop the function
this.typewrite(curString, curStrPos)
},
doneTyping(curString, curStrPos) {
this.timeout = setTimeout(() => {
this.backspace(curString, curStrPos)
}, this.backDelay)
},
typeHtmlChars(curString, curStrPos) {
const curChar = curString.substr(curStrPos).charAt(0)
if (curChar === '<' || curChar === '&') {
let endTag = ''
if (curChar === '<') {
endTag = '>'
} else {
endTag = ';'
}
while (curString.substr(curStrPos + 1).charAt(0) !== endTag) {
curStrPos++
if (curStrPos + 1 > curString.length) {
break
}
}
curStrPos++
}
return curStrPos
},
backspace(curString, curStrPos) {
this.timeout = setTimeout(() => {
curStrPos = this.backSpaceHtmlChars(curString, curStrPos, this)
// replace text with base text + typed characters
const curStringAtPosition = curString.substr(0, curStrPos)
this.placeholder = curStringAtPosition
// if smartBack is enabled
if (this.smartBackspace) {
// the remaining part of the current string is equal of the same part of the new string
const nextString = this.strings[this.arrayPos + 1]
if (
nextString &&
curStringAtPosition === nextString.substr(0, curStrPos)
) {
this.stopNum = curStrPos
} else {
this.stopNum = 0
}
}
// if the number (id of character in current string) is
// less than the stop number, keep going
if (curStrPos > this.stopNum) {
// subtract characters one by one
curStrPos--
// loop the function
this.backspace(curString, curStrPos)
} else if (curStrPos <= this.stopNum) {
// if the stop number has been reached, increase
// array position to next string
this.arrayPos++
// When looping, begin at the beginning after backspace complete
if (this.arrayPos === this.strings.length) {
this.arrayPos = 0
this.typewrite(this.strings[this.arrayPos], this.strPos)
} else {
this.typewrite(this.strings[this.arrayPos], curStrPos)
}
}
}, this.backSpeed)
},
backSpaceHtmlChars(curString, curStrPos) {
const curChar = curString.substr(curStrPos).charAt(0)
if (curChar === '>' || curChar === ';') {
let endTag = ''
if (curChar === '>') {
endTag = '<'
} else {
endTag = '&'
}
while (curString.substr(curStrPos - 1).charAt(0) !== endTag) {
curStrPos--
if (curStrPos < 0) {
break
}
}
curStrPos--
}
return curStrPos
},
},
}
</script>
<style lang="scss" scoped></style>
<template>
<div
class="flex flex-wrap justify-center bg-gradient-to-r from-purple-600 to-purple-400 p-6 text-white"
class="
bg-gradient-to-r
from-purple-600
to-purple-400
pt-8
text-white
z-10
dark:from-purple-900 dark:to-purple-700 dark:text-gray-100
transition
"
>
<div class="container">
<div class="flex flex-wrap mb-4 w-full">
<div class="w-full sm:w-1/2 md:w-1/2">
<div>
<h3 class="text-3xl py-4">À propos</h3>
<nuxt-content
v-if="$store.state.footer"
:document="$store.state.footer"
/>
</div>
<div>
<h3 class="text-3xl mt-8 py-4">Encouragez les développeurs</h3>
<div class="text-xl mb-2">Participez au financement en Ğ1 !</div>
<div
class="grid grid-flow-row gap-8 mb-4 w-full sm:grid-cols-5 lg:gap-x-20"
>
<div class="col-span-2 sm:col-span-5 lg:col-span-3">
<h3 class="text-3xl py-4">À propos</h3>
<nuxt-content
v-if="$store.state.footer"
:document="$store.state.footer"
class="about"
/>
</div>
<G1Crowdfunding
pubkey="78ZwwgpgdH5uLZLbThUQH7LKwPgjMunYfLiCfUCySkM8"
class="flex"
>
<template #button="{ onClick }">
<t-button @click="onClick" class="w-full lg:w-max">
<fa icon="heart"></fa><span class="ml-2">Je participe !</span>
</t-button>
</template>
</G1Crowdfunding>
</div>
<div class="col-span-2 sm:col-span-5 lg:col-span-3 max-w-xl">
<h3 class="text-3xl py-4">Encouragez les développeurs</h3>
<div class="text-xl mb-3">Participez au financement en Ğ1 !</div>
<G1Crowdfunding
pubkey="78ZwwgpgdH5uLZLbThUQH7LKwPgjMunYfLiCfUCySkM8"
class="sm:grid grid-flow-col"
>
<template #button="{ onClick }">
<t-button class="w-full sm:w-auto lg:w-max" @click="onClick">
<fa icon="heart"></fa><span class="ml-2">Je participe !</span>
</t-button>
</template>
</G1Crowdfunding>
</div>
<div
class="w-full flex flex-col pl-0 sm:w-1/2 sm:items-start sm:pl-8 md:justify-around md:flex-row"
class="
col-span-2
sm:col-span-3 sm:flex
justify-between
lg:col-span-2 lg:row-start-1 lg:col-start-4
"
>
<div>
<h3 class="text-3xl py-4">Sections</h3>
<ul>
<li>
<nuxt-link class="hover:underline" to="/blog">Blog</nuxt-link>
</li>
<li>
<nuxt-link class="hover:underline" to="/ressources">
Ressources
</nuxt-link>
</li>
<li>
<nuxt-link class="hover:underline" to="/faq">FAQ</nuxt-link>
</li>
<li>
<nuxt-link class="hover:underline" to="/lexique">
Lexique
</nuxt-link>
<div v-for="(column, index) of menuFooter" :key="index">
<h3 class="text-3xl py-4">{{ column.title }}</h3>
<ul v-if="column.type === 'column'">
<li v-for="(menu, i) of column.menus" :key="i">
<nuxt-link class="hover:underline" :to="menu.url">{{
menu.title
}}</nuxt-link>
</li>
</ul>
</div>
<div>
<h3 class="text-3xl py-4">Ressources</h3>
<ul>
<ul v-if="column.type === 'ressources'" class="grid gap-x-6">
<li v-for="category in categories" :key="category.title">
<nuxt-link
class="hover:underline"
:to="`/ressources?cat=${category.title}`"
:to="{
path: '/ressources',
query: { filters: category.title },
}"
>
{{ category.title }}
</nuxt-link>
......@@ -67,21 +72,142 @@
</ul>
</div>
</div>
<div class="sm:col-span-2">
<h3 class="text-3xl py-4">Suivez-nous</h3>
<div class="text-xl">
<a
v-if="$config.mastodon_link && $config.mastodon_user"
:href="$config.mastodon_link"
target="_blank"
class="flex items-center mb-2 hover:underline"
rel="noopener noreferrer"
>
<fa :icon="['fab', 'mastodon']" class="text-2xl mr-2.5" />
<span>{{ $config.mastodon_user }}</span>
</a>
<a
v-if="$config.twitter_user"
:href="`https://twitter.com/${$config.twitter_user}`"
target="_blank"
class="flex items-center mb-2 hover:underline"
rel="noopener noreferrer"
>
<fa :icon="['fab', 'twitter']" class="text-2xl mr-2.5" />
<span>@{{ $config.twitter_user }}</span>
</a>
<a
v-if="$config.facebook_group"
:href="$config.facebook_group"
target="_blank"
class="flex items-center mb-2 hover:underline"
rel="noopener noreferrer"
>
<fa :icon="['fab', 'facebook']" class="text-2xl mr-2.5" />
<span>Groupe Facebook</span>
</a>
<a
href="https://t.me/monnaielibrejune"
target="_blank"
class="flex items-center mb-2 hover:underline"
rel="noopener noreferrer"
>
<fa :icon="['fab', 'telegram']" class="text-2xl mr-2.5" />
<span>Groupe de discussion Telegram</span>
</a>
<a
href="https://t.me/infomonnaielibre"
target="_blank"
class="flex items-center mb-2 hover:underline"
rel="noopener noreferrer"
>
<fa :icon="['fab', 'telegram']" class="text-2xl mr-2.5" />
<span>Canal d'info Telegram</span>
</a>
<nuxt-link
to="/feed"
class="flex items-center mb-2 hover:underline"
>
<fa icon="rss-square" class="text-2xl mr-2.5" />
<span>Flux RSS</span>
</nuxt-link>
</div>
</div>
</div>
<div
class="flex items-center justify-between text-xs border-t mt-12 py-4"
>
<div class="block lg:flex justify-between">
<nuxt-link to="/mentions-legales" class="lg:mr-4 hover:underline">
Mentions légales
</nuxt-link>
<div class="lg:mr-4">
Site généré avec
<a
href="https://nuxtjs.org"
target="_blank"
class="hover:underline"
rel="noopener noreferrer"
>
nuxtjs
</a>
au commit
<a
:href="`https://git.duniter.org/websites/monnaie-libre-fr/-/commit/${$config.git_commit}`"
target="_blank"
class="hover:underline"
rel="noopener noreferrer"
>
{{ $config.git_commit }}
</a>
</div>
</div>
<div class="">
<a
rel="license noopener noreferrer"
href="http://creativecommons.org/licenses/by-sa/4.0/"
target="_blank"
class="md:flex items-center"
>
<div class="pb-1 md:pr-3 md:pb-0">
Tout le contenu du site est<br />
en licence CC BY-SA 4.0
</div>
<img
alt="Licence Creative Commons"
style="border-width: 0"
src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png"
/>
</a>
</div>
</div>
</div>
</div>
</template>
<script>
import menus from '~/static/settings/menus.json'
import categories from '~/static/settings/categories.json'
export default {
data() {
return {
menuFooter: menus.menu_footer,
categories: categories.ressources,
}
},
}
</script>
<style lang="scss" scoped></style>
<style lang="postcss" scoped>
>>> .about a {
font-weight: 500;
text-decoration: underline;
}
</style>
<template>
<div
class="header bg-white w-full fixed z-20 top-0 transition-transform duration-300 transform translate-y-0 dark:bg-black dark:text-gray-300"
class="
header
bg-white
w-full
fixed
z-20
top-0
transition
duration-300
transform
translate-y-0
dark:bg-black dark:text-gray-300
"
:class="{ '-translate-y-20': scrolled }"
>
<nav class="container flex justify-between items-center mx-auto h-16">
<LayoutHeaderLogo />
<LayoutHeaderMenuHamburger class="mr-3 flex lg:hidden">
<LayoutHeaderMenu class="flex-col space-y-4" />
</LayoutHeaderMenuHamburger>
<LayoutHeaderLogo :show-title="!searchFocus" />
<div class="flex justify-end items-center relative">
<LayoutHeaderMenu />
<LayoutHeaderMenuSearch @search-focus="searchFocus = $event" />
<LayoutHeaderMenu class="hidden lg:flex" />
<AppDarkMode class="ml-2 mr-4" />
<AppDarkModeToggle class="hidden xl:inline-flex ml-3" />
<LayoutHeaderMenuAvatar />
<LayoutHeaderMenuAvatar class="hidden sm:block" />
</div>
</nav>
</div>
......@@ -25,6 +43,7 @@ export default {
limitPosition: 64,
scrolled: false,
lastPosition: 0,
searchFocus: false,
}
},
mounted() {
......@@ -60,13 +79,4 @@ export default {
/* Copied from discourse */
box-shadow: 0 2px 4px -1px rgb(0 0 0 / 25%);
}
.menu-hamburger {
display: block;
fill: none;
height: 16px;
width: 16px;
stroke: currentcolor;
stroke-width: 3;
overflow: visible;
}
</style>
......@@ -4,12 +4,32 @@
class="group inline-flex items-center focus:outline-none py-3"
>
<div
class="w-9 mr-4 fill-current group-hover:text-purple-800 transition-colors"
class="
w-6
sm:w-9
mr-4
fill-current
group-hover:text-purple-800
transition-colors
"
v-html="$options.rawLogo"
/>
<span
class="bg-clip-text bg-gradient-to-r font-semibold from-purple-800 group-hover:text-transparent text-2xl to-blue-700 tracking-tight transition-colors"
class="
bg-clip-text bg-gradient-to-r
font-semibold
from-purple-800
group-hover:text-transparent
text-lg
sm:text-xl
lg:text-2xl
to-blue-700
tracking-tight
transition-colors
whitespace-nowrap
"
:class="!props.showTitle && 'hidden lg:block'"
>
{{ $options.config.site_title }}
</span>
......@@ -24,5 +44,11 @@ export default {
name: 'LayoutHeaderLogo',
rawLogo,
config,
props: {
showTitle: {
type: Boolean,
default: true,
},
},
}
</script>
<template>
<div class="flex">
<template v-for="(item, index) of menu">
<nuxt-link
v-if="item.url.startsWith('/')"
:key="index"
class="inline-flex items-center relative cursor-pointer whitespace-nowrap py-2 px-3 hover:bg-hover-light dark-hover:bg-hover-dark rounded-full focus:outline-none focus:ring-2 transition-shadow"
:to="item.url"
>
<nuxt-link v-if="item.url" :key="index" class="menu-item" :to="item.url">
{{ item.title }}
</nuxt-link>
</template>
<t-dropdown
toggle-on-hover
:classes="{
dropdown:
'origin-top-left absolute left-0 w-80 rounded-md shadow bg-white mt-1 dark:bg-gray-600',
}"
>
<a
slot="trigger"
class="inline-flex items-center relative cursor-pointer whitespace-nowrap py-2 px-3 hover:bg-hover-light dark-hover:bg-hover-dark rounded-full focus:outline-none focus:ring-2 transition-shadow"
href="https://forum.monnaie-libre.fr/"
target="_blank"
rel="noopener noreferrer"
<t-dropdown
v-else-if="item.title.toLowerCase() === 'forum'"
:key="index"
toggle-on-hover
:hide-on-leave-timeout="0"
:classes="{
dropdown:
'absolute right-0 rounded-md shadow-lg bg-white dark:bg-gray-600 dark:border-gray-500 border transform translate-y-1',
}"
>
Forum
<fa icon="external-link-alt" class="w-3 ml-1 text-gray-500" />
</a>
<LayoutHeaderMenuForum />
</t-dropdown>
<a
slot="trigger"
class="menu-item"
:href="$config.forum_url"
target="_blank"
rel="noopener noreferrer"
>
Forum
<fa
icon="external-link-alt"
class="w-3 ml-1.5 text-gray-500 opacity-75"
/>
</a>
<LayoutHeaderMenuForum />
</t-dropdown>
</template>
</div>
</template>
......@@ -46,4 +46,8 @@ export default {
}
</script>
<style lang="scss" scoped></style>
<style lang="postcss" scoped>
.menu-item {
@apply inline-flex items-center relative cursor-pointer whitespace-nowrap py-2 px-3 text-gray-600 hover:text-gray-700 hover:bg-hover-light dark:text-gray-200 rounded-full focus:outline-none focus:ring-2;
}
</style>
<template functional>
<button
type="button"
class="inline-flex items-center relative px-2 dark:border-gray-500 border-2 rounded-full hover:shadow-lg focus:outline-none focus:ring-2 transition-shadow"
aria-label="User menu"
<template>
<t-dropdown
toggle-on-hover
:hide-on-leave-timeout="0"
:show.sync="show"
:classes="{
dropdown:
'absolute right-0 rounded-md shadow-lg bg-white dark:bg-gray-600 dark:border-gray-500 border transform translate-y-1',
}"
>
<fa icon="bars" class="ml-2 text-gray-500" />
<div slot="trigger" class="flex items-center ml-5">
<button
type="button"
aria-label="User menu"
aria-haspopup="true"
class="h-8 w-8 focus:ring-2 focus:outline-none rounded-full"
>
<img
v-if="user && user.avatar_url"
:src="user.avatar_url"
class="rounded-full"
/>
<fa v-else icon="user-circle" class="text-3xl" />
</button>
<fa icon="caret-down" class="pl-1.5" />
</div>
<div class="my-1" style="min-width: 15rem">
<template v-if="user">
<div class="px-3 py-2">
<div>{{ user.name }}</div>
<div class="text-sm text-gray-600 dark:text-gray-400">
@{{ user.username }}
</div>
</div>
<hr class="border-t dark:border-gray-500 my-1" />
<a class="menu-item" href="/admin/#/" target="_blank"
><svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="fill-current text-gray-400 mr-1"
>
<path
d="m13.366 3 5.625 5.493L19 19.169C19 20.176 18.156 21 17.125 21H5.865C4.836 21 4 20.176 4 19.17V4.83C4 3.825 4.834 3 5.866 3h7.5zM6.94 12.11a.94.94 0 1 0 0 1.88h9.12a.94.94 0 0 0 0-1.88H6.94zm0 3.89a.94.94 0 1 0 0 1.88h9.12a.94.94 0 1 0 0-1.88H6.94zm5.073-6h5.139l-5.14-5.053V10z"
></path></svg
>Contenus
</a>
<a
class="menu-item justify-between"
href="/admin/#/workflow"
target="_blank"
>
<div class="flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="fill-current text-gray-400 mr-1.5"
>
<path
d="M10 4h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1h-3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1zm7 0h3a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1h-3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1zM3 4h3a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1z"
></path></svg
>Flux
</div>
<div v-if="git">
<span
class="
bg-purple-200
text-purple-800
font-medium
py-0.5
px-1
text-sm
rounded
"
v-text="git.draft.count"
/>
<span
class="
bg-yellow-200
text-yellow-800
font-medium
py-0.5
px-1
text-sm
rounded
"
v-text="git.pending_review.count"
/>
<span
class="
bg-green-200
text-green-800
font-medium
py-0.5
px-1
text-sm
rounded
"
v-text="git.pending_publish.count"
/>
</div>
</a>
<hr class="border-t dark:border-gray-500 my-1" />
<a
class="menu-item justify-between"
href="https://git.duniter.org/websites/monnaie-libre-fr"
target="_blank"
>
<div>Dépôt gitlab</div>
<span
v-if="isLoading === true"
class="loading-state h-6 w-6 border-gray-500"
/>
<fa
v-if="isLoading === 'error'"
icon="exclamation-triangle"
class="w-3 ml-1.5 text-gray-500 opacity-75"
/>
</a>
<template v-if="git">
<div class="flex text-xs">
<a
class="menu-item whitespace-nowrap"
href="https://git.duniter.org/websites/monnaie-libre-fr/-/issues"
target="_blank"
v-text="`${git.issueStatusCounts.opened} tickets`"
/>
<a
class="menu-item whitespace-nowrap"
href="https://git.duniter.org/websites/monnaie-libre-fr/-/merge_requests"
target="_blank"
v-text="`${git.mergeRequests.count} demandes de fusion`"
/>
</div>
<a
class="menu-item justify-between"
:href="`https://git.duniter.org/websites/monnaie-libre-fr/-/issues?scope=all&utf8=%E2%9C%93&state=opened&author_username=${user.username}`"
target="_blank"
>
<div>Mes tickets</div>
<div
class="
bg-gray-200
text-gray-800
font-medium
w-5
h-5
text-center text-sm
rounded-full
"
v-text="git.my_issues.count"
/>
</a>
<a
class="menu-item justify-between"
:href="`https://git.duniter.org/websites/monnaie-libre-fr/-/merge_requests?scope=all&utf8=%E2%9C%93&state=opened&author_username=${user.username}`"
target="_blank"
>
<div>Mes demandes de fusion</div>
<div
class="
bg-gray-200
text-gray-800
font-medium
w-5
h-5
text-center text-sm
rounded-full
"
v-text="git.my_merge_requests.count"
/>
</a>
</template>
</template>
<div class="flex items-center h-10 w-10 pl-3">
<fa icon="user-circle" class="text-3xl" />
<template v-else>
<a
href="/admin/#/"
target="_blank"
class="menu-item group"
@click="fetchLocalStorageChange"
>
<span>Se connecter</span>
<fa
icon="external-link-alt"
class="w-3 ml-1.5 text-gray-500 opacity-75"
/>
</a>
<nuxt-link to="/contribuer" class="menu-item">
<span>Contribuer</span>
</nuxt-link>
</template>
<hr class="border-t dark:border-gray-500 my-1" />
<div class="flex items-center justify-between">
<nuxt-link to="/aide" class="menu-item">
<fa icon="info-circle" class="text-gray-400 mr-2" />
<span>Aide</span>
</nuxt-link>
<AppDarkModeToggle class="xl:hidden ml-2" />
<AppA11y class="ml-2" />
</div>
</div>
</button>
</t-dropdown>
</template>
<script>
export default {
name: 'LayoutHeaderMenuAvatar',
data() {
return {
show: false,
user: null,
git: null,
lastFetch: null,
isLoading: false,
}
},
watch: {
show(newVal) {
// Debounce fetch under 10s
if (newVal && this.user && Date.now() - this.lastFetch > 10000) {
// Fetch gitlab data
this.fetchGitlab()
}
},
},
mounted() {
this.fetchCmsUser()
},
methods: {
fetchCmsUser() {
if (localStorage.getItem('netlify-cms-user')) {
this.user = JSON.parse(localStorage.getItem('netlify-cms-user'))
}
},
// if User click on "Connect" link, wait for authentification
fetchLocalStorageChange() {
setInterval(() => {
this.fetchCmsUser()
}, 1000)
},
fetchGitlab() {
this.isLoading = true
this.$axios
.$post('https://git.duniter.org/api/graphql', {
query: `#graphql
query projectML($authorUsername: String) {
project(fullPath: "websites/monnaie-libre-fr") {
lastActivityAt
issueStatusCounts {
opened
}
mergeRequests(state: opened) {
count
}
my_issues: issues(authorUsername: $authorUsername, state: opened) {
count
}
my_merge_requests:mergeRequests(authorUsername: $authorUsername, state: opened) {
count
}
draft: mergeRequests(labels: "netlify-cms/draft", state: opened) {
count
}
pending_review: mergeRequests(labels: "netlify-cms/pending_review", state: opened) {
count
}
pending_publish: mergeRequests(labels: "netlify-cms/pending_publish", state: opened) {
count
}
}
}
`,
variables: {
authorUsername: this.user.username,
},
})
.then(({ data }) => {
this.git = data.project
this.lastFetch = Date.now()
this.isLoading = false
})
.catch(() => {
this.isLoading = 'error'
})
},
},
}
</script>
<style lang="postcss" scoped>
.menu-item {
@apply flex items-center text-gray-600 hover:text-gray-700 hover:bg-hover-light dark:text-gray-200 px-3 py-1 w-full;
}
</style>
<template>
<div v-if="!loading">
<div class="text-left text-xs py-1">
<div v-if="!loading" class="menu-forum flex items-center">
<MiniMap class="mx-4" style="min-width: 440px" />
<div class="text-left text-xs py-1 w-80 border-l dark:border-gray-500">
<a
v-for="(cat, index) in categories"
:key="index"
class="block hover:bg-hover-light hover:text-gray-700 p-2 text-gray-600 dark:text-gray-200 w-full"
:href="`https://forum.monnaie-libre.fr${cat.topic_url}`"
class="
block
text-gray-600
hover:text-gray-700 hover:bg-hover-light
dark:text-gray-200
p-2
w-full
"
:href="`${$config.forum_url}/c/${cat.slug}/${cat.id}`"
target="_blank"
rel="noopener noreferrer"
>
......@@ -49,4 +58,7 @@ export default {
margin-right: 5px;
display: inline-block;
}
.hamburger-menu .menu-forum {
display: none;
}
</style>
<script>
export default {
name: 'LayoutHeaderMenuHamburger',
data() {
return {
isOpen: false,
}
},
}
</script>
<template>
<div>
<div
class="hamburger flex flex-col space-between space-y-1.5 p-6 -mx-6"
:class="isOpen ? 'is-open' : ''"
@click="isOpen = !isOpen"
>
<span class="hamburger-item" />
<span class="hamburger-item" />
<span class="hamburger-item" />
</div>
<transition :name="isOpen ? 'slideback' : 'slide'">
<div
v-show="isOpen"
class="hamburger-menu fixed bg-white left-0 top-0 w-min z-10 shadow p-8"
>
<slot />
</div>
</transition>
</div>
</template>
<style lang="postcss" scoped>
.hamburger-item {
@apply block w-6 h-0.5 bg-gray-800 dark:bg-gray-300 transition;
}
.is-open.hamburger > span:nth-child(1) {
transform: translate(0, 0.5rem) rotate(45deg);
}
.is-open.hamburger > span:nth-child(2) {
opacity: 0;
}
.is-open.hamburger > span:nth-child(3) {
transform: translate(0, -0.5rem) rotate(-45deg);
}
.hamburger-menu {
top: 65px;
height: calc(100vh - 65px);
}
.slide-leave-active,
.slide-enter-active {
transition: 0.5s;
}
.slide-enter {
transform: translate(100%, 0);
}
.slide-leave-to {
transform: translate(-100%, 0);
}
.slideback-leave-active,
.slideback-enter-active {
transition: 0.5s;
}
.slideback-enter {
transform: translate(-100%, 0);
}
.slideback-leave-to {
transform: translate(100%, 0);
}
</style>
<template>
<div :class="isVisible && 'mr-2'">
<t-input
ref="search"
v-model="query"
:class="!isVisible && 'hidden'"
type="search"
:placeholder="$t('search') + ` [${isMac ? 'cmd' : 'ctrl'} K]`"
class="pl-12 w-full text-base rounded-full"
@keyup.enter="search()"
@blur="isVisible = false"
@focus="$event.target.select()"
/>
<div
class="flex items-center bottom-0 top-0"
:class="[
isVisible
? `absolute ml-3 text-2xl text-blue-100 dark:text-gray-600`
: 'text-2xl text-blue-300 dark:text-blue-800 hover:text-blue-400 dark-hover:text-blue-700 cursor-text py-3 pr-3 lg:pl-48',
]"
@click="isVisible = true"
>
<fa slot="icon" icon="search" :class="[isVisible ? `` : '', '-mb-1']" />
</div>
</div>
</template>
<script>
export default {
data() {
return {
query: '',
isVisible: false,
isMac: false,
}
},
watch: {
isVisible(newValue) {
this.$emit('search-focus', newValue)
if (newValue) {
this.$nextTick(() => {
this.$refs.search.$el.focus()
})
}
},
},
mounted() {
this.isMac = /(macintosh|macintel|macppc|mac68k|macos)/i.test(
navigator.userAgentData.platform
)
document.addEventListener('keydown', this.searchShortcut)
},
beforeDestroy() {
document.removeEventListener('keydown', this.searchShortcut)
},
methods: {
search() {
if (this.$route.path !== '/recherche') {
this.$router.push(`/recherche?q=${this.query}`)
} else {
this.$nuxt.$emit('global-search', this.query)
}
},
searchShortcut(e) {
if (e.key === 'k' && (e.ctrlKey || e.metaKey)) {
e.preventDefault() // present "Save Page" from getting triggered.
this.isVisible = !this.isVisible
}
},
},
}
</script>
<style lang="postcss" scoped></style>
<template>
<div class="container">
<div class="container mb-16">
<slot name="header">
<PageHeader :document="document" />
</slot>
......@@ -8,16 +8,26 @@
<nuxt-content :document="document" class="w-full prose dark:prose-dark" />
<div
class="sticky h-full top-12 lg:w-1/4 lg:ml-12 mt-8 lg:mt-0 border-t pt-8 lg:pt-0 lg:border-none"
class="
sticky
h-full
top-12
lg:w-1/4 lg:ml-12
mt-8
lg:mt-0
border-t
pt-8
lg:pt-0 lg:border-none
"
>
<PageToc v-if="toc" :document="document" class="mb-8" />
<slot name="sidebar" />
<AppShareModal :document="document" class="mb-2" />
<PageEdit :document="document" class="w-full border-t mb-8 pt-2" />
</div>
</div>
<slot name="footer">
<PageBottom :document="document" class="w-full border-t my-8 pt-8" />
</slot>
<slot name="footer" />
</div>
</template>
......@@ -29,10 +39,6 @@ export default {
type: Object,
required: true,
},
toc: {
type: Boolean,
default: true,
},
},
}
</script>
<template>
<div class="flex flex-col text-gray-500 text-sm">
<span class="font-bold">
<div class="text-right text-gray-400 dark:text-gray-600 text-sm">
<div class="font-semibold">
{{ $t('page.updatedAt') }}
{{ $d(Date.parse(document.updatedAt), 'short').replace(',', ' à') }}
</span>
</div>
<a
:href="`admin/#/collections${document.dir}/entries/${document.slug}`"
:href="`https://monnaie-libre.fr/admin/#/collections${document.dir}/entries/${document.slug}`"
target="_blank"
rel="noopener noreferrer"
class="hover:underline"
class="hover:underline block"
>
{{ $t('page.editAdmin') }}
</a>
......@@ -19,7 +19,7 @@
:href="linkVSCode"
target="_blank"
rel="noopener noreferrer"
class="hover:underline"
class="hover:underline block"
>
{{ $t('page.editVscode') }}
</a>
......@@ -28,6 +28,7 @@
<script>
export default {
name: 'PageEdit',
props: {
document: {
type: Object,
......
<template>
<nav>
<nuxt-link
v-if="showNext && nextPage"
class="text-3xl pb-3 mb-3 block group"
:to="nextPage.to"
>
<span class="opacity-75">Page suivante</span>
<fa icon="arrow-right" class="mx-4"></fa>
<span class="font-semibold group-hover:underline">
{{ nextPage.title }}
</span>
</nuxt-link>
<section
class="section-flatplan gap-6 grid lg:grid-cols-4 md:grid-cols-2 group"
>
<nuxt-link
v-for="(section, i) in sections"
:key="i"
:to="section.to"
class="
bg-blue-100
dark:bg-blue-900
px-4
py-4
pb-3
rounded
transition
hover:shadow-xl
transform
hover:-translate-y-0.5
"
:class="
showNext &&
(nextPage && nextPage.to === section.to
? 'ring-4 ring-blue-200 dark:ring-blue-800'
: 'bg-opacity-80 dark:bg-opacity-80 group-hover:bg-opacity-100')
"
>
<div class="flex items-center justify-between">
<h2 class="font-semibold text-lg">{{ section.title }}</h2>
<fa icon="arrow-right" class="ml-4"></fa>
</div>
<ul class="mt-1 font-light list-flatplan">
<li v-for="(item, ii) in section.children" :key="ii">
{{ item }}
</li>
</ul>
</nuxt-link>
</section>
</nav>
</template>
<script>
export default {
name: 'PageFlatPlan',
props: {
plan: {
type: String,
required: true,
},
showNext: {
type: Boolean,
default: false,
},
},
data() {
return {
sections: [],
}
},
async fetch() {
const document = await this.$content(this.plan).fetch()
// This is ugly, but I don't found another way to extract title, link and li elements.
// It's work with 'content:file:beforeParse' nuxt-content hook. See in config.nuxt.js
this.sections = document.body.children
.filter((item) => item.tag === 'section')
.map((item) => {
const title = item.children.filter((item) => item.type === 'element')[0]
.children[1]
const children = item.children
.filter((item) => item.type === 'element')[1]
.children.filter((item) => item.type === 'element')
.map((li) => li.children[0].value)
return {
children,
title: title.children[0].value,
to: title.props.to,
}
})
},
computed: {
nextPage() {
const index = this.sections.findIndex(
(index) => index.to === this.$route.path
)
return this.sections[index + 1] ? this.sections[index + 1] : null
},
},
}
</script>
<style lang="postcss" scoped>
.list-flatplan > li {
position: relative;
padding-left: 1.75em;
margin: 0.5em 0;
line-height: 1.2em;
}
.list-flatplan > li::before {
content: '';
position: absolute;
background-color: #3b82f6;
border-radius: 50%;
width: 0.375em;
height: 0.375em;
top: 0.375em;
left: 0.25em;
}
.section-flatplan .svg-inline--fa {
animation: shake 10s cubic-bezier(0.36, 0.07, 0.19, 0.97) infinite;
animation-delay: 5s;
}
.section-flatplan > a:nth-child(2) .svg-inline--fa {
animation-delay: 5.5s;
}
.section-flatplan > a:nth-child(3) .svg-inline--fa {
animation-delay: 6s;
}
.section-flatplan > a:nth-child(4) .svg-inline--fa {
animation-delay: 6.5s;
}
@keyframes shake {
0% {
-webkit-transform: translate(2px, 1px) rotate(0deg);
}
1% {
-webkit-transform: translate(-1px, -2px) rotate(-3deg);
}
2% {
-webkit-transform: translate(-3px, 0px) rotate(3deg);
}
3% {
-webkit-transform: translate(0px, 2px) rotate(0deg);
}
4% {
-webkit-transform: translate(1px, -1px) rotate(3deg);
}
5% {
-webkit-transform: translate(-1px, 2px) rotate(-3deg);
}
6% {
-webkit-transform: translate(-3px, 1px) rotate(0deg);
}
7% {
-webkit-transform: translate(2px, 1px) rotate(-3deg);
}
8% {
-webkit-transform: translate(-1px, -1px) rotate(3deg);
}
9% {
-webkit-transform: translate(2px, 2px) rotate(0deg);
}
10% {
-webkit-transform: translate(1px, -2px) rotate(-3deg);
}
11% {
transform: translate(0, 0) rotate(0deg);
}
}
</style>
<template>
<div class="flex items-center justify-between py-8">
<h1 class="text-4xl font-extrabold">{{ document.title }}</h1>
<div class="flex items-center justify-between">
<h1
v-prevent-last-char-break
class="
text-3xl
md:text-4xl
2xl:text-5xl
bg-clip-text
text-transparent
bg-gradient-to-r
from-purple-800
to-blue-600
font-extrabold
leading-tight
py-10
"
>
{{ document.title }}
</h1>
<slot />
</div>
</template>
......
<template>
<aside v-if="items.length">
<h1 class="text-xl uppercase text-gray-400 font-bold mb-2">
{{ $t('page.related') }}
</h1>
<ul>
<li
v-for="(item, index) in items"
:key="index"
class="block leading-5 mb-2 text-gray-500"
>
<nuxt-link :to="item.path" class="hover:underline">
{{ item.title }}
</nuxt-link>
</li>
</ul>
</aside>
<PageSidebarAside
v-if="items.length"
:title="$t('page.related')"
:items="items"
/>
</template>
<script>
......@@ -43,14 +31,36 @@ export default {
.split(' ')
.filter((word) => word.length > 3)
this.items = await this.$content(this.path)
const items = await this.$content(this.path)
.where({ title: { $containsAny: words } })
.limit(6)
.only(['title', 'path'])
.fetch()
/* test
await $vm0.$content('faq')
.search({
query: {
type: 'bool',
should: ['title', 'description'].map(field => ({
type: 'match',
field,
value: 'a quoi ça sert d être référent',
prefix_length: 1,
operator: 'or',
minimum_should_match: 1,
fuzziness: 1,
extended: true
}))
}
})
.limit(6)
.only(['title', 'description', 'path'])
.fetch()
*/
// remove current document
this.items = this.items
this.items = items
.filter((item) => item.title !== this.document.title)
.slice(0, 5) // to be sure we return only 5 elements
},
......