Components rework and lots of fixes
This commit is contained in:
parent
094f27d9ad
commit
d7a8087c6c
|
|
@ -31,8 +31,8 @@ const colors = computed(() => {
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="absolute" :style="{transform: `translate(${node.x}px, ${node.y}px)`, width: `${node.width}px`, height: `${node.height}px`, '--canvas-color': node.color?.hex}">
|
<div class="absolute" :style="{transform: `translate(${node.x}px, ${node.y}px)`, width: `${node.width}px`, height: `${node.height}px`, '--canvas-color': node.color?.hex}" :class="{'-z-10': node.type === 'group', 'z-10': node.type !== 'group'}">
|
||||||
<div :class="[{'-z-10': node.type === 'group', 'z-10': node.type !== 'group'}, colors.border]" class="border-2 bg-light-20 dark:bg-dark-20 overflow-hidden contain-strict w-full h-full flex">
|
<div :class="[colors.border]" class="border-2 bg-light-20 dark:bg-dark-20 overflow-hidden contain-strict w-full h-full flex">
|
||||||
<div class="w-full h-full py-2 px-4 flex !bg-opacity-[0.07]" :class="colors.bg">
|
<div class="w-full h-full py-2 px-4 flex !bg-opacity-[0.07]" :class="colors.bg">
|
||||||
<template v-if="node.type === 'group' || zoom > Math.min(0.4, 1000 / size)">
|
<template v-if="node.type === 'group' || zoom > Math.min(0.4, 1000 / size)">
|
||||||
<div v-if="node.text?.length > 0" class="flex items-center">
|
<div v-if="node.text?.length > 0" class="flex items-center">
|
||||||
|
|
|
||||||
|
|
@ -18,23 +18,13 @@ const emit = defineEmits(['navigate']);
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="py-1" v-if="project && !isNaN(project)">
|
<div class="py-1" v-if="project && !isNaN(project)">
|
||||||
<template v-if="hasChildren">
|
<Accordion v-if="hasChildren" v-model="collapsed" class="data-[type]:after:content-[attr(data-type)] data-[type]:after:border data-[type]:after:border-light-35 data-[type]:dark:after:border-dark-35
|
||||||
<div class="flex gap-2 items-center cursor-pointer hover:text-opacity-75 text-light-100 dark:text-dark-100
|
data-[type]:after:text-sm data-[type]:after:px-1 data-[type]:after:bg-light-20 data-[type]:dark:after:bg-dark-20 data-[type]:after:[font-variant:all-petite-caps]">
|
||||||
data-[type]:after:content-[attr(data-type)] data-[type]:after:border data-[type]:after:border-light-35 data-[type]:dark:after:border-dark-35
|
<template #header>{{ link.title }}</template>
|
||||||
data-[type]:after:text-sm data-[type]:after:px-1 data-[type]:after:bg-light-20 data-[type]:dark:after:bg-dark-20 data-[type]:after:[font-variant:all-petite-caps]"
|
<template #content >
|
||||||
@click="collapsed = hasChildren && !collapsed" :data-type="link.private ? 'privé' : undefined">
|
|
||||||
<div v-if="hasChildren" class="w-4 h-4 transition-transform" :class="{'-rotate-90': collapsed}">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
|
||||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M3 8L12 17L21 8"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="font-semibold xl:text-base text-sm flex-1">{{ link.title }}</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="!collapsed">
|
|
||||||
<NavigationLink @navigate="e => emit('navigate')" class="border-light-40 dark:border-dark-40 ms-2 ps-4 border-l" v-if="hasChildren" v-for="l of link.children" :link="l" :project="project" />
|
<NavigationLink @navigate="e => emit('navigate')" class="border-light-40 dark:border-dark-40 ms-2 ps-4 border-l" v-if="hasChildren" v-for="l of link.children" :link="l" :project="project" />
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
</Accordion>
|
||||||
<NuxtLink v-else @click="e => emit('navigate')" class="text-light-100 dark:text-dark-100 cursor-pointer hover:text-opacity-75 xl:text-base text-sm flex justify-between items-center
|
<NuxtLink v-else @click="e => emit('navigate')" class="text-light-100 dark:text-dark-100 cursor-pointer hover:text-opacity-75 xl:text-base text-sm flex justify-between items-center
|
||||||
data-[type]:after:content-[attr(data-type)] data-[type]:after:border data-[type]:after:border-light-35 data-[type]:dark:after:border-dark-35
|
data-[type]:after:content-[attr(data-type)] data-[type]:after:border data-[type]:after:border-light-35 data-[type]:dark:after:border-dark-35
|
||||||
data-[type]:after:text-sm data-[type]:after:px-1 data-[type]:after:bg-light-20 data-[type]:dark:after:bg-dark-20 data-[type]:after:[font-variant:all-petite-caps]" :to="{ path: `/explorer/${project}/${link.path}`, force: true }"
|
data-[type]:after:text-sm data-[type]:after:px-1 data-[type]:after:bg-light-20 data-[type]:dark:after:bg-dark-20 data-[type]:after:[font-variant:all-petite-caps]" :to="{ path: `/explorer/${project}/${link.path}`, force: true }"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<Teleport to="#teleports" v-if="display && (!fetched || loaded)">
|
<HoverPopup @before-show="fetch" :class="[{'is-loaded': fetched}, file?.type === 'Markdown' ? 'overflow-auto' : 'overflow-hidden']">
|
||||||
<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}, file?.type === 'Markdown' ? 'overflow-auto' : 'overflow-hidden']" :style="pos"
|
<template #content>
|
||||||
@mouseenter="debounce(show, 250)" @mouseleave="debounce(() => display = false, 250)">
|
<Suspense>
|
||||||
<div v-if="pending" class="loading"></div>
|
<div v-if="pending" class="loading w-[550px] h-[450px]"></div>
|
||||||
<template v-else-if="!!file">
|
<template v-else-if="!!file">
|
||||||
<div v-if="file.type === 'Markdown'" class="p-6 ms-6">
|
<div v-if="file.type === 'Markdown'" class="p-6 ms-6">
|
||||||
<ProseH1>{{ file.title }}</ProseH1>
|
<ProseH1>{{ file.title }}</ProseH1>
|
||||||
|
|
@ -20,11 +20,10 @@
|
||||||
<div class="text-3xl font-extralight tracking-wide text-light-60 dark:text-dark-60">Impossible d'afficher</div>
|
<div class="text-3xl font-extralight tracking-wide text-light-60 dark:text-dark-60">Impossible d'afficher</div>
|
||||||
<div class="text-lg text-light-60 dark:text-dark-60">Cette page est impossible à traiter</div>
|
<div class="text-lg text-light-60 dark:text-dark-60">Cette page est impossible à traiter</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Suspense>
|
||||||
</Teleport>
|
</template>
|
||||||
<span ref="el" @mouseenter="debounce(show, 250)" @mouseleave="debounce(() => display = false, 250)">
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</span>
|
</HoverPopup>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
@ -45,12 +44,13 @@ const props = defineProps({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const file = ref<CommentedFile | null>();
|
const file = ref<CommentedFile | null>();
|
||||||
const display = ref(false), pending = ref(false), fetched = ref(false);
|
const pending = ref(false), fetched = ref(false);
|
||||||
const el = ref(), pos = ref<Record<string, string>>();
|
|
||||||
const loaded = computed(() => !pending.value && fetched.value && file.value !== undefined);
|
|
||||||
|
|
||||||
async function fetch()
|
async function fetch()
|
||||||
{
|
{
|
||||||
|
if(fetched.value)
|
||||||
|
return;
|
||||||
|
|
||||||
fetched.value = true;
|
fetched.value = true;
|
||||||
pending.value = true;
|
pending.value = true;
|
||||||
|
|
||||||
|
|
@ -59,37 +59,4 @@ async function fetch()
|
||||||
pending.value = false;
|
pending.value = false;
|
||||||
file.value = data.value;
|
file.value = data.value;
|
||||||
}
|
}
|
||||||
async function show()
|
|
||||||
{
|
|
||||||
if(display.value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(!fetched.value)
|
|
||||||
await fetch();
|
|
||||||
|
|
||||||
const rect = (el.value as HTMLDivElement)?.getBoundingClientRect();
|
|
||||||
|
|
||||||
if(!rect)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const r: Record<string, string> = {};
|
|
||||||
if (rect.bottom + 450 < window.innerHeight)
|
|
||||||
r.top = (rect.bottom + 4) + "px";
|
|
||||||
else
|
|
||||||
r.bottom = (window.innerHeight - rect.top + 4) + "px";
|
|
||||||
if (rect.right + 550 < window.innerWidth)
|
|
||||||
r.left = rect.left + "px";
|
|
||||||
else
|
|
||||||
r.right = (window.innerWidth - rect.right + 4) + "px";
|
|
||||||
|
|
||||||
pos.value = r;
|
|
||||||
display.value = true;
|
|
||||||
}
|
|
||||||
let debounceFn: () => void, timeout: NodeJS.Timeout;
|
|
||||||
function debounce(fn: () => void, ms: number)
|
|
||||||
{
|
|
||||||
debounceFn = fn;
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = setTimeout(debounceFn, ms);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<NuxtLink class="text-accent-blue" v-if="data && data[0] && status !== 'pending'"
|
<Suspense>
|
||||||
|
<NuxtLink no-prefetch class="text-accent-blue inline-flex items-center" v-if="data && data[0] && status !== 'pending'"
|
||||||
:to="{ path: `/explorer/${project}/${data[0].path}`, hash: hash }" :class="class">
|
:to="{ path: `/explorer/${project}/${data[0].path}`, hash: hash }" :class="class">
|
||||||
<PreviewContent :project="project" :path="data[0].path" :anchor="hash">
|
<PreviewContent :project="project" :path="data[0].path" :anchor="hash">
|
||||||
<div class="inline-flex items-center">
|
<div class="inline-flex items-center">
|
||||||
|
|
@ -9,12 +10,13 @@
|
||||||
</div>
|
</div>
|
||||||
</PreviewContent>
|
</PreviewContent>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink v-else-if="href" :to="href" :class="class" class="text-accent-blue inline-flex items-center">
|
<NuxtLink no-prefetch v-else-if="href" :to="href" :class="class" class="text-accent-blue inline-flex items-center">
|
||||||
<slot v-bind="$attrs"></slot>
|
<slot v-bind="$attrs"></slot>
|
||||||
<ThemeIcon class="w-4 h-4 inline-block" v-if="data && data[0] && data[0].type !== 'Markdown'" :height="20" :width="20"
|
<ThemeIcon class="w-4 h-4 inline-block" v-if="data && data[0] && data[0].type !== 'Markdown'" :height="20" :width="20"
|
||||||
:icon="`link-${data[0].type.toLowerCase()}`" />
|
:icon="`link-${data[0].type.toLowerCase()}`" />
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<slot :class="class" v-else v-bind="$attrs"></slot>
|
<slot :class="class" v-else v-bind="$attrs"></slot>
|
||||||
|
</Suspense>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
@ -31,9 +33,11 @@ const props = defineProps({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const nuxt = useNuxtApp();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const { hash, pathname, protocol } = parseURL(props.href);
|
const { hash, pathname, protocol } = parseURL(props.href);
|
||||||
const project = computed(() => parseInt(Array.isArray(route.params.projectId) ? '0' : route.params.projectId));
|
const project = computed(() => parseInt(Array.isArray(route.params.projectId) ? '0' : route.params.projectId));
|
||||||
|
const key = computed(() => `file:${project.value}:%${pathname}`);
|
||||||
|
|
||||||
const { data, status, execute } = await useFetch(`/api/project/${project.value}/file`, {
|
const { data, status, execute } = await useFetch(`/api/project/${project.value}/file`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|
@ -41,14 +45,19 @@ const { data, status, execute } = await useFetch(`/api/project/${project.value}/
|
||||||
search: `%${pathname}`
|
search: `%${pathname}`
|
||||||
},
|
},
|
||||||
transform: (data) => data?.map(e => ({ path: e.path, type: e.type })),
|
transform: (data) => data?.map(e => ({ path: e.path, type: e.type })),
|
||||||
key: `file:${project.value}:%${pathname}`,
|
key: key.value,
|
||||||
dedupe: 'defer',
|
|
||||||
ignoreResponseError: true,
|
ignoreResponseError: true,
|
||||||
immediate: false,
|
immediate: false,
|
||||||
|
dedupe: 'defer'
|
||||||
});
|
});
|
||||||
|
|
||||||
if(pathname && !protocol)
|
if(!!pathname && !protocol)
|
||||||
{
|
{
|
||||||
execute();
|
await execute();
|
||||||
|
if(data.value === null && status.value === 'idle')
|
||||||
|
{
|
||||||
|
data.value = nuxt.payload.data[key.value] ?? nuxt.static.data[key.value];
|
||||||
|
status.value = 'success';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -1,5 +1,44 @@
|
||||||
|
<template>
|
||||||
|
<HoverPopup @before-show="status === 'idle' && project && tag && execute()">
|
||||||
|
<template #content>
|
||||||
|
<Suspense>
|
||||||
|
<div v-if="status === 'pending'" class="loading w-[550px] h-[450px]"></div>
|
||||||
|
<template v-else-if="!!data">
|
||||||
|
<div v-if="data.description" class="p-6 ms-6">
|
||||||
|
<ProseH2>{{ data.tag }}</ProseH2>
|
||||||
|
<Markdown v-model="data.description"></Markdown>
|
||||||
|
</div>
|
||||||
|
<div class="h-100 w-100 flex flex-1 flex-col justify-center items-center" v-else>
|
||||||
|
<div class="text-3xl font-extralight tracking-wide text-light-60 dark:text-dark-60">Fichier vide</div>
|
||||||
|
<div class="text-lg text-light-60 dark:text-dark-60">Cette page est vide</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="h-100 w-100 flex flex-1 flex-col justify-center items-center" v-else>
|
||||||
|
<div class="text-3xl font-extralight tracking-wide text-light-60 dark:text-dark-60">Impossible d'afficher</div>
|
||||||
|
<div class="text-lg text-light-60 dark:text-dark-60">Cette page est impossible à traiter</div>
|
||||||
|
</div>
|
||||||
|
</Suspense>
|
||||||
|
</template>
|
||||||
<template>
|
<template>
|
||||||
<span class="before:content-['#'] cursor-default bg-accent-blue bg-opacity-10 hover:bg-opacity-20 text-accent-blue text-sm px-1 ms-1 pb-0.5 rounded-full rounded-se-none border border-accent-blue border-opacity-30">
|
<span class="before:content-['#'] cursor-default bg-accent-blue bg-opacity-10 hover:bg-opacity-20 text-accent-blue text-sm px-1 ms-1 pb-0.5 rounded-full rounded-se-none border border-accent-blue border-opacity-30">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
</HoverPopup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { tag } = defineProps({
|
||||||
|
tag: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const project = computed(() => parseInt(Array.isArray(route.params.projectId) ? '0' : route.params.projectId));
|
||||||
|
const { data, status, execute } = useFetch(`/api/project/${project.value}/tags/${tag}`, {
|
||||||
|
immediate: false,
|
||||||
|
key: `file:${project.value}:%${tag}`,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
const collapsed = defineModel<boolean>({
|
||||||
|
default: true,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div @click="collapsed = !collapsed" class="flex gap-2 items-center cursor-pointer hover:text-opacity-75 text-light-100 dark:text-dark-100" :class="$attrs.class">
|
||||||
|
<div class="w-4 h-4 transition-transform" :class="{'-rotate-90': collapsed}">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||||
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M3 8L12 17L21 8"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="font-semibold xl:text-base text-sm flex-1"><slot name="header"></slot></div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!collapsed">
|
||||||
|
<slot name="content"></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
{{beforeText}}<span class="highlight">{{matchedText}}</span>{{afterText}}
|
{{beforeText}}<span class="font-bold">{{matchedText}}</span>{{afterText}}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
<template>
|
||||||
|
<Teleport to="#teleports" v-if="display">
|
||||||
|
<div :class="$attrs.class" 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" :style="pos"
|
||||||
|
@mouseenter="debounce(show, 250)" @mouseleave="debounce(() => { emit('beforeHide'); display = false }, 250)">
|
||||||
|
<slot name="content"></slot>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
<span ref="el" @mouseenter="debounce(show, 250)" @mouseleave="debounce(() => { emit('beforeHide'); display = false }, 250)">
|
||||||
|
<slot></slot>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useTemplateRef } from 'vue';
|
||||||
|
|
||||||
|
const display = ref(false), fetched = ref(false);
|
||||||
|
const el = useTemplateRef('el'), pos = ref<Record<string, string>>();
|
||||||
|
const emit = defineEmits(['beforeShow', 'beforeHide']);
|
||||||
|
|
||||||
|
async function show()
|
||||||
|
{
|
||||||
|
if(display.value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
emit('beforeShow');
|
||||||
|
|
||||||
|
const rect = (el.value as HTMLDivElement)?.getBoundingClientRect();
|
||||||
|
|
||||||
|
if(!rect)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const r: Record<string, string> = {};
|
||||||
|
if (rect.bottom + 450 < window.innerHeight)
|
||||||
|
r.top = (rect.bottom + 4) + "px";
|
||||||
|
else
|
||||||
|
r.bottom = (window.innerHeight - rect.top + 4) + "px";
|
||||||
|
if (rect.right + 550 < window.innerWidth)
|
||||||
|
r.left = rect.left + "px";
|
||||||
|
else
|
||||||
|
r.right = (window.innerWidth - rect.right + 4) + "px";
|
||||||
|
|
||||||
|
pos.value = r;
|
||||||
|
display.value = true;
|
||||||
|
}
|
||||||
|
let debounceFn: () => void, timeout: NodeJS.Timeout;
|
||||||
|
function debounce(fn: () => void, ms: number)
|
||||||
|
{
|
||||||
|
debounceFn = fn;
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(debounceFn, ms);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -1,3 +1,9 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
const emit = defineEmits(['input', 'change', 'focus', 'blur']);
|
||||||
|
const model = defineModel();
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<input v-bind="$attrs" 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"/>
|
<input v-model="model" v-bind="$attrs" @input="e => emit('input', e)" @change="e => emit('change', e)" @focus="e => emit('focus', e)" @blur="e => emit('blur', e)"
|
||||||
|
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"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -111,6 +111,7 @@ function renderNode(node: RootContent, tags: Record<string, any>): VNode | undef
|
||||||
}
|
}
|
||||||
else if(node.type === 'element')
|
else if(node.type === 'element')
|
||||||
{
|
{
|
||||||
|
node.tagName === 'tag' && console.log(node);
|
||||||
return h(tags[node.tagName] ?? node.tagName, { ...node.properties, class: node.properties.className }, {default: () => node.children.map(e => renderNode(e, tags)).filter(e => !!e)});
|
return h(tags[node.tagName] ?? node.tagName, { ...node.properties, class: node.properties.className }, {default: () => node.children.map(e => renderNode(e, tags)).filter(e => !!e)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useTemplateRef } from 'vue';
|
||||||
|
|
||||||
|
const containerRef = useTemplateRef("container");
|
||||||
|
const { direction } = useSwipe(window, { threshold: 150 });
|
||||||
|
|
||||||
|
watch(direction, () => {
|
||||||
|
if(direction.value === 'right')
|
||||||
|
toggleNavigation(true);
|
||||||
|
if(direction.value === 'left')
|
||||||
|
toggleNavigation(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
function toggleNavigation(bool?: boolean)
|
||||||
|
{
|
||||||
|
containerRef.value?.setAttribute('aria-expanded', bool === undefined ? (containerRef.value?.getAttribute('aria-expanded') !== 'true').toString() : bool.toString());
|
||||||
|
}
|
||||||
|
const hideNavigation = () => toggleNavigation(false);
|
||||||
|
onMounted(() => {
|
||||||
|
const links = containerRef.value?.getElementsByTagName('a');
|
||||||
|
if(links)
|
||||||
|
{
|
||||||
|
for(const link of links)
|
||||||
|
{
|
||||||
|
link.addEventListener("click", hideNavigation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onUnmounted(() => {
|
||||||
|
const links = containerRef.value?.getElementsByTagName('a');
|
||||||
|
if(links)
|
||||||
|
{
|
||||||
|
for(const link of links)
|
||||||
|
{
|
||||||
|
link.addEventListener("click", hideNavigation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="container" aria-expanded="false" class="group flex h-screen overflow-hidden">
|
||||||
|
<div class="z-50 sm:hidden block absolute top-0 left-0 p-2 border-e border-b border-light-35 dark:border-dark-35 bg-light-0 dark:bg-dark-0 cursor-pointer hover:bg-light-25 dark:hover:bg-dark-25" @click="e => toggleNavigation()">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||||
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
class="w-4 h-4 fill-light-100 dark:fill-dark-100">
|
||||||
|
<line x1="21" y1="6" x2="3" y2="6"></line>
|
||||||
|
<line x1="15" y1="12" x2="3" y2="12"></line>
|
||||||
|
<line x1="17" y1="18" x2="3" y2="18"></line>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="bg-light-0 sm:my-8 sm:py-3 dark:bg-dark-0 top-0 z-40 xl:w-96 sm:w-[15em] w-full border-r border-light-30 dark:border-dark-30 flex flex-col justify-between max-sm:absolute max-sm:-top-0 max-sm:-bottom-0 sm:left-0 max-sm:-left-full max-sm:group-aria-expanded:left-0 max-sm:transition-[left] py-8 max-sm:z-40">
|
||||||
|
<div class="relative bottom-6 flex flex-1 flex-col gap-4 xl:px-6 px-3">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<NuxtLink @click="hideNavigation" class=" text-light-100 dark:text-dark-100 hover:text-opacity-70 max-sm:ps-6" aria-label="Accueil" :to="{ path: '/', force: true }"><ThemeIcon class="inline" icon="logo" :width=56 :height=56 /></NuxtLink>
|
||||||
|
<div class="flex gap-4 items-center">
|
||||||
|
<ThemeSwitch />
|
||||||
|
<NuxtLink @click="hideNavigation" class="" :to="{ path: '/user/profile', force: true }"><ThemeIcon icon="user" :width=32 :height=32 /></NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex"><SearchView @navigate="hideNavigation" /></div>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<div class="text-center xl:text-sm text-xs text-light-70 dark:text-dark-70">
|
||||||
|
<NuxtLink class="hover:underline italic" :to="{ path: '/third-party', force: true }">Mentions légales</NuxtLink>
|
||||||
|
<p>Copyright Peaceultime - 2024</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -54,7 +54,7 @@ async function debounced()
|
||||||
@mouseleave="(e) => (e.target as HTMLElement).classList.remove('is-selected')"
|
@mouseleave="(e) => (e.target as HTMLElement).classList.remove('is-selected')"
|
||||||
@mousedown.prevent="navigateTo(`/explorer/${result.id}/${result.home}`); input = ''; emit('navigate');">
|
@mousedown.prevent="navigateTo(`/explorer/${result.id}/${result.home}`); input = ''; emit('navigate');">
|
||||||
<div class="">
|
<div class="">
|
||||||
<BoldContent class="text-lg" :text="result.name" :matched="input" />
|
<Highlight class="text-lg" :text="result.name" :matched="input" />
|
||||||
<div class="flex justify-between text-sm">
|
<div class="flex justify-between text-sm">
|
||||||
<div class="">{{ result.username }}</div>
|
<div class="">{{ result.username }}</div>
|
||||||
<div class="">{{ result.pages }} pages</div>
|
<div class="">{{ result.pages }} pages</div>
|
||||||
|
|
@ -66,7 +66,7 @@ async function debounced()
|
||||||
@mouseleave="(e) => (e.target as HTMLElement).classList.remove('is-selected')"
|
@mouseleave="(e) => (e.target as HTMLElement).classList.remove('is-selected')"
|
||||||
@mousedown.prevent="navigateTo(`/explorer/${result.project}/${result.path}`); input = ''; emit('navigate');">
|
@mousedown.prevent="navigateTo(`/explorer/${result.project}/${result.path}`); input = ''; emit('navigate');">
|
||||||
<div class="">
|
<div class="">
|
||||||
<BoldContent class="text-lg" :text="result.title" :matched="input" />
|
<Highlight class="text-lg" :text="result.title" :matched="input" />
|
||||||
<div class="flex justify-between text-sm">
|
<div class="flex justify-between text-sm">
|
||||||
<div class="">{{ result.username }}</div>
|
<div class="">{{ result.username }}</div>
|
||||||
<div class="">{{ result.comments }} commentaires</div>
|
<div class="">{{ result.comments }} commentaires</div>
|
||||||
|
|
@ -78,7 +78,7 @@ async function debounced()
|
||||||
@mouseleave="(e) => (e.target as HTMLElement).classList.remove('is-selected')"
|
@mouseleave="(e) => (e.target as HTMLElement).classList.remove('is-selected')"
|
||||||
@mousedown.prevent="navigateTo(`/user/${result.id}`); input = ''; emit('navigate');">
|
@mousedown.prevent="navigateTo(`/user/${result.id}`); input = ''; emit('navigate');">
|
||||||
<div class="">
|
<div class="">
|
||||||
<BoldContent class="text-lg" :text="result.username" :matched="input" />
|
<Highlight class="text-lg" :text="result.username" :matched="input" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class=""
|
<div class=""
|
||||||
|
|
@ -1,43 +1,9 @@
|
||||||
<script setup lang="ts">
|
|
||||||
const container = ref<HTMLElement>();
|
|
||||||
|
|
||||||
function toggleNavigation(bool?: boolean)
|
|
||||||
{
|
|
||||||
container.value?.setAttribute('aria-expanded', bool === undefined ? (container.value?.getAttribute('aria-expanded') !== 'true').toString() : bool.toString());
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div ref="container" aria-expanded="false" class="group flex flex-1 h-screen overflow-hidden">
|
<NavBar>
|
||||||
<div class="z-50 sm:hidden block absolute top-0 left-0 p-2 border-e border-b border-light-35 dark:border-dark-35 bg-light-0 dark:bg-dark-0 cursor-pointer hover:bg-light-25 dark:hover:bg-dark-25" @click="e => toggleNavigation()">
|
<NuxtLink class="xl:px-6 px-3 text-lg xl: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>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
<NuxtLink class="xl:px-6 px-3 text-lg xl: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>
|
||||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
</NavBar>
|
||||||
class="w-4 h-4 fill-light-100 dark:fill-dark-100">
|
|
||||||
<line x1="21" y1="6" x2="3" y2="6"></line>
|
|
||||||
<line x1="15" y1="12" x2="3" y2="12"></line>
|
|
||||||
<line x1="17" y1="18" x2="3" y2="18"></line>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="bg-light-0 sm:my-8 sm:py-3 dark:bg-dark-0 top-0 z-40 xl:w-96 sm:w-[15em] w-full border-r border-light-30 dark:border-dark-30 flex flex-col justify-between max-sm:absolute max-sm:-top-0 max-sm:-bottom-0 sm:left-0 max-sm:-left-full max-sm:group-aria-expanded:left-0 max-sm:transition-[left] py-8 max-sm:z-40">
|
|
||||||
<div class="relative bottom-6 flex flex-1 flex-col gap-4 xl:px-6 px-3">
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<NuxtLink @click="e => toggleNavigation(false)" class=" text-light-100 dark:text-dark-100 hover:text-opacity-70 max-sm:ps-6" aria-label="Accueil" :to="{ path: '/', force: true }"><ThemeIcon class="inline" icon="logo" :width=56 :height=56 /></NuxtLink>
|
|
||||||
<div class="flex gap-4 items-center">
|
|
||||||
<ThemeSwitch />
|
|
||||||
<NuxtLink @click="e => toggleNavigation(false)" class="" :to="{ path: '/user/profile', force: true }"><ThemeIcon icon="user" :width=32 :height=32 /></NuxtLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex"><SearchView @navigate="e => toggleNavigation(false)" /></div>
|
|
||||||
<NuxtLink @click="e => toggleNavigation(false)" class="xl:px-6 px-3 text-lg xl: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 @click="e => toggleNavigation(false)" class="xl:px-6 px-3 text-lg xl: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 class="text-center xl:text-sm text-xs text-light-70 dark:text-dark-70">
|
|
||||||
<NuxtLink class="hover:underline italic" :to="{ path: '/third-party', force: true }">Mentions légales</NuxtLink>
|
|
||||||
<p>Copyright Peaceultime - 2024</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex-1 flex items-baseline overflow-auto py-8 sm:px-8 px-4 relative">
|
<div class="flex-1 flex items-baseline overflow-auto py-8 sm:px-8 px-4 relative">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -1,45 +1,11 @@
|
||||||
<script setup lang="ts">
|
|
||||||
const container = ref<HTMLElement>();
|
|
||||||
|
|
||||||
function toggleNavigation(bool?: boolean)
|
|
||||||
{
|
|
||||||
container.value?.setAttribute('aria-expanded', bool === undefined ? (container.value?.getAttribute('aria-expanded') !== 'true').toString() : bool.toString());
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div ref="container" aria-expanded="false" class="group flex flex-1 h-screen overflow-hidden">
|
<NavBar>
|
||||||
<div class="z-50 sm:hidden block absolute top-0 left-0 p-2 border-e border-b border-light-35 dark:border-dark-35 bg-light-0 dark:bg-dark-0 cursor-pointer hover:bg-light-25 dark:hover:bg-dark-25" @click="e => toggleNavigation()">
|
<NuxtLink class="xl:px-6 px-3 text-lg xl: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>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
<NuxtLink class="xl:px-6 px-3 text-lg xl: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>
|
||||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
|
||||||
class="w-4 h-4 fill-light-100 dark:fill-dark-100">
|
|
||||||
<line x1="21" y1="6" x2="3" y2="6"></line>
|
|
||||||
<line x1="15" y1="12" x2="3" y2="12"></line>
|
|
||||||
<line x1="17" y1="18" x2="3" y2="18"></line>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="bg-light-0 sm:my-8 sm:py-3 dark:bg-dark-0 top-0 z-40 xl:w-96 sm:w-[15em] w-full border-r border-light-30 dark:border-dark-30 flex flex-col justify-between max-sm:absolute max-sm:-top-0 max-sm:-bottom-0 sm:left-0 max-sm:-left-full max-sm:group-aria-expanded:left-0 max-sm:transition-[left] py-8 max-sm:z-40">
|
|
||||||
<div class="relative bottom-6 flex flex-1 flex-col gap-4 xl:px-6 px-3">
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<NuxtLink @click="e => toggleNavigation(false)" class=" text-light-100 dark:text-dark-100 hover:text-opacity-70 max-sm:ps-6" aria-label="Accueil" :to="{ path: '/', force: true }"><ThemeIcon class="inline" icon="logo" :width=56 :height=56 /></NuxtLink>
|
|
||||||
<div class="flex gap-4 items-center">
|
|
||||||
<ThemeSwitch />
|
|
||||||
<NuxtLink @click="e => toggleNavigation(false)" class="" :to="{ path: '/user/profile', force: true }"><ThemeIcon icon="user" :width=32 :height=32 /></NuxtLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex"><SearchView @navigate="e => toggleNavigation(false)" /></div>
|
|
||||||
<NuxtLink @click="e => toggleNavigation(false)" class="xl:px-6 px-3 text-lg xl: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 @click="e => toggleNavigation(false)" class="xl:px-6 px-3 text-lg xl: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>
|
|
||||||
<hr class="border-light-35 dark:border-dark-35"/>
|
<hr class="border-light-35 dark:border-dark-35"/>
|
||||||
<ExplorerNavigation @navigate="e => toggleNavigation(false)"></ExplorerNavigation>
|
<ExplorerNavigation></ExplorerNavigation>
|
||||||
</div>
|
</NavBar>
|
||||||
<div class="text-center xl:text-sm text-xs text-light-70 dark:text-dark-70">
|
|
||||||
<NuxtLink class="hover:underline italic" :to="{ path: '/third-party', force: true }">Mentions légales</NuxtLink>
|
|
||||||
<p>Copyright Peaceultime - 2024</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex-1 flex items-baseline overflow-auto py-8 sm:px-8 px-4 relative">
|
<div class="flex-1 flex items-baseline overflow-auto py-8 sm:px-8 px-4 relative">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
let data = `### Librairies, framework et outils libres utilisés:
|
let data = `### Librairies, framework et outils libres utilisés:
|
||||||
- [Vuejs](https://vuejs.org/) - MIT License - Copyright (c) 2018-present, Yuxi (Evan) You and Vue contributors
|
- [vue.js](https://vuejs.org/) - MIT License - Copyright (c) 2018-present, Yuxi (Evan) You and Vue contributors
|
||||||
- [vue-router](https://router.vuejs.org/) - MIT License - Copyright (c) 2019-present Eduardo San Martin Morote
|
- [vue-router](https://router.vuejs.org/) - MIT License - Copyright (c) 2019-present Eduardo San Martin Morote
|
||||||
- [Nuxt](https://nuxt.com/) - MIT License - Copyright (c) 2016-present - Nuxt Team
|
- [nuxt](https://nuxt.com/) - MIT License - Copyright (c) 2016-present - Nuxt Team
|
||||||
- [nuxt-auth-utils](https://github.com/atinux/nuxt-auth-utils) - MIT License - Copyright (c) 2023 Sébastien Chopin
|
- [@nuxtjs/color-mode](https://color-mode.nuxtjs.org/) - MIT License - Copyright (c) Nuxt Team
|
||||||
|
- [@nuxtjs/tailwindcss](https://tailwindcss.nuxtjs.org/) - MIT License - Copyright (c) Nuxt Community
|
||||||
|
- [@vueuse/nuxt](https://vueuse.org/) - MIT License - Copyright (c) 2019-PRESENT Anthony Fu https://github.com/antfu
|
||||||
|
- [nuxt-security](https://nuxt-security.vercel.app/) - MIT License - Copyright (c) 2023 Baroshem jakub.andrzejewski.dev@gmail.com
|
||||||
|
- [zod](https://zod.dev/) - MIT License - Copyright (c) 2020 Colin McDonnell
|
||||||
|
- [unified](https://unifiedjs.com/) - MIT License - Copyright (c) 2015 Titus Wormer <tituswormer@gmail.com>
|
||||||
|
|
||||||
Le logo a été créé grace aux icones de [Game Icons](https://game-icons.net).`;
|
Le logo a été créé grace aux icones de [Game Icons](https://game-icons.net).`;
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -80,9 +80,9 @@ function handleErrors(error: Error | ZodError)
|
||||||
<div class="p-8 w-[48em] border border-light-35 dark:border-dark-35">
|
<div class="p-8 w-[48em] border border-light-35 dark:border-dark-35">
|
||||||
<form @submit.prevent="submit" class="p-4 bg-light-25 dark:bg-dark-30">
|
<form @submit.prevent="submit" class="p-4 bg-light-25 dark:bg-dark-30">
|
||||||
<h1 class="text-2xl font-bold tracking-wider pb-4">Connexion</h1>
|
<h1 class="text-2xl font-bold tracking-wider pb-4">Connexion</h1>
|
||||||
<Input type="text" autocomplete="username" v-model="state.usernameOrEmail"
|
<InputField type="text" autocomplete="username" v-model="state.usernameOrEmail"
|
||||||
placeholder="" title="Nom d'utilisateur ou adresse mail" :error="usernameError" class="w-[24em]" />
|
placeholder="" title="Nom d'utilisateur ou adresse mail" :error="usernameError" class="w-[24em]" />
|
||||||
<Input type="password" autocomplete="current-password" v-model="state.password"
|
<InputField type="password" autocomplete="current-password" v-model="state.password"
|
||||||
placeholder="" title="Mot de passe"
|
placeholder="" title="Mot de passe"
|
||||||
:error="passwordError" class="w-[24em]"/>
|
:error="passwordError" class="w-[24em]"/>
|
||||||
<span v-if="generalError" class="text-light-red dark:text-dark-red">{{ generalError }}</span>
|
<span v-if="generalError" class="text-light-red dark:text-dark-red">{{ generalError }}</span>
|
||||||
|
|
|
||||||
|
|
@ -89,11 +89,11 @@ function handleErrors(error: Error | ZodError)
|
||||||
<div class="p-8 w-[48em] border border-light-35 dark:border-dark-35">
|
<div class="p-8 w-[48em] border border-light-35 dark:border-dark-35">
|
||||||
<form @submit.prevent="submit" class="p-4 bg-light-25 dark:bg-dark-30">
|
<form @submit.prevent="submit" class="p-4 bg-light-25 dark:bg-dark-30">
|
||||||
<h1 class="text-2xl font-bold tracking-wider pb-4">Inscription</h1>
|
<h1 class="text-2xl font-bold tracking-wider pb-4">Inscription</h1>
|
||||||
<Input type="text" autocomplete="username" v-model="state.username"
|
<InputField type="text" autocomplete="username" v-model="state.username"
|
||||||
placeholder="Entrez un nom d'utilisateur" title="Nom d'utilisateur" :error="usernameError" class="w-[24em]"/>
|
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"
|
<InputField type="text" autocomplete="email" v-model="state.email" placeholder="Entrez une addresse mail"
|
||||||
title="Adresse mail" :error="emailError" class="w-[24em]"/>
|
title="Adresse mail" :error="emailError" class="w-[24em]"/>
|
||||||
<Input type="password" autocomplete="new-password" v-model="state.password"
|
<InputField 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)" class="w-[24em]"/>
|
:error="!(checkedLength && checkedLowerUpper && checkedDigit && checkedSymbol)" class="w-[24em]"/>
|
||||||
<div class="flex flex-col font-light">
|
<div class="flex flex-col font-light">
|
||||||
|
|
@ -110,7 +110,7 @@ function handleErrors(error: Error | ZodError)
|
||||||
<pre>! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~</pre>
|
<pre>! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~</pre>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Input type="password" v-model="confirmPassword" placeholder="Confirmer le mot de passe"
|
<InputField 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'" class="w-[24em]"/>
|
:error="confirmPassword === '' || confirmPassword === state.password ? '' : 'Les mots de passe saisies ne sont pas identique'" class="w-[24em]"/>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue