Add sync, Tree, Markdown, content editor.

This commit is contained in:
2024-11-10 15:41:47 +01:00
parent 41951d7603
commit 721e7ff3db
32 changed files with 658 additions and 124 deletions

22
pages/editor.vue Normal file
View File

@@ -0,0 +1,22 @@
<template>
<Head>
<Title>Editeur</Title>
</Head>
<Editor v-model="model" />
</template>
<script setup lang="ts">
definePageMeta({
rights: ['admin']
})
const model = defineModel<string>({
default: `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam quis orci et est malesuada vulputate. Aenean sagittis congue eros, non feugiat metus bibendum consectetur. Duis volutpat leo nisi, in maximus nulla rhoncus ac. Sed scelerisque ipsum et volutpat dignissim. Integer massa nibh, imperdiet quis condimentum vitae, imperdiet quis quam. Cras pretium ex eget hendrerit porttitor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque rutrum scelerisque quam, sit amet malesuada mi convallis aliquam. Curabitur eget dolor in diam scelerisque tincidunt at et sapien. Nulla vel nisl finibus odio porttitor sagittis ac ut sem. Aenean orci enim, fringilla eu porta eget, egestas vel libero. Aenean ac efficitur nunc, id finibus nibh. Suspendisse potenti. Quisque vel vestibulum ante. Morbi mi nulla, gravida ac malesuada at, hendrerit nec nibh.
Fusce sodales convallis velit, ac tempor sem auctor sed.Aenean commodo sodales lorem eu mollis.Suspendisse lectus diam, bibendum quis maximus id, euismod placerat velit.Vestibulum hendrerit justo vel ultricies molestie.Donec rhoncus, ante at facilisis fermentum, diam diam hendrerit nunc, et dapibus lacus leo in massa.Duis iaculis sem sed molestie posuere.Morbi a erat hendrerit, volutpat libero non, elementum dui.
Cras imperdiet velit cursus, fringilla tellus eu, lacinia neque.Sed id est suscipit quam gravida vestibulum ut sed tortor.Aliquam erat volutpat.Praesent non orci ac quam consequat tempor.Nulla facilisi.Proin at vulputate lectus.Nunc at tellus at diam faucibus eleifend et et diam.Duis pellentesque lobortis lectus id egestas.Sed quis lacinia sapien.Quisque porta tincidunt pulvinar.Aliquam hendrerit hendrerit quam, sed pulvinar turpis dictum nec.
Donec bibendum, orci nec tempus fermentum, diam tellus pretium elit, vel porttitor ligula lectus a augue.Aliquam tristique, mi eu mollis sodales, enim lorem hendrerit est, id semper dui tellus id felis.Duis finibus lacus nunc, vitae tincidunt metus sagittis at.Curabitur euismod neque sed malesuada consectetur.Aliquam eget efficitur urna.Sed neque sem, interdum in turpis vitae, efficitur aliquam neque.Integer consectetur consequat diam, sed suscipit arcu maximus ac.Nunc imperdiet leo condimentum tellus luctus porta.Aenean et lorem sit amet eros rutrum fermentum.
Nam placerat leo sed nulla imperdiet dapibus.Etiam vitae tortor efficitur, interdum ipsum non, tincidunt ante.Quisque et placerat nisi, eu bibendum neque.Nulla facilisi.Pellentesque accumsan lacus arcu, vitae iaculis elit sollicitudin quis.Sed et iaculis neque.In quis nunc laoreet turpis fermentum sodales.Etiam eget sodales lorem.Nunc id risus ac purus mollis auctor.Integer imperdiet placerat massa eu efficitur.` });
</script>

View File

@@ -1,3 +1,29 @@
<template>
Current path: {{ $route.params.path }}
</template>
<div v-if="status === 'pending'" class="flex"><Loading /></div>
<div class="flex flex-1 justify-start items-start" v-if="page">
<template v-if="page.type === 'markdown'">
<div class="flex flex-1 justify-start items-start flex-col px-24 py-6">
<div class="flex flex-1 flex-row justify-between items-center">
<ProseH1>{{ page.title }}</ProseH1>
<Button v-if="isOwner"><NuxtLink :href="{ name: 'explore-edit-path', params: { path: path } }">Modifier</NuxtLink></Button>
</div>
<Markdown :content="page.content" />
</div>
</template>
<template v-else>
<span>Contenu non traitable</span>
</template>
</div>
<div v-else-if="status === 'pending'"><Loading /></div>
<div v-else-if="status === 'error'">{{ error?.message }}</div>
<div v-else>Impossible de retrouver le contenu demandé</div>
</template>
<script setup lang="ts">
const route = useRouter().currentRoute;
const path = computed(() => Array.isArray(route.value.params.path) ? route.value.params.path[0] : route.value.params.path);
const { loggedIn, user } = useUserSession();
const { data: page, status, error } = await useFetch(`/api/file/${encodeURIComponent(path.value)}`, { watch: [route, path], });
const isOwner = computed(() => user.value?.id === page.value?.owner);
</script>

