Polish CSS for mobile editor. Add user logout from admin panel.

This commit is contained in:
Peaceultime 2024-12-03 14:22:02 +01:00
parent ecdfa947ac
commit 51a5d501be
12 changed files with 238 additions and 161 deletions

View File

@ -4,7 +4,7 @@
<NuxtLoadingIndicator />
<TooltipProvider>
<NuxtLayout>
<div class="xl:ps-12 xl:pe-12 ps-6 pe-4 flex flex-1 justify-center overflow-auto max-h-full relative">
<div class="xl:px-12 xl:py-8 lg:px-8 lg:py-6 px-6 py-3 flex flex-1 justify-center overflow-auto max-h-full relative">
<NuxtPage></NuxtPage>
</div>
</NuxtLayout>

View File

@ -1,5 +1,5 @@
<template>
<TreeRoot v-bind="forward" v-slot="{ flattenItems }" class="list-none select-none text-light-100 dark:text-dark-100 p-2 xl:text-base text-sm overflow-auto max-h-full">
<TreeRoot v-bind="forward" v-slot="{ flattenItems }" class="list-none select-none text-light-100 dark:text-dark-100 overflow-auto max-h-full">
<DraggableTreeItem v-for="item in flattenItems" :key="item._id" v-bind="item" class="flex items-center outline-none relative cursor-pointer hover:bg-light-20 dark:hover:bg-dark-20 data-[selected]:bg-light-35 dark:data-[selected]:bg-dark-35" @select.prevent @toggle.prevent>
<template #default="{ handleToggle, handleSelect, isExpanded, isSelected, isDragging, isDraggedOver }">
<slot :handleToggle="handleToggle"

View File

@ -1,6 +1,6 @@
<template>
<DropdownMenuRoot>
<DropdownMenuTrigger :disabled="disabled" ><slot /></DropdownMenuTrigger>
<DropdownMenuTrigger :disabled="disabled"><slot /></DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent :align="align" :side="side" class="z-50 outline-none bg-light-20 dark:bg-dark-20 will-change-[opacity,transform] data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade border border-light-35 dark:border-dark-35">

View File

@ -1,5 +1,6 @@
<template>
<Label class="flex justify-center items-center my-2">{{ label }}
<Label class="flex justify-center items-center my-2">
<span class="md:text-base text-sm">{{ label }}</span>
<SwitchRoot v-model:checked="model" :disabled="disabled"
class="group mx-3 w-12 h-6 select-none transition-all border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 outline-none
data-[state=checked]:bg-light-35 dark:data-[state=checked]:bg-dark-35 hover:border-light-50 dark:hover:border-dark-50 focus:shadow-raw focus:shadow-light-40 dark:focus:shadow-dark-40

View File

