Users pages CSS + PreviewContent for Canvas + Tags CSS

This commit is contained in:
Peaceultime 2024-09-02 15:59:15 +02:00
parent e757682ed1
commit 35d356ae22
17 changed files with 90 additions and 64 deletions

View File

@ -9,7 +9,7 @@
</style> </style>
<template> <template>
<div class="editor" contenteditable> <div class="editor">
<template <template
v-if="model && model.length > 0"> v-if="model && model.length > 0">
<MarkdownRenderer <MarkdownRenderer

View File

@ -13,10 +13,11 @@ watchEffect(() => err.value = props.error);
</script> </script>
<template> <template>
<div class="input-group"> <template></template>
<label v-if="title" class="input-label">{{ title }}</label> <div class="m-1">
<input @input="err = false" class="input-input" :class="{ 'input-has-error': !!err }" v-model="model" <label v-if="title" class="pe-4">{{ title }}</label>
<input @input="err = false" class="caret-light-50 dark:caret-dark-50 text-light-100 dark:text-dark-100 placeholder:text-light-50 dark:placeholder:text-dark-50 bg-light-20 dark:bg-dark-20 appearance-none outline-none px-3 py-1 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40 border border-light-35 dark:border-dark-35" :class="{ 'input-has-error': !!err }" v-model="model"
v-bind="$attrs" /> v-bind="$attrs" />
<span v-if="err && typeof err === 'string'" class="input-error">{{ err }}</span> <span v-if="err && typeof err === 'string'" class="text-light-red dark:text-dark-red block pb-2">{{ err }}</span>
</div> </div>
</template> </template>

View File

@ -1,6 +1,6 @@
<template> <template>
<Teleport to="#teleports" v-if="display && (!fetched || loaded)"> <Teleport to="#teleports" v-if="display && (!fetched || loaded)">
<div class="absolute border-2 border-light-35 dark:border-dark-35 max-w-[550px] max-h-[450px] overflow-auto bg-light-0 dark:bg-dark-0 text-light-100 dark:text-dark-100" :class="{'is-loaded': fetched}" :style="pos" <div class="absolute border-2 border-light-35 dark:border-dark-35 max-w-[550px] max-h-[450px] bg-light-0 dark:bg-dark-0 text-light-100 dark:text-dark-100" :class="[{'is-loaded': fetched}, type === 'Markdown' ? 'overflow-auto' : 'overflow-hidden']" :style="pos"
@mouseenter="debounce(show, 250)" @mouseleave="debounce(() => display = false, 250)"> @mouseenter="debounce(show, 250)" @mouseleave="debounce(() => display = false, 250)">
<div v-if="pending" class="loading"></div> <div v-if="pending" class="loading"></div>
<template v-else-if="content !==''"> <template v-else-if="content !==''">
@ -8,7 +8,7 @@
<ProseH1>{{ title }}</ProseH1> <ProseH1>{{ title }}</ProseH1>
<Markdown v-model="content"></Markdown> <Markdown v-model="content"></Markdown>
</div> </div>
<div v-else-if="type === 'Canvas'" class=""> <div v-else-if="type === 'Canvas'" class="w-[550px] h-[450px] overflow-hidden">
<CanvasRenderer :canvas="JSON.parse(content) " /> <CanvasRenderer :canvas="JSON.parse(content) " />
</div> </div>
<div class="h-100 w-100 flex flex-1 flex-col justify-center items-center" v-else> <div class="h-100 w-100 flex flex-1 flex-col justify-center items-center" v-else>

View File

@ -44,3 +44,22 @@ const { data, status } = await useFetch(`/api/project/${project.value}/file`, {
dedupe: 'defer' dedupe: 'defer'
}); });
</script> </script>
<style>
.tag
{
@apply bg-accent-blue;
@apply bg-opacity-10;
@apply hover:bg-opacity-20;
@apply text-accent-blue;
@apply text-sm;
@apply px-1;
@apply ms-1;
@apply py-1;
@apply rounded-full;
@apply rounded-se-none;
@apply border;
@apply border-accent-blue;
@apply border-opacity-30;
}
</style>

View File

@ -1,11 +1,9 @@
<template> <template>
<h1 :id="id" class="text-3xl font-semibold my-3 first:pt-0 pt-2"> <h1 :id="parseId(id)" class="text-3xl font-semibold my-3 first:pt-0 pt-2">
<slot /> <slot />
</h1> </h1>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const props = defineProps<{ id?: string }>() const props = defineProps<{ id?: string }>()
const generate = computed(() => props.id)
</script> </script>

View File

@ -1,5 +1,5 @@
<template> <template>
<h2 :id="id" class="text-2xl font-semibold my-3 ms-1 first:pt-0 pt-2"> <h2 :id="parseId(id)" class="text-2xl font-semibold my-3 ms-1 first:pt-0 pt-2">
<slot /> <slot />
</h2> </h2>
</template> </template>