View File

@@ -0,0 +1,75 @@
<template>
<div v-if="page" class="p-12 flex flex-1 flex-col items-start justify-start">
<Head>
<Title>Modification de {{ page.title }}</Title>
</Head>
<div class="flex justify-between items-center w-full px-4 max-h-full">
<div class="flex gap-16 items-center justify-start">
<input type="text" v-model="page.title" placeholder="Titre" class="mx-4 h-16 caret-light-50 dark:caret-dark-50 text-light-100 dark:text-dark-100 placeholder:text-light-50 dark:placeholder:text-dark-50 appearance-none outline-none px-3 py-1 text-5xl font-thin bg-transparent" />
</div>
<div class="flex gap-4">
<Tooltip message="Consultable uniquement par le propriétaire" side="bottom"><Switch label="Privé" v-model="page.private" /></Tooltip>
<Tooltip message="Afficher dans le menu de navigation" side="bottom"><Switch label="Navigable" v-model="page.navigable" /></Tooltip>
<Button @click="() => save()" :loading="saveStatus === 'pending'" class="border-light-blue dark:border-dark-blue hover:border-light-blue dark:hover:border-dark-blue focus:shadow-light-blue dark:focus:shadow-dark-blue">Enregistrer</Button>
</div>
</div>
<div class="border-t border-light-35 dark:border-dark-35 mt-4 flex-1 w-full max-h-full flex pt-4">
<template v-if="page.type === 'markdown'">
<textarea v-model="page.content" class="flex-1 bg-transparent appearance-none outline-none max-h-full resize-none"></textarea>
<div class="flex-1 border-s border-light-35 dark:border-dark-35 ms-4 pt-4 ps-4 max-h-full">
<Markdown class="max-h-full overflow-auto" :content="page.content" />
</div>
</template>
<template v-else-if="page.type === 'canvas'">
<span class="flex-1 items-center"><ProseH1>Editeur de graphe en cours de développement</ProseH1></span>
</template>
<template v-else-if="page.type === 'file'">
<span>Modifier le contenu :</span><input type="file" @change="(e) => console.log(e)" />
</template>
</div>
</div>
<div v-else-if="status === 'pending'" class="flex">
<Head>
<Title>Chargement</Title>
</Head>
<Loading />
</div>
<div v-else-if="status === 'error'">{{ error?.message }}</div>
</template>
<script setup lang="ts">
const route = useRouter().currentRoute;
const path = computed(() => Array.isArray(route.value.params.path) ? route.value.params.path[0] : route.value.params.path);
const toaster = useToast();
const saveStatus = ref<'idle' | 'pending' | 'success' | 'error'>('idle');
const { data: page, status, error } = await useLazyFetch(`/api/file/${encodeURIComponent(path.value)}`, { watch: [ route, path ]});
async function save(): Promise<void>
{
saveStatus.value = 'pending';
try {
await $fetch(`/api/file`, {
method: 'post',
body: page.value,
headers: {
'Content-Type': 'application/json',
},
});
saveStatus.value = 'success';
toaster.clear('error');
toaster.add({
type: 'success', content: 'Contenu enregistré', timer: true, duration: 10000
});
useRouter().push({ name: 'explore-path', params: { path: path.value } });
} catch(e: any) {
toaster.add({
type: 'error', content: e.message, timer: true, duration: 10000
})
saveStatus.value = 'error';
}
}
</script>

View File

@@ -1,3 +1,33 @@
<template>
<div class="flex flex-col max-w-[1200px] p-16">
<ProseH3>Mentions Légales</ProseH3>
<ProseH4>Collecte et Traitement des Données Personnelles</ProseH4>
Ce site collecte des données personnelles durant l'inscription et des données anonymes durant la navigation sur
le site dans un but de collecte statistiques.<br />
Conformément à la réglementation en vigueur, vous disposez d'un droit d'accès, de rectification et de
suppression de vos données personnelles. <br />
Pour exercer ces droits, vous pouvez vous rendre dans votre profil et selectionner l'option "Supprimer mon
compte" qui garanti une suppression de l'intégralité de vos données personnelles.
<ProseH4>Utilisation des Cookies</ProseH4>
Ce site utilise des cookies uniquement pour maintenir la connexion des utilisateurs et faciliter leur navigation
lors de chaque visite. Aucune information de suivi ou de profilage n'est réalisée. Ces cookies sont essentiels
au fonctionnement du site et ne nécessitent pas de consentement préalable. <br />
Vous pouvez gérer les cookies en configurant les paramètres de votre navigateur, mais la désactivation de ces
cookies pourrait affecter votre expérience de navigation.
<ProseH4>Limitation de Responsabilité</ProseH4>
Les informations publiées sur ce site sont fournies à titre indicatif et peuvent contenir des erreurs. <br />
L'éditeur décline toute responsabilité quant à l'usage qui pourrait être fait de ces informations.
<ProseH4>Propriété Intellectuelle</ProseH4>
Tous les contenus présents sur ce site (textes, images, logos, etc.) sont protégés par les lois en vigueur
sur la propriété intellectuelle. Toute reproduction ou utilisation de ces contenus sans autorisation préalable
est interdite. <br /><br />
<span class="text-light-60 dark:text-dark-60 text-lg">© Copyright Peaceultime - 2024</span>
</div>
</template>

