Components rework and lots of fixes

This commit is contained in:
Peaceultime 2024-09-05 17:44:30 +02:00
parent 094f27d9ad
commit d7a8087c6c
21 changed files with 286 additions and 196 deletions

View File

@ -31,8 +31,8 @@ const colors = computed(() => {
</style>
<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="[{'-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="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="[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">
<template v-if="node.type === 'group' || zoom > Math.min(0.4, 1000 / size)">
<div v-if="node.text?.length > 0" class="flex items-center">

View File

@ -18,23 +18,13 @@ const emit = defineEmits(['navigate']);
<template>
<div class="py-1" v-if="project && !isNaN(project)">
<template v-if="hasChildren">
<div class="flex gap-2 items-center cursor-pointer hover:text-opacity-75 text-light-100 dark:text-dark-100
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]"
@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">
<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
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 #header>{{ link.title }}</template>
<template #content >
<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
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 }"

View File

@ -1,30 +1,29 @@
<template>
<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] 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"
@mouseenter="debounce(show, 250)" @mouseleave="debounce(() => display = false, 250)">
<div v-if="pending" class="loading"></div>
<template v-else-if="!!file">
<div v-if="file.type === 'Markdown'" class="p-6 ms-6">
<ProseH1>{{ file.title }}</ProseH1>
<Markdown v-model="file.content"></Markdown>
</div>
<div v-else-if="file.type === 'Canvas'" class="w-[550px] h-[450px] overflow-hidden">
<CanvasRenderer :canvas="JSON.parse(file.content) " />
</div>
<HoverPopup @before-show="fetch" :class="[{'is-loaded': fetched}, file?.type === 'Markdown' ? 'overflow-auto' : 'overflow-hidden']">
<template #content>
<Suspense>
<div v-if="pending" class="loading w-[550px] h-[450px]"></div>
<template v-else-if="!!file">
<div v-if="file.type === 'Markdown'" class="p-6 ms-6">
<ProseH1>{{ file.title }}</ProseH1>
<Markdown v-model="file.content"></Markdown>
</div>
<div v-else-if="file.type === 'Canvas'" class="w-[550px] h-[450px] overflow-hidden">
<CanvasRenderer :canvas="JSON.parse(file.content) " />
</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">Fichier vide</div>
<div class="text-lg text-light-60 dark:text-dark-60">Cette page est vide</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>
</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>
</div>
</Teleport>
<span ref="el" @mouseenter="debounce(show, 250)" @mouseleave="debounce(() => display = false, 250)">
</Suspense>
</template>
<slot></slot>
</span>
</HoverPopup>
</template>
<script setup lang="ts">
@ -45,12 +44,13 @@ const props = defineProps({
}
})
const file = ref<CommentedFile | null>();
const display = ref(false), pending = ref(false), fetched = ref(false);
const el = ref(), pos = ref<Record<string, string>>();
const loaded = computed(() => !pending.value && fetched.value && file.value !== undefined);
const pending = ref(false), fetched = ref(false);
async function fetch()
{
if(fetched.value)
return;
fetched.value = true;
pending.value = true;
@ -59,37 +59,4 @@ async function fetch()
pending.value = false;
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>

View File

@ -1,20 +1,22 @@
<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">
<PreviewContent :project="project" :path="data[0].path" :anchor="hash">
<div class="inline-flex items-center">
<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"
:icon="`link-${data[0].type.toLowerCase()}`" />
</div>
</PreviewContent>
</NuxtLink>
<NuxtLink v-else-if="href" :to="href" :class="class" class="text-accent-blue inline-flex items-center">
<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"
:icon="`link-${data[0].type.toLowerCase()}`" />
</NuxtLink>
<slot :class="class" v-else v-bind="$attrs"></slot>
<PreviewContent :project="project" :path="data[0].path" :anchor="hash">
<div class="inline-flex items-center">
<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"
:icon="`link-${data[0].type.toLowerCase()}`" />
</div>
</PreviewContent>
</NuxtLink>
<NuxtLink no-prefetch v-else-if="href" :to="href" :class="class" class="text-accent-blue inline-flex items-center">
<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"
:icon="`link-${data[0].type.toLowerCase()}`" />
</NuxtLink>
<slot :class="class" v-else v-bind="$attrs"></slot>
</Suspense>
</template>
<script setup lang="ts">
@ -31,9 +33,11 @@ const props = defineProps({
}
});
const nuxt = useNuxtApp();
const route = useRoute();
const { hash, pathname, protocol } = parseURL(props.href);
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`, {
method: 'GET',
@ -41,14 +45,19 @@ const { data, status, execute } = await useFetch(`/api/project/${project.value}/
search: `%${pathname}`
},
transform: (data) => data?.map(e => ({ path: e.path, type: e.type })),
key: `file:${project.value}:%${pathname}`,
dedupe: 'defer',
key: key.value,
ignoreResponseError: true,
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>