View File

@ -1,5 +1,5 @@
<template> <template>
<h3 :id="id" class="text-xl font-semibold my-2 ms-4"> <h3 :id="parseId(id)" class="text-xl font-semibold my-2 ms-4">
<slot /> <slot />
</h3> </h3>
</template> </template>

View File

@ -1,5 +1,5 @@
<template> <template>
<h4 :id="id" class="text-lg font-semibold my-2 ms-4"> <h4 :id="parseId(id)" class="text-lg font-semibold my-2 ms-4">
<slot /> <slot />
</h4> </h4>
</template> </template>

View File

@ -1,5 +1,5 @@
<template> <template>
<h5 :id="id" class="text-base font-semibold my-1 ms-4"> <h5 :id="parseId(id)" class="text-base font-semibold my-1 ms-4">
<slot /> <slot />
</h5> </h5>
</template> </template>

View File

@ -1,5 +1,5 @@
<template> <template>
<h6 :id="id"> <h6 :id="parseId(id)">
<slot /> <slot />
</h6> </h6>
</template> </template>

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="bg-light-0 my-8 dark:bg-dark-0 px-6 py-3 top-0 z-40 w-96 lg:z-50 border-r border-light-30 dark:border-dark-30 flex flex-col justify-between"> <div class="bg-light-0 my-8 py-3 dark:bg-dark-0 top-0 z-40 w-96 lg:z-50 border-r border-light-30 dark:border-dark-30 flex flex-col justify-between">
<div class="relative bottom-6 flex flex-col gap-4 overflow-auto"> <div class="relative bottom-6 flex flex-1 flex-col gap-4 px-6">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<NuxtLink class=" text-light-100 dark:text-dark-100 hover:text-opacity-70" aria-label="Accueil" :to="{ path: '/', force: true }"><ThemeIcon class="inline" icon="logo" :width=56 :height=56 /></NuxtLink> <NuxtLink class=" text-light-100 dark:text-dark-100 hover:text-opacity-70" aria-label="Accueil" :to="{ path: '/', force: true }"><ThemeIcon class="inline" icon="logo" :width=56 :height=56 /></NuxtLink>
<div class="flex gap-4 items-center"> <div class="flex gap-4 items-center">
@ -8,9 +8,9 @@
<NuxtLink class="" :to="{ path: '/user/profile', force: true }"><ThemeIcon icon="user" :width=32 :height=32 /></NuxtLink> <NuxtLink class="" :to="{ path: '/user/profile', force: true }"><ThemeIcon icon="user" :width=32 :height=32 /></NuxtLink>
</div> </div>
</div> </div>
<div class="flex-auto"><SearchView /></div> <div class="flex"><SearchView /></div>
<NuxtLink class="px-6 text-lg flex items-center font-semibold text-light-100 dark:text-dark-100 hover:text-opacity-70" aria-label="Projets" :to="{ path: `/explorer`, force: true }">Projets</NuxtLink> <NuxtLink class="px-6 text-lg tracking-wider flex items-center font-semibold text-light-100 dark:text-dark-100 hover:text-opacity-70" aria-label="Projets" :to="{ path: `/explorer`, force: true }">Projets</NuxtLink>
<NuxtLink class="px-6 text-lg flex items-center font-semibold text-light-100 dark:text-dark-100 hover:text-opacity-70" aria-label="Editeur" :to="{ path: '/editing', force: true }">Editeur</NuxtLink> <NuxtLink class="px-6 text-lg tracking-wider flex items-center font-semibold text-light-100 dark:text-dark-100 hover:text-opacity-70" aria-label="Editeur" :to="{ path: '/editing', force: true }">Editeur</NuxtLink>
</div> </div>
<div class="text-center text-sm text-light-70 dark:text-dark-70"> <div class="text-center text-sm text-light-70 dark:text-dark-70">
<NuxtLink class="hover:underline italic" :to="{ path: '/third-party', force: true }">Mentions légales</NuxtLink> <NuxtLink class="hover:underline italic" :to="{ path: '/third-party', force: true }">Mentions légales</NuxtLink>

View File