@ -7,6 +7,7 @@ export interface ShortcutConfig {
handler: Function
usingInput?: string | boolean
whenever?: WatchSource<boolean>[]
prevent?: boolean
}
export interface ShortcutsConfig {
@ -29,6 +30,7 @@ interface Shortcut {
altKey: boolean
// code?: string
// keyCode?: number
prevent?: boolean
}
const chainedShortcutRegex = /^[^-]+.*-.*[^-]+$/
@ -61,7 +63,8 @@ export const useShortcuts = (config: ShortcutsConfig, options: ShortcutsOptions
if (shortcut.key !== chainedKey) { continue }
if (shortcut.condition.value) {
e.preventDefault()
e.stopPropagation
shortcut.prevent && e.preventDefault()
shortcut.handler()
}
clearChainedInput()
@ -139,7 +142,7 @@ export const useShortcuts = (config: ShortcutsConfig, options: ShortcutsOptions
if (typeof shortcutConfig === 'function') {
shortcut.handler = shortcutConfig
} else if (typeof shortcutConfig === 'object') {
shortcut = { ...shortcut, handler: shortcutConfig.handler }
shortcut = { ...shortcut, handler: shortcutConfig.handler, prevent: shortcutConfig.prevent }
}
if (!shortcut.handler) {

BIN
db.sqlite

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -13,13 +13,15 @@
<div class="flex items-center px-2">
<Tooltip message="Changer de theme" side="left"><ThemeSwitch /></Tooltip>
<Tooltip v-if="!loggedIn" :message="'Se connecter'" side="right">
<div class="hover:border-opacity-70 flex">
<Icon :icon="'radix-icons:person'" class="w-7 h-7 p-1" />
</div>
<NuxtLink :to="{ name: 'user-login' }">
<div class="hover:border-opacity-70 flex items-center">
<Icon :icon="'radix-icons:person'" class="w-7 h-7 p-1" />
</div>
</NuxtLink>
</Tooltip>
<Tooltip v-else :message="'Mon profil'" side="right">
<DropdownMenu :options="options" side="bottom" align="end">
<div class="hover:border-opacity-70 flex">
<div class="hover:border-opacity-70 flex items-center">
<Icon :icon="'radix-icons:avatar'" class="w-7 h-7 p-1" />
</div>
</DropdownMenu>
@ -28,8 +30,8 @@
</div>
<div class="flex flex-1 flex-row relative h-screen overflow-hidden">
<CollapsibleContent asChild forceMount>
<div class="bg-light-0 md:py-11 dark:bg-dark-0 z-40 xl:w-96 md:w-[15em] w-full border-r border-light-30 dark:border-dark-30 flex flex-col justify-between max-md:absolute max-md:-top-0 max-md:-bottom-0 md:left-0 max-md:data-[state=closed]:-left-full max-md:transition-[left] py-8 max-md:z-40 max-md:data-[state=open]:left-0">
<div class="relative bottom-6 flex flex-col gap-4 xl:px-6 px-3">
<div class="bg-light-0 dark:bg-dark-0 z-40 xl:w-96 md:w-[15em] w-full border-r border-light-30 dark:border-dark-30 flex flex-col justify-between max-md:absolute max-md:-top-0 max-md:-bottom-0 md:left-0 max-md:data-[state=closed]:-left-full max-md:transition-[left] max-md:z-40 max-md:data-[state=open]:left-0">
<div class="flex flex-col gap-4 xl:px-6 px-3 py-4">
<div class="flex justify-between items-center max-md:hidden">
<NuxtLink class=" text-light-100 dark:text-dark-100 hover:text-opacity-70 max-md:ps-6" aria-label="Accueil" :to="{ path: '/', force: true }">
<Avatar src="/logo.dark.svg" class="dark:block hidden" />
@ -74,7 +76,7 @@
</template>
</Tree>
</div>
<div class="xl:px-12 px-6 text-start text-xs text-light-60 dark:text-dark-60 relative top-4">
<div class="xl:px-12 px-6 py-4 text-center text-xs text-light-60 dark:text-dark-60">
<NuxtLink class="hover:underline italic" :to="{ name: 'roadmap' }">Roadmap</NuxtLink> - <NuxtLink class="hover:underline italic" :to="{ name: 'legal' }">Mentions légales</NuxtLink>
<p>Copyright Peaceultime - 2024</p>
</div>

View File

@ -135,6 +135,27 @@ async function editPermissions(user: User)
});
}
}
async function logout(user: User)
{
try
{
await $fetch(`/api/admin/user/${user.id}/logout`, {
method: 'POST',
});
user.session.length = 0;
toaster.add({
duration: 10000, type: 'success', content: 'L\'utilisateur vient d\'être déconnecté.', timer: true,
});
}
catch(e)
{
toaster.add({
duration: 10000, type: 'error', content: (e as any).message, timer: true,
});
}
}
</script>
<template>
@ -178,7 +199,7 @@ async function editPermissions(user: User)
</DialogTitle>
<div class="flex flex-1 justify-end gap-4">
<DialogClose asChild><Button>Non</Button></DialogClose>
<DialogClose asChild><Button class="border-light-green dark:border-dark-green hover:border-light-green dark:hover:border-dark-green hover:bg-light-greenBack dark:hover:bg-dark-greenBack text-light-green dark:text-dark-green focus:shadow-light-green dark:focus:shadow-dark-green">Oui</Button></DialogClose>
<DialogClose asChild><Button @click="() => logout(user)" class="border-light-green dark:border-dark-green hover:border-light-green dark:hover:border-dark-green hover:bg-light-greenBack dark:hover:bg-dark-greenBack text-light-green dark:text-dark-green focus:shadow-light-green dark:focus:shadow-dark-green">Oui</Button></DialogClose>
</div>
</DialogContent>
</DialogPortal>