View File

@ -1,5 +1,44 @@
<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">
<slot></slot>
</span>
</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>
<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>
</span>
</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>

View File

@ -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>

View File

@ -1,6 +1,6 @@
<template>
<div>
{{beforeText}}<span class="highlight">{{matchedText}}</span>{{afterText}}
{{beforeText}}<span class="font-bold">{{matchedText}}</span>{{afterText}}
</div>
</template>

View File

@ -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>

View File

@ -1,3 +1,9 @@
<script setup lang="ts">
const emit = defineEmits(['input', 'change', 'focus', 'blur']);
const model = defineModel();
</script>
<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>

View File

@ -111,6 +111,7 @@ function renderNode(node: RootContent, tags: Record<string, any>): VNode | undef
}
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)});
}

View File

@ -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>

View File

@ -54,7 +54,7 @@ async function debounced()
@mouseleave="(e) => (e.target as HTMLElement).classList.remove('is-selected')"
@mousedown.prevent="navigateTo(`/explorer/${result.id}/${result.home}`); input = ''; emit('navigate');">
<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="">{{ result.username }}</div>
<div class="">{{ result.pages }} pages</div>
@ -66,7 +66,7 @@ async function debounced()
@mouseleave="(e) => (e.target as HTMLElement).classList.remove('is-selected')"
@mousedown.prevent="navigateTo(`/explorer/${result.project}/${result.path}`); input = ''; emit('navigate');">
<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="">{{ result.username }}</div>
<div class="">{{ result.comments }} commentaires</div>
@ -78,7 +78,7 @@ async function debounced()
@mouseleave="(e) => (e.target as HTMLElement).classList.remove('is-selected')"
@mousedown.prevent="navigateTo(`/user/${result.id}`); input = ''; emit('navigate');">
<div class="">
<BoldContent class="text-lg" :text="result.username" :matched="input" />
<Highlight class="text-lg" :text="result.username" :matched="input" />
</div>
</div>
<div class=""

View File

@ -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>
<div ref="container" aria-expanded="false" class="group flex flex-1 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="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">
<slot></slot>
</div>
<NavBar>
<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>
<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>
</NavBar>
<div class="flex-1 flex items-baseline overflow-auto py-8 sm:px-8 px-4 relative">
<slot></slot>
</div>
</template>

View File

@ -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>
<div ref="container" aria-expanded="false" class="group flex flex-1 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="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"/>
<ExplorerNavigation @navigate="e => toggleNavigation(false)"></ExplorerNavigation>
</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">
<slot></slot>
</div>
<NavBar>
<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>
<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>
<hr class="border-light-35 dark:border-dark-35"/>
<ExplorerNavigation></ExplorerNavigation>
</NavBar>
<div class="flex-1 flex items-baseline overflow-auto py-8 sm:px-8 px-4 relative">
<slot></slot>
</div>
</template>

View File

@ -1,9 +1,14 @@
<script setup lang="ts">
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
- [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
- [nuxt](https://nuxt.com/) - MIT License - Copyright (c) 2016-present - Nuxt Team
- [@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).`;
</script>

View File

@ -80,9 +80,9 @@ function handleErrors(error: Error | ZodError)
<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">
<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]" />
<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"
:error="passwordError" class="w-[24em]"/>
<span v-if="generalError" class="text-light-red dark:text-dark-red">{{ generalError }}</span>

View File

@ -89,11 +89,11 @@ function handleErrors(error: Error | ZodError)
<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">
<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]"/>
<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]"/>
<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"
:error="!(checkedLength && checkedLowerUpper && checkedDigit && checkedSymbol)" class="w-[24em]"/>
<div class="flex flex-col font-light">
@ -110,7 +110,7 @@ function handleErrors(error: Error | ZodError)
<pre>! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~</pre>
</span>
</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"
autocomplete="new-password"
:error="confirmPassword === '' || confirmPassword === state.password ? '' : 'Les mots de passe saisies ne sont pas identique'" class="w-[24em]"/>