@ -17,14 +17,17 @@
<Head> <Head>
<Title>Live Editing</Title> <Title>Live Editing</Title>
</Head> </Head>
<div class="editor-container"> <div class="flex flex-1 flex-col justify-center items-center">
<Suspense> <h1 class="block flex-1 text-3xl">En cours de développement</h1>
<template #fallback> <div class="flex flex-1">
<div class="loading"></div> <Suspense>
</template> <template #fallback>
<EditableMarkdown class="editor-preview" v-if="input.length > 0" v-model="input"></EditableMarkdown> <div class="loading"></div>
</Suspense> </template>
<textarea class="editor" v-model="input"></textarea> <EditableMarkdown class="editor-preview" v-if="input.length > 0" v-model="input"></EditableMarkdown>
</Suspense>
<textarea class="caret-light-50 dark:caret-dark-50 text-light-100 dark:text-dark-100 placeholder:text-light-50 dark:placeholder:text-dark-50 bg-light-20 dark:bg-dark-20 appearance-none w-1/2 outline-none px-3 py-1 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40 border border-light-35 dark:border-dark-35" v-model="input"></textarea>
</div>
</div> </div>
</template> </template>

View File

@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { hydrate } from 'vue';
import { ZodError } from 'zod'; import { ZodError } from 'zod';
import { schema, type Login } from '~/schemas/login'; import { schema, type Login } from '~/schemas/login';
@ -77,21 +76,21 @@ function handleErrors(error: Error | ZodError)
<Head> <Head>
<Title>Se connecter</Title> <Title>Se connecter</Title>
</Head> </Head>
<div class="site-body-center-column"> <div class="flex flex-1 justify-center items-center">
<div class="render-container flex align-center justify-center"> <div class="p-8 w-[48em] border border-light-35 dark:border-dark-35">
<form @submit.prevent="submit" class="input-form input-form-wide"> <form @submit.prevent="submit" class="p-4 bg-light-25 dark:bg-dark-30">
<h1>Connexion</h1> <h1 class="text-2xl font-bold tracking-wider pb-4">Connexion</h1>
<Input type="text" autocomplete="username" v-model="state.usernameOrEmail" <Input type="text" autocomplete="username" v-model="state.usernameOrEmail"
placeholder="" title="Nom d'utilisateur ou adresse mail" :error="usernameError" /> placeholder="" title="Nom d'utilisateur ou adresse mail" :error="usernameError" class="w-[24em]" />
<Input type="password" autocomplete="current-password" v-model="state.password" <Input type="password" autocomplete="current-password" v-model="state.password"
placeholder="" title="Mot de passe" placeholder="" title="Mot de passe"
:error="passwordError" /> :error="passwordError" class="w-[24em]"/>
<span v-if="generalError" class="input-error">{{ generalError }}</span> <span v-if="generalError" class="text-light-red dark:text-dark-red">{{ generalError }}</span>
<button> <button class="m-auto block px-4 py-1 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40 hover:border-light-50 dark:hover:border-dark-50 active:relative active:top-[1px]">
<div class="loading" v-if="status === 'pending'"></div> <div class="loading" v-if="status === 'pending'"></div>
<template v-else>Se connecter</template> <template v-else>Se connecter</template>
</button> </button>
<NuxtLink :to="{ path: `/user/register`, force: true }">Pas de compte ?</NuxtLink> <NuxtLink class="mt-4 text-center block text-sm font-semibold tracking-wide hover:italic" :to="{ path: `/user/register`, force: true }">Pas de compte ?</NuxtLink>
</form> </form>
</div> </div>
</div> </div>

View File

@ -10,13 +10,9 @@ const { user, clear } = useUserSession();
<Head> <Head>
<Title>Votre profil</Title> <Title>Votre profil</Title>
</Head> </Head>
<div class="site-body-center-column"> <div class="flex-1 flex justify-center items-center flex-col">
<div class="render-container"> <ThemeIcon icon="logo" :width=128 :height=128 />
<div class="not-found-container"> <div class="not-found-title">Bonjour {{ user?.username }} :)</div>
<ThemeIcon icon="logo" :width=128 :height=128 /> <button class="m-auto block px-4 py-1 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40 hover:border-light-50 dark:hover:border-dark-50 active:relative active:top-[1px]" @click="clear">Se deconnecter</button>
<div class="not-found-title">Bonjour {{ user?.username }} :)</div>
<button @click="clear">Se deconnecter</button>
</div>
</div>
</div> </div>
</template> </template>

View File