View File

@@ -3,28 +3,63 @@ definePageMeta({
guestsGoesTo: '/user/login',
})
let { user, clear } = useUserSession();
const deleting = ref(false);
</script>
<template>
<Head>
<Title>Mon profil</Title>
</Head>
<div class="grid grid-cols-4 w-full items-start py-8 gap-6 content-start" v-if="user">
<div class="flex gap-4 col-span-3 border border-light-35 dark:border-dark-35 p-4">
<Avatar icon="radix-icons:person" :src="`/users/${user?.id}.medium.jpg`" class="w-32 h-32" />
<div class="flex flex-col items-start">
<ProseH5>{{ user.username }}</ProseH5>
<ProseH5>{{ user.email }}</ProseH5>
<div class="grid lg:grid-cols-4 grid-col-2 w-full items-start py-8 gap-6 content-start" v-if="user">
<div class="flex flex-col gap-4 col-span-4 lg:col-span-3 border border-light-35 dark:border-dark-35 p-4">
<div class="flex gap-4">
<Avatar icon="radix-icons:person" :src="`/users/${user?.id}.medium.jpg`" class="w-32 h-32" />
<div class="flex flex-col items-start">
<ProseH5>{{ user.username }}</ProseH5>
<ProseH5>{{ user.email }}</ProseH5>
</div>
</div>
<div class="border-light-red dark:border-dark-red bg-light-redBack dark:bg-dark-redBack text-light-red dark:text-dark-red py-1 px-3 flex items-center justify-between flex-col md:flex-row"
v-if="user.state === 0">
<HoverCard>
<template v-slot:default><div class="border-light-red dark:border-dark-red bg-light-red bg-opacity-25 dark:bg-dark-red dark:bg-opacity-25 text-light-red dark:text-dark-red py-1 px-3" v-if="user.state === 0">Votre adresse mail n'as pas encore été validée. <Button class="ms-4">Renvoyez un mail</Button></div></template>
<template v-slot:content><span>Tant que votre adresse mail n'as pas été validée, vous n'avez que des droits de lecture.</span></template>
<template v-slot:default>Votre adresse mail n'as pas encore été validée. </template>
<template v-slot:content><span>Tant que votre adresse mail n'as pas été validée, vous n'avez que
des droits de lecture.</span></template>
</HoverCard>
<Button class="ms-4">Renvoyez un mail</Button>
</div>
</div>
<div class="flex-1">
<div class="flex flex-col self-center flex-1 gap-4">
<Button @click="async () => await clear()">Se deconnecter</Button>
<Button>Modifier mon profil</Button>
<AlertDialogRoot v-model="deleting">
<AlertDialogTrigger asChild><Button
class="border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red hover:bg-light-redBack dark:hover:bg-dark-redBack text-light-red dark:text-dark-red focus:shadow-light-red dark:focus:shadow-dark-red">Supprimer
mon compte</Button></AlertDialogTrigger>
<AlertDialogPortal>
<AlertDialogOverlay class="bg-light-0 dark:bg-dark-0 opacity-70 fixed inset-0 z-40" />
<AlertDialogContent
class="data-[state=open]:animate-contentShow fixed top-[50%] left-[50%] max-h-[85vh] w-[90vw] max-w-[800px] translate-x-[-50%] translate-y-[-50%] bg-light-10 dark:bg-dark-10 border border-light-30 dark:border-dark-30 p-6 z-50 text-light-100 dark:text-dark-100">
<AlertDialogTitle class="text-3xl font-light relative -top-2">Suppression de compte
</AlertDialogTitle>
<AlertDialogDescription class="text-base pb-2">Supprimer votre compte va supprimer toutes vos
données personnelles de notre base de données, incluant vos commentaires et vos
contributions. <br />
Êtes vous sûr de vouloir supprimer votre compte ?
</AlertDialogDescription>
<div class="flex flex-1 justify-end gap-4">
<Button @click="() => { deleting = false; }" class="">Annuler</Button>
<Button @click="() => { deleting = false; }"
class="border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red hover:bg-light-redBack dark:hover:bg-dark-redBack text-light-red dark:text-dark-red focus:shadow-light-red dark:focus:shadow-dark-red">Supprimer</Button>
</div>
</AlertDialogContent>
</AlertDialogPortal>
</AlertDialogRoot>
<Button v-if="hasPermissions(user.permissions, ['admin'])"><NuxtLink :href="{ name: 'admin' }">Administration</NuxtLink></Button>
</div>
<div class="flex">
<div class="flex" v-if="user.permissions">
<ProseTable class="!m-0">
<ProseThead>
<ProseTr>