View File

@ -1,164 +1,173 @@
<template>
<!-- TODO: Put back the Collapsible components, I removed it before I decided to add the file editor into this page -->
<Head>
<Title>d[any] - Modification</Title>
</Head>
<ClientOnly>
<div class="xl:-ms-12 xl:-me-12 -ms-6 -me-4 flex flex-1">
<div class="z-50 md:hidden flex w-full items-center justify-between h-12 border-b border-light-35 dark:border-dark-35">
<div class="flex items-center px-2">
<Button icon class="ms-2 !bg-transparent group" disabled>
<Icon class="group-data-[state=open]:hidden" icon="radix-icons:hamburger-menu" />
<Icon class="group-data-[state=closed]:hidden" icon="radix-icons:cross-1" />
</Button>
<span class="text-light-100 dark:text-dark-100 hover:text-opacity-70 max-md:ps-6" aria-label="Accueil">Accueil</span>
</div>
<div class="flex items-center px-2">
<Tooltip message="Changer de theme" side="left"><ThemeSwitch /></Tooltip>
</div>
</div>
<div class="flex flex-1 flex-row relative h-screen overflow-hidden">
<div class="bg-light-0 md:py-11 dark:bg-dark-0 z-40 xl:w-96 md:w-[15em] w-full border-r border-light-30 dark:border-dark-30 flex flex-col justify-between max-md:absolute max-md:-top-0 max-md:-bottom-0 md:left-0 max-md:data-[state=closed]:-left-full max-md:transition-[left] py-8 max-md:z-40 max-md:data-[state=open]:left-0">
<div class="relative bottom-6 flex flex-col gap-4 xl:px-6 px-3">
<div class="flex justify-between items-center max-md:hidden">
<div class=" text-light-100 dark:text-dark-100 hover:text-opacity-70 max-md:ps-6" aria-label="Accueil">
<Avatar src="/logo.dark.svg" class="dark:block hidden" />
<Avatar src="/logo.light.svg" class="block dark:hidden" />
</div>
<div class="flex gap-4 items-center">
<Tooltip message="Changer de theme" side="left"><ThemeSwitch /></Tooltip>
</div>
</div>
<CollapsibleRoot asChild class="flex flex-1 flex-col xl:-mx-12 xl:-my-8 lg:-mx-8 lg:-my-6 -mx-6 -my-3 overflow-hidden" v-model="open">
<div>
<div class="z-50 md:hidden flex w-full items-center justify-between h-12 border-b border-light-35 dark:border-dark-35">
<div class="flex items-center px-2">
<CollapsibleTrigger asChild>
<Button icon class="ms-2 !bg-transparent group">
<Icon class="group-data-[state=open]:hidden" icon="radix-icons:hamburger-menu" />
<Icon class="group-data-[state=closed]:hidden" icon="radix-icons:cross-1" />
</Button>
</CollapsibleTrigger>
</div>
<div class="flex-1 xl:px-6 px-3 max-w-full max-h-full" v-if="navigation">
<div class="flex flex-1 flex-row justify-between items-center mb-4">
<div class="flex flex-1 flex-row justify-start items-center gap-4">
<Tooltip side="top" message="Annuler (Ctrl+Shift+W)" ><Button icon @click="router.go(-1)"><Icon class="w-5 h-5" icon="radix-icons:arrow-left" /></Button></Tooltip>
<Tooltip side="top" message="Enregistrer (Ctrl+S)" ><Button icon :loading="saveStatus === 'pending'" @click="save(true)"><Icon class="w-5 h-5" icon="radix-icons:check" /></Button></Tooltip>
<span v-if="edited" class="text-sm text-light-60 dark:text-dark-60 italic">Modifications non enregistrées</span>
<div class="flex items-center px-2">
<Tooltip message="Changer de theme" side="left"><ThemeSwitch /></Tooltip>
</div>
</div>
<div class="flex flex-1 flex-row relative overflow-hidden">
<CollapsibleContent asChild forceMount>
<div class=" overflow-hidden bg-light-0 dark:bg-dark-0 z-40 xl:w-96 md:w-[15em] max-h-full w-full border-r border-light-30 dark:border-dark-30 flex flex-col justify-between max-md:absolute max-md:-top-0 max-md:-bottom-0 md:left-0 max-md:data-[state=closed]:-left-full max-md:transition-[left] max-md:z-40 max-md:data-[state=open]:left-0">
<div class="flex flex-col gap-4 xl:px-6 px-3 py-4">
<div class="flex justify-between items-center max-md:hidden">
<div class=" text-light-100 dark:text-dark-100 hover:text-opacity-70 max-md:ps-6" aria-label="Accueil">
<Avatar src="/logo.dark.svg" class="dark:block hidden" />
<Avatar src="/logo.light.svg" class="block dark:hidden" />
</div>
<div class="flex gap-4 items-center">
<Tooltip message="Changer de theme" side="left"><ThemeSwitch /></Tooltip>
</div>
</div>
</div>
<div class="flex flex-row justify-end items-center gap-4">
<AlertDialogRoot v-if="selected">
<AlertDialogTrigger as="span"><Tooltip side="top" message="Supprimer"><Button icon 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" ><Icon class="w-5 h-5" icon="radix-icons:trash" /></Button></Tooltip></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 flex flex-row items-center">
<AlertDialogTitle class="text-xl font-semibold">Supprimer <span>{{ selected.title }}</span><span v-if="selected.children"> et tous ces enfants</span> ?</AlertDialogTitle>
<div class="flex flex-1 flex-row gap-4 justify-end">
<AlertDialogAction asChild @click="navigation = tree.remove(navigation, getPath(selected))"><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">Oui</Button></AlertDialogAction>
<AlertDialogCancel asChild><Button>Non</Button></AlertDialogCancel>
<div class="flex flex-1 flex-col max-w-full max-h-full overflow-hidden py-3" v-if="navigation">
<div class="flex flex-row justify-between items-center mb-4 px-6">
<div class="flex flex-1 flex-row justify-start items-center gap-4">
<Tooltip side="top" message="Annuler (Ctrl+Shift+W)" ><Button icon @click="router.go(-1)"><Icon class="w-5 h-5" icon="radix-icons:arrow-left" /></Button></Tooltip>
<Tooltip side="top" message="Enregistrer (Ctrl+S)" ><Button icon :loading="saveStatus === 'pending'" @click="save(true)"><Icon class="w-5 h-5" icon="radix-icons:check" /></Button></Tooltip>
<span v-if="edited" class="text-sm text-light-60 dark:text-dark-60 italic">Modifications non enregistrées</span>
</div>
<div class="flex flex-row justify-end items-center gap-4">
<AlertDialogRoot v-if="selected">
<Tooltip side="top" message="Supprimer"><AlertDialogTrigger as="span"><Button icon 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" ><Icon class="w-5 h-5" icon="radix-icons:trash" /></Button></AlertDialogTrigger></Tooltip>
<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 flex md:flex-row flex-col gap-4 items-center">
<AlertDialogTitle class="text-xl font-semibold">Supprimer <span>{{ selected.title }}</span><span v-if="selected.children"> et tous ces enfants</span> ?</AlertDialogTitle>
<div class="flex flex-1 flex-row gap-4 justify-end">
<AlertDialogAction asChild @click="navigation = tree.remove(navigation, getPath(selected))"><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">Oui</Button></AlertDialogAction>
<AlertDialogCancel asChild><Button>Non</Button></AlertDialogCancel>
</div>
</AlertDialogContent>
</AlertDialogPortal>
</AlertDialogRoot>
<Tooltip side="top" message="Nouveau">
<DropdownMenu align="center" side="bottom" :options="[{
type: 'item',
label: 'Markdown',
kbd: 'Ctrl+N',
icon: 'radix-icons:file',
select: () => add('markdown'),
}, {
type: 'item',
label: 'Dossier',
kbd: 'Ctrl+Shift+N',
icon: 'lucide:folder',
select: () => add('folder'),
}, {
type: 'item',
label: 'Canvas',
icon: 'ph:graph-light',
select: () => add('canvas'),
}, {
type: 'item',
label: 'Fichier',
icon: 'radix-icons:file-text',
select: () => add('file'),
}]">
<Button icon><Icon class="w-5 h-5" icon="radix-icons:plus" /></Button>
</DropdownMenu>
</Tooltip>
</div>
</div>
<DraggableTree class="ps-4 pe-2 xl:text-base text-sm"
:items="navigation ?? undefined" :get-key="(item: Partial<ProjectExtendedItem>) => item.path !== undefined ? getPath(item as ProjectExtendedItem) : ''" @updateTree="drop"
v-model="selected" :defaultExpanded="defaultExpanded" >
<template #default="{ handleToggle, handleSelect, isExpanded, isSelected, isDragging, item }">
<div class="flex flex-1 items-center px-2 max-w-full pe-4" :class="{ 'opacity-50': isDragging }" :style="{ 'padding-left': `${item.level - 0.5}em` }">
<span class="py-2 px-2" @click="handleToggle" v-if="item.hasChildren" >
<Icon :icon="isExpanded ? 'lucide:folder-open' : 'lucide:folder'"/>
</span>
<Icon v-else-if="iconByType[item.value.type]" :icon="iconByType[item.value.type]" class="group-[:hover]:text-accent-purple mx-2" @click="handleSelect" />
<div class="pl-3 py-1 flex-1 truncate" :title="item.value.title" @click="handleSelect" :class="{ 'font-semibold': item.hasChildren }">
{{ item.value.title }}
</div>
</AlertDialogContent>
</AlertDialogPortal>
</AlertDialogRoot>
<DropdownMenu align="center" side="bottom" :options="[{
type: 'item',
label: 'Markdown',
kbd: 'Ctrl+N',
icon: 'radix-icons:file',
select: () => add('markdown'),
}, {
type: 'item',
label: 'Dossier',
kbd: 'Ctrl+Shift+N',
icon: 'lucide:folder',
select: () => add('folder'),
}, {
type: 'item',
label: 'Canvas',
icon: 'ph:graph-light',
select: () => add('canvas'),
}, {
type: 'item',
label: 'Fichier',
icon: 'radix-icons:file-text',
select: () => add('file'),
}]">
<Tooltip side="top" message="Nouveau" ><Button icon><Icon class="w-5 h-5" icon="radix-icons:plus" /></Button></Tooltip>
</DropdownMenu>
<div class="flex gap-2">
<span @click="item.value.private = !item.value.private">
<Icon v-if="item.value.private" icon="radix-icons:lock-closed" />
<Icon v-else class="text-light-50 dark:text-dark-50" icon="radix-icons:lock-open-2" />
</span>
<span @click="item.value.navigable = !item.value.navigable">
<Icon v-if="item.value.navigable" icon="radix-icons:eye-open" />
<Icon v-else class="text-light-50 dark:text-dark-50" icon="radix-icons:eye-none" />
</span>
</div>
</div>
</template>
<template #hint="{ instruction }">
<div v-if="instruction" class="absolute h-full w-full top-0 right-0 border-light-50 dark:border-dark-50" :style="{
width: `calc(100% - ${instruction.currentLevel - 1}em)`
}" :class="{
'!border-b-4': instruction?.type === 'reorder-below',
'!border-t-4': instruction?.type === 'reorder-above',
'!border-4': instruction?.type === 'make-child',
}"></div>
</template>
</DraggableTree>
</div>
</div>
<DraggableTree class="-mx-3 list-none select-none text-light-100 dark:text-dark-100 p-2 xl:text-base text-sm overflow-auto"
:items="navigation ?? undefined" :get-key="(item: Partial<ProjectExtendedItem>) => item.path !== undefined ? getPath(item as ProjectExtendedItem) : ''" @updateTree="drop"
v-model="selected" :defaultExpanded="defaultExpanded" >
<template #default="{ handleToggle, handleSelect, isExpanded, isSelected, isDragging, item }">
<div class="flex flex-1 items-center px-2" :class="{ 'opacity-50': isDragging }" :style="{ 'padding-left': `${item.level - 0.5}em` }">
<span class="py-2 px-2" @click="handleToggle" v-if="item.hasChildren" >
<Icon :icon="isExpanded ? 'lucide:folder-open' : 'lucide:folder'"/>
</CollapsibleContent>
<div class="flex flex-1 flex-row max-h-full overflow-hidden">
<div v-if="selected" class="xl:px-12 xl:py-8 lg:px-8 lg:py-6 px-6 py-3 flex flex-1 flex-col items-start justify-start max-h-full relative">
<Head>
<Title>d[any] - Modification de {{ selected.title }}</Title>
</Head>
<div>
<input type="text" v-model="selected.title" placeholder="Titre" class="inline md:h-16 h-12 w-full 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 py-1 md:text-5xl text-4xl font-thin bg-transparent" />
<div class="flex flex-col justify-start items-start">
<Switch label="Chemin personnalisé" v-model="selected.customPath" />
<span>
<pre v-if="selected.customPath" class="flex md:items-center md:text-base md:text-nowrap text-wrap md:flex-nowrap flex-wrap text-sm">/{{ selected.parent !== '' ? selected.parent + '/' : '' }}<TextInput v-model="selected.name" @input="(e) => {
if(selected && selected.customPath)
{
selected.name = parsePath(selected.name);
rebuildPath(selected.children, getPath(selected));
}
}" class="mx-0"/></pre>
<pre v-else class="md:text-base text-smmd:text-nowrap text-wrap ">/{{ getPath(selected) }}</pre>
</span>
<Icon v-else-if="iconByType[item.value.type]" :icon="iconByType[item.value.type]" class="group-[:hover]:text-accent-purple mx-2" @click="handleSelect" />
<div class="pl-3 py-1 flex-1 truncate" :title="item.value.title" @click="handleSelect">
{{ item.value.title }}
</div>
<div class="flex gap-2">
<span @click="item.value.private = !item.value.private">
<Icon v-if="item.value.private" icon="radix-icons:lock-closed" />
<Icon v-else class="text-light-50 dark:text-dark-50" icon="radix-icons:lock-open-2" />
</span>
<span @click="item.value.navigable = !item.value.navigable">
<Icon v-if="item.value.navigable" icon="radix-icons:eye-open" />
<Icon v-else class="text-light-50 dark:text-dark-50" icon="radix-icons:eye-none" />
</span>
</div>
</div>
</template>
<template #hint="{ instruction }">
<div v-if="instruction" class="absolute h-full w-full top-0 right-0 border-light-50 dark:border-dark-50" :style="{
width: `calc(100% - ${instruction.currentLevel - 1}em)`
}" :class="{
'!border-b-4': instruction?.type === 'reorder-below',
'!border-t-4': instruction?.type === 'reorder-above',
'!border-4': instruction?.type === 'make-child',
}"></div>
</template>
</DraggableTree>
</div>
</div>
<div class="flex flex-1 flex-row">
<div v-if="selected" class="xl:p-12 lg:p-8 py-4 flex flex-1 flex-col items-start justify-start max-h-full">
<Head>
<Title>d[any] - Modification de {{ selected.title }}</Title>
</Head>
<div class="">
<input type="text" v-model="selected.title" placeholder="Titre" class="inline h-16 w-full 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 py-1 text-5xl font-thin bg-transparent" />
<div class="flex flex-col justify-start items-start">
<Switch label="Chemin personnalisé" v-model="selected.customPath" />
<span>
<pre v-if="selected.customPath" class="flex items-center">/{{ selected.parent !== '' ? selected.parent + '/' : '' }}<TextInput v-model="selected.name" @input="(e) => {
if(selected && selected.customPath)
{
selected.name = parsePath(selected.name);
rebuildPath(selected.children, getPath(selected));
}
}" class="mx-0"/></pre>
<pre v-else>/{{ getPath(selected) }}</pre>
</span>
</div>
</div>
<div class="py-4 flex-1 w-full max-h-full flex overflow-hidden">
<template v-if="selected.type === 'markdown'">
<div v-if="contentStatus === 'pending'" class="flex flex-1 justify-center items-center">
<Loading />
</div>
<span v-else-if="contentError">{{ contentError }}</span>
<SplitterGroup direction="horizontal" class="flex-1 w-full flex" v-else-if="selected.content !== undefined">
<SplitterPanel asChild collapsible :collapsedSize="0" :minSize="20" v-slot="{ isCollapsed }" :defaultSize="50">
<Editor v-model="selected.content" placeholder="Commencer votre aventure ..." class="flex-1 bg-transparent appearance-none outline-none max-h-full resize-none !overflow-y-auto" :class="{ 'hidden': isCollapsed }" />
</SplitterPanel>
<SplitterResizeHandle class="bg-light-35 dark:bg-dark-35 w-px xl!mx-4 mx-2" />
<SplitterPanel asChild collapsible :collapsedSize="0" :minSize="20" v-slot="{ isCollapsed }">
<div class="flex-1 max-h-full !overflow-y-auto px-8" :class="{ 'hidden': isCollapsed }"><Markdown :content="debounced" :proses="{ 'a': FakeA }" /></div>
</SplitterPanel>
</SplitterGroup>
</template>
<template v-else-if="selected.type === 'canvas'">
<span class="flex flex-1 justify-center items-center"><ProseH3>Editeur de graphe en cours de développement</ProseH3></span>
</template>
<template v-else-if="selected.type === 'file'">
<span>Modifier le contenu :</span><input type="file" @change="(e) => console.log(e)" />
</template>
<div class="py-4 flex-1 w-full max-h-full flex overflow-hidden">
<template v-if="selected.type === 'markdown'">
<div v-if="contentStatus === 'pending'" class="flex flex-1 justify-center items-center">
<Loading />
</div>
<span v-else-if="contentError">{{ contentError }}</span>
<SplitterGroup direction="horizontal" class="flex-1 w-full flex" v-else-if="selected.content !== undefined">
<SplitterPanel asChild collapsible :collapsedSize="0" :minSize="20" v-slot="{ isCollapsed }" :defaultSize="50">
<Editor v-model="selected.content" placeholder="Commencer votre aventure ..." class="flex-1 bg-transparent appearance-none outline-none max-h-full resize-none !overflow-y-auto" :class="{ 'hidden': isCollapsed }" />
</SplitterPanel>
<SplitterResizeHandle class="bg-light-35 dark:bg-dark-35 w-px xl!mx-4 mx-2" />
<SplitterPanel asChild collapsible :collapsedSize="0" :minSize="20" v-slot="{ isCollapsed }">
<div class="flex-1 max-h-full !overflow-y-auto px-8" :class="{ 'hidden': isCollapsed }"><Markdown :content="debounced" :proses="{ 'a': FakeA }" /></div>
</SplitterPanel>
</SplitterGroup>
</template>
<template v-else-if="selected.type === 'canvas'">
<span class="flex flex-1 justify-center items-center"><ProseH3>Editeur de graphe en cours de développement</ProseH3></span>
</template>
<template v-else-if="selected.type === 'file'">
<span>Modifier le contenu :</span><input type="file" @change="(e) => console.log(e)" />
</template>
</div>
</div>
</div>
</div>
</div>
</div>
</CollapsibleRoot>
</ClientOnly>
</template>
@ -188,6 +197,7 @@ definePageMeta({
});
const router = useRouter();
const open = ref(true);
const toaster = useToast();
const saveStatus = ref<'idle' | 'pending' | 'success' | 'error'>('idle');

View File

@ -0,0 +1,40 @@
import { hasPermissions } from "~/shared/auth.util";
import useDatabase from '~/composables/useDatabase';
import { and, eq, notInArray } from "drizzle-orm";
import { z } from "zod";
import { userSessionsTable } from "~/db/schema";
const schema = z.array(z.string());
export default defineEventHandler(async (e) => {
const session = await getUserSession(e);
if(!session || !session.user || !hasPermissions(session.user.permissions, ['admin']))
{
throw createError({
statusCode: 401,
message: 'Unauthorized',
});
}
const param = getRouterParam(e, 'id');
if(!param)
{
throw createError({
statusCode: 403,
message: 'Forbidden',
});
}
try {
const id = parseInt(param, 10);
const db = useDatabase();
db.delete(userSessionsTable).where(eq(userSessionsTable.user_id, id)).run();
} catch(e) {
console.error(e);
throw e;
}
});