@ -85,27 +85,27 @@ function handleErrors(error: Error | ZodError)
<Head> <Head>
<Title>S'inscrire</Title> <Title>S'inscrire</Title>
</Head> </Head>
<div class="site-body-center-column"> <div class="flex flex-1 justify-center items-center">
<div class="render-container flex align-center justify-center"> <div class="p-8 w-[48em] border border-light-35 dark:border-dark-35">
<form @submit.prevent="submit" class="input-form input-form-wide"> <form @submit.prevent="submit" class="p-4 bg-light-25 dark:bg-dark-30">
<h1>Inscription</h1> <h1 class="text-2xl font-bold tracking-wider pb-4">Inscription</h1>
<Input type="text" autocomplete="username" v-model="state.username" <Input type="text" autocomplete="username" v-model="state.username"
placeholder="Entrez un nom d'utiliateur" title="Nom d'utilisateur" :error="usernameError" /> placeholder="Entrez un nom d'utilisateur" title="Nom d'utilisateur" :error="usernameError" class="w-[24em]"/>
<Input type="text" autocomplete="email" v-model="state.email" placeholder="Entrez une addresse mail" <Input type="text" autocomplete="email" v-model="state.email" placeholder="Entrez une addresse mail"
title="Adresse mail" :error="emailError" /> title="Adresse mail" :error="emailError" class="w-[24em]"/>
<Input type="password" autocomplete="new-password" v-model="state.password" <Input type="password" autocomplete="new-password" v-model="state.password"
placeholder="Entrez un mot de passe" title="Mot de passe" placeholder="Entrez un mot de passe" title="Mot de passe"
:error="!(checkedLength && checkedLowerUpper && checkedDigit && checkedSymbol)" /> :error="!(checkedLength && checkedLowerUpper && checkedDigit && checkedSymbol)" class="w-[24em]"/>
<div class="password-validation-group"> <div class="flex flex-col font-light">
<span class="password-validation-title">Votre mot de passe doit respecter les critères suivants <span class="">Votre mot de passe doit respecter les critères de sécurité suivants
:</span> :</span>
<span class="password-validation-item" :class="{'validation-error': !checkedLength}">Entre 8 et 128 <span class="px-4" :class="{'text-light-red dark:text-dark-red': !checkedLength}">Entre 8 et 128
caractères</span> caractères</span>
<span class="password-validation-item" :class="{'validation-error': !checkedLowerUpper}">Au moins <span class="px-4" :class="{'text-light-red dark:text-dark-red': !checkedLowerUpper}">Au moins
une minuscule et une majuscule</span> une minuscule et une majuscule</span>
<span class="password-validation-item" :class="{'validation-error': !checkedDigit}">Au moins un <span class="px-4" :class="{'text-light-red dark:text-dark-red': !checkedDigit}">Au moins un
chiffre</span> chiffre</span>
<span class="password-validation-item" :class="{'validation-error': !checkedSymbol}">Au moins un <span class="px-4" :class="{'text-light-red dark:text-dark-red': !checkedSymbol}">Au moins un
caractère spécial parmis la liste suivante: caractère spécial parmis la liste suivante:
<pre>! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~</pre> <pre>! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~</pre>
</span> </span>
@ -113,10 +113,10 @@ function handleErrors(error: Error | ZodError)
<Input type="password" v-model="confirmPassword" placeholder="Confirmer le mot de passe" <Input type="password" v-model="confirmPassword" placeholder="Confirmer le mot de passe"
title="Confirmer le mot de passe" title="Confirmer le mot de passe"
autocomplete="new-password" autocomplete="new-password"
:error="confirmPassword === '' || confirmPassword === state.password ? '' : 'Les mots de passe saisies ne sont pas identique'" /> :error="confirmPassword === '' || confirmPassword === state.password ? '' : 'Les mots de passe saisies ne sont pas identique'" class="w-[24em]"/>
<span v-if="generalError" class="input-error">{{ generalError }}</span> <span v-if="generalError" class="text-light-red dark:text-dark-red">{{ generalError }}</span>
<button><div v-if="status === 'pending'" class="loading"></div><template v-else>S'inscrire</template></button> <button class="m-auto block px-4 py-1 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40 hover:border-light-50 dark:hover:border-dark-50 active:relative active:top-[1px]"><div v-if="status === 'pending'" class="loading"></div><template v-else>S'inscrire</template></button>
<NuxtLink :to="{ path: `/user/login`, force: true }">Se connecter</NuxtLink> <NuxtLink class="mt-4 text-center block text-sm font-semibold tracking-wide hover:italic" :to="{ path: `/user/login`, force: true }">Vous avez déjà un compte ? Se connecter</NuxtLink>
</form> </form>
</div> </div>
</div> </div>

6
pages/users/[id].vue Normal file
View File

@ -0,0 +1,6 @@
<template>
<Head>
<Title>Inconnu</Title>
</Head>
<div>TODO :)</div>
</template>

View File

@ -2,3 +2,7 @@ export function unifySlug(slug: string | string[]): string
{ {
return (Array.isArray(slug) ? slug.join('/') : slug); return (Array.isArray(slug) ? slug.join('/') : slug);
} }
export function parseId(id: string | undefined): string |undefined
{
return id?.normalize('NFD')?.replace(/[\u0300-\u036f]/g, '')?.replace(/^\d\. */g, '')?.replace(/\s/g, "-")?.replace(/%/g, "-percent")?.replace(/\?/g, "-q")?.toLowerCase();
}