Polish CSS for mobile editor. Add user logout from admin panel.
This commit is contained in:
parent
ecdfa947ac
commit
51a5d501be
2
app.vue
2
app.vue
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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-shm
BIN
db.sqlite-shm
Binary file not shown.
BIN
db.sqlite-wal
BIN
db.sqlite-wal
Binary file not shown.
|
|
@ -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">
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -1,22 +1,27 @@
|
|||
<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">
|
||||
<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">
|
||||
<Button icon class="ms-2 !bg-transparent group" disabled>
|
||||
<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>
|
||||
<span class="text-light-100 dark:text-dark-100 hover:text-opacity-70 max-md:ps-6" aria-label="Accueil">Accueil</span>
|
||||
</CollapsibleTrigger>
|
||||
</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 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" />
|
||||
|
|
@ -27,8 +32,8 @@
|
|||
</div>
|
||||
</div>
|
||||
</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-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>
|
||||
|
|
@ -36,10 +41,10 @@
|
|||
</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>
|
||||
<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 flex-row items-center">
|
||||
<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>
|
||||
|
|
@ -48,6 +53,7 @@
|
|||
</AlertDialogContent>
|
||||
</AlertDialogPortal>
|
||||
</AlertDialogRoot>
|
||||
<Tooltip side="top" message="Nouveau">
|
||||
<DropdownMenu align="center" side="bottom" :options="[{
|
||||
type: 'item',
|
||||
label: 'Markdown',
|
||||
|
|
@ -71,20 +77,21 @@
|
|||
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>
|
||||
<Button icon><Icon class="w-5 h-5" icon="radix-icons:plus" /></Button>
|
||||
</DropdownMenu>
|
||||
</Tooltip>
|
||||
</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"
|
||||
<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" :class="{ 'opacity-50': isDragging }" :style="{ 'padding-left': `${item.level - 0.5}em` }">
|
||||
<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">
|
||||
<div class="pl-3 py-1 flex-1 truncate" :title="item.value.title" @click="handleSelect" :class="{ 'font-semibold': item.hasChildren }">
|
||||
{{ item.value.title }}
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
|
|
@ -111,24 +118,25 @@
|
|||
</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">
|
||||
</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 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>
|
||||
<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 items-center">/{{ selected.parent !== '' ? selected.parent + '/' : '' }}<TextInput v-model="selected.name" @input="(e) => {
|
||||
<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>/{{ getPath(selected) }}</pre>
|
||||
<pre v-else class="md:text-base text-smmd:text-nowrap text-wrap ">/{{ getPath(selected) }}</pre>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -159,6 +167,7 @@
|
|||
</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');
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
Loading…
Reference in New Issue