Add order in file schema and main projecct edition
This commit is contained in:
parent
b54402fc19
commit
adb37b255a
|
|
@ -2,7 +2,7 @@
|
|||
import { useDrag, usePinch, useWheel } from '@vueuse/gesture';
|
||||
import type { CanvasContent, CanvasNode } from '~/types/canvas';
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
import { clamp } from '#imports';
|
||||
import { clamp } from '#shared/general.utils';
|
||||
|
||||
interface Props
|
||||
{
|
||||
|
|
@ -151,16 +151,12 @@ dark:border-dark-purple
|
|||
*/
|
||||
|
||||
const pinchHandler = usePinch(({ event, offset: [z] }: { event: Event, offset: number[] }) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
zoom.value = clamp(z / 2048, minZoom.value, 3);
|
||||
}, {
|
||||
domTarget: canvas,
|
||||
eventOptions: { passive: false, }
|
||||
})
|
||||
const dragHandler = useDrag(({ event, delta: [x, y] }: { event: Event, delta: number[] }) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
dispX.value += x / zoom.value;
|
||||
dispY.value += y / zoom.value;
|
||||
}, {
|
||||
|
|
@ -168,8 +164,6 @@ const dragHandler = useDrag(({ event, delta: [x, y] }: { event: Event, delta: nu
|
|||
eventOptions: { passive: false, }
|
||||
})
|
||||
const wheelHandler = useWheel(({ event, delta: [x, y] }: { event: Event, delta: number[] }) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
zoom.value = clamp(zoom.value + y * -0.001, minZoom.value, 3);
|
||||
}, {
|
||||
domTarget: canvas,
|
||||
|
|
|
|||
|
|
@ -5,5 +5,6 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { parseId } from '#shared/general.utils';
|
||||
const props = defineProps<{ id?: string }>()
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { parseId } from '#shared/general.utils';
|
||||
const props = defineProps<{ id?: string }>()
|
||||
|
||||
const generate = computed(() => props.id)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { parseId } from '#shared/general.utils';
|
||||
const props = defineProps<{ id?: string }>()
|
||||
|
||||
const generate = computed(() => props.id)
|
||||
|
|
|
|||
|
|
@ -5,5 +5,6 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { parseId } from '#shared/general.utils';
|
||||
const props = defineProps<{ id?: string }>()
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { parseId } from '#shared/general.utils';
|
||||
const props = defineProps<{ id?: string }>()
|
||||
|
||||
const generate = computed(() => props.id)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { parseId } from '#shared/general.utils';
|
||||
const props = defineProps<{ id?: string }>()
|
||||
|
||||
const generate = computed(() => props.id)
|
||||
|
|
|
|||
BIN
db.sqlite-shm
BIN
db.sqlite-shm
Binary file not shown.
BIN
db.sqlite-wal
BIN
db.sqlite-wal
Binary file not shown.
|
|
@ -39,8 +39,9 @@ export const explorerContentTable = sqliteTable("explorer_content", {
|
|||
title: text().notNull(),
|
||||
type: text({ enum: ['file', 'folder', 'markdown', 'canvas'] }).notNull(),
|
||||
content: blob({ mode: 'buffer' }),
|
||||
navigable: int({ mode: 'boolean' }).default(true),
|
||||
private: int({ mode: 'boolean' }).default(false),
|
||||
navigable: int({ mode: 'boolean' }).notNull().default(true),
|
||||
private: int({ mode: 'boolean' }).notNull().default(false),
|
||||
order: int().unique('order').notNull(),
|
||||
});
|
||||
|
||||
export const usersRelation = relations(usersTable, ({ one, many }) => ({
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE `explorer_content` ADD `order` integer;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `order` ON `explorer_content` (`order`);
|
||||
|
|
@ -0,0 +1,313 @@
|
|||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "a1a7b478-d0c3-4fc6-b74a-1a010c1d8ca1",
|
||||
"prevId": "6da7ff20-0db8-4055-a353-bb0ea2fa5e0b",
|
||||
"tables": {
|
||||
"explorer_content": {
|
||||
"name": "explorer_content",
|
||||
"columns": {
|
||||
"path": {
|
||||
"name": "path",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"owner": {
|
||||
"name": "owner",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"content": {
|
||||
"name": "content",
|
||||
"type": "blob",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"navigable": {
|
||||
"name": "navigable",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": true
|
||||
},
|
||||
"private": {
|
||||
"name": "private",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
},
|
||||
"order": {
|
||||
"name": "order",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"order": {
|
||||
"name": "order",
|
||||
"columns": [
|
||||
"order"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"explorer_content_owner_users_id_fk": {
|
||||
"name": "explorer_content_owner_users_id_fk",
|
||||
"tableFrom": "explorer_content",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"owner"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"user_permissions": {
|
||||
"name": "user_permissions",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"permission": {
|
||||
"name": "permission",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_permissions_id_users_id_fk": {
|
||||
"name": "user_permissions_id_users_id_fk",
|
||||
"tableFrom": "user_permissions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"user_permissions_id_permission_pk": {
|
||||
"columns": [
|
||||
"id",
|
||||
"permission"
|
||||
],
|
||||
"name": "user_permissions_id_permission_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"user_sessions": {
|
||||
"name": "user_sessions",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_sessions_user_id_users_id_fk": {
|
||||
"name": "user_sessions_user_id_users_id_fk",
|
||||
"tableFrom": "user_sessions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"user_sessions_id_user_id_pk": {
|
||||
"columns": [
|
||||
"id",
|
||||
"user_id"
|
||||
],
|
||||
"name": "user_sessions_id_user_id_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"users_data": {
|
||||
"name": "users_data",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"signin": {
|
||||
"name": "signin",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"users_data_id_users_id_fk": {
|
||||
"name": "users_data_id_users_id_fk",
|
||||
"tableFrom": "users_data",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"users": {
|
||||
"name": "users",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"hash": {
|
||||
"name": "hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"state": {
|
||||
"name": "state",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"users_username_unique": {
|
||||
"name": "users_username_unique",
|
||||
"columns": [
|
||||
"username"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"users_email_unique": {
|
||||
"name": "users_email_unique",
|
||||
"columns": [
|
||||
"email"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"users_hash_unique": {
|
||||
"name": "users_hash_unique",
|
||||
"columns": [
|
||||
"hash"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,13 @@
|
|||
"when": 1730985155814,
|
||||
"tag": "0002_messy_solo",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "6",
|
||||
"when": 1731344368953,
|
||||
"tag": "0003_cultured_skaar",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
<div class="text-3xl">Une erreur est survenue.</div>
|
||||
</div>
|
||||
<pre class="">Erreur {{ error?.statusCode }}: {{ error?.message }}</pre>
|
||||
<NuxtLink :href="{ name: 'index' }"><Button>Revenir en lieu sûr</Button></NuxtLink>
|
||||
<Button @click="handleError">Revenir en lieu sûr</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,12 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Tree v-if="pages" v-model="pages" class="flex-1 xl:px-6 px-3 max-w-full max-h-full overflow-y-auto overflow-x-hidden"/>
|
||||
<div class="flex-1 xl:px-6 px-3 max-w-full max-h-full overflow-y-auto overflow-x-hidden">
|
||||
<NuxtLink :href="{ name: 'explore' }" no-prefetch class="flex flex-1 font-bold text-lg items-center border-light-35 dark:border-dark-35 hover:border-accent-blue" active-class="text-accent-blue border-s-2 !border-accent-blue">
|
||||
<div class="pl-3 py-1 flex-1 truncate">Projet</div>
|
||||
</NuxtLink>
|
||||
<Tree v-if="pages" v-model="pages"/>
|
||||
</div>
|
||||
<div class="xl:px-12 px-6 text-start text-xs text-light-60 dark:text-dark-60 relative top-4">
|
||||
<NuxtLink class="hover:underline italic" :to="{ name: 'legal' }">Mentions légales</NuxtLink>
|
||||
<p>Copyright Peaceultime - 2024</p>
|
||||
|
|
@ -62,6 +67,7 @@ const { loggedIn } = useUserSession();
|
|||
|
||||
const { data: pages } = await useLazyFetch('/api/navigation', {
|
||||
transform: transform,
|
||||
watch: [useRouter().currentRoute]
|
||||
});
|
||||
|
||||
watch(useRouter().currentRoute, () => {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { hasPermissions } from "#shared/auth.util";
|
||||
|
||||
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||
const { loggedIn, fetch, user } = useUserSession();
|
||||
const meta = to.meta;
|
||||
|
|
|
|||
18
package.json
18
package.json
|
|
@ -10,20 +10,36 @@
|
|||
"@iconify/vue": "^4.1.2",
|
||||
"@nuxtjs/color-mode": "^3.5.2",
|
||||
"@nuxtjs/tailwindcss": "^6.12.2",
|
||||
"@vueuse/gesture": "^2.0.0",
|
||||
"@vueuse/nuxt": "^11.1.0",
|
||||
"codemirror": "^6.0.1",
|
||||
"drizzle-orm": "^0.35.3",
|
||||
"hast": "^1.0.0",
|
||||
"lodash.capitalize": "^4.2.1",
|
||||
"mdast-util-find-and-replace": "^3.0.1",
|
||||
"nuxt": "^3.14.159",
|
||||
"nuxt-security": "^2.0.0",
|
||||
"radix-vue": "^1.9.8",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-breaks": "^4.0.0",
|
||||
"remark-frontmatter": "^5.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.1",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"vue": "latest",
|
||||
"vue-router": "latest",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.1.12",
|
||||
"@types/lodash.capitalize": "^4.2.9",
|
||||
"@types/unist": "^3.0.3",
|
||||
"better-sqlite3": "^11.5.0",
|
||||
"bun-types": "^1.1.34",
|
||||
"drizzle-kit": "^0.26.2"
|
||||
"drizzle-kit": "^0.26.2",
|
||||
"mdast-util-to-string": "^4.0.0",
|
||||
"rehype-stringify": "^10.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,14 @@
|
|||
<script setup lang="ts">
|
||||
const route = useRouter().currentRoute;
|
||||
const path = computed(() => Array.isArray(route.value.params.path) ? route.value.params.path[0] : route.value.params.path);
|
||||
|
||||
watch(path, () => {
|
||||
if(path.value === 'index')
|
||||
{
|
||||
useRouter().push({ name: 'explore' });
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
const { loggedIn, user } = useUserSession();
|
||||
|
||||
const { data: page, status, error } = await useFetch(`/api/file/${encodeURIComponent(path.value)}`, { watch: [route, path], });
|
||||
|
|
|
|||
|
|
@ -6,9 +6,14 @@
|
|||
<div class="flex flex-col xl:flex-row xl:justify-between justify-center items-center w-full px-4 pb-4 border-b border-light-35 dark:border-dark-35 bg-light-0 dark:bg-dark-0">
|
||||
<input type="text" v-model="page.title" placeholder="Titre" class="flex-1 mx-4 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 px-3 py-1 text-5xl font-thin bg-transparent" />
|
||||
<div class="flex gap-4 self-end xl:self-auto flex-wrap">
|
||||
<Tooltip message="Consultable uniquement par le propriétaire" side="bottom"><Switch label="Privé" v-model="page.private" /></Tooltip>
|
||||
<Tooltip message="Afficher dans le menu de navigation" side="bottom"><Switch label="Navigable" v-model="page.navigable" /></Tooltip>
|
||||
<Button @click="() => save()" :loading="saveStatus === 'pending'" class="border-light-blue dark:border-dark-blue hover:border-light-blue dark:hover:border-dark-blue focus:shadow-light-blue dark:focus:shadow-dark-blue">Enregistrer</Button>
|
||||
<div class="flex gap-4">
|
||||
<Tooltip message="Consultable uniquement par le propriétaire" side="bottom"><Switch label="Privé" v-model="page.private" /></Tooltip>
|
||||
<Tooltip message="Afficher dans le menu de navigation" side="bottom"><Switch label="Navigable" v-model="page.navigable" /></Tooltip>
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<Button @click="() => save()" :loading="saveStatus === 'pending'" class="border-light-blue dark:border-dark-blue hover:border-light-blue dark:hover:border-dark-blue focus:shadow-light-blue dark:focus:shadow-dark-blue">Enregistrer</Button>
|
||||
<NuxtLink :href="{ name: 'explore-path', params: { path: path } }"><Button>Annuler</Button></NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-4 flex-1 w-full max-h-full flex">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
const { user, loggedIn } = useUserSession();
|
||||
|
||||
const toaster = useToast();
|
||||
const saveStatus = ref<'idle' | 'pending' | 'success' | 'error'>('idle');
|
||||
|
||||
const { data: page, status, error } = await useLazyFetch(`/api/navigation`);
|
||||
|
||||
if(!loggedIn || (page.value && page.value.owner !== user.value?.id))
|
||||
{
|
||||
useRouter().replace({ name: 'explore-path', params: { path: path.value } });
|
||||
}
|
||||
|
||||
async function save(): Promise<void>
|
||||
{
|
||||
saveStatus.value = 'pending';
|
||||
try {
|
||||
await $fetch(`/api/file`, {
|
||||
method: 'post',
|
||||
body: page.value,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
saveStatus.value = 'success';
|
||||
|
||||
toaster.clear('error');
|
||||
toaster.add({
|
||||
type: 'success', content: 'Contenu enregistré', timer: true, duration: 10000
|
||||
});
|
||||
|
||||
useRouter().push({ name: 'explore-path', params: { path: path.value } });
|
||||
} catch(e: any) {
|
||||
toaster.add({
|
||||
type: 'error', content: e.message, timer: true, duration: 10000
|
||||
})
|
||||
saveStatus.value = 'error';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -1,3 +1,42 @@
|
|||
<template>
|
||||
Index
|
||||
<div v-if="status === 'pending'" class="flex">
|
||||
<Head>
|
||||
<Title>d[any] - Chargement</Title>
|
||||
</Head>
|
||||
<Loading />
|
||||
</div>
|
||||
<div class="flex flex-1 justify-start items-start" v-else-if="page">
|
||||
<Head>
|
||||
<Title>d[any] - Accueil</Title>
|
||||
</Head>
|
||||
<div class="flex flex-1 justify-start items-start flex-col xl:px-24 md:px-8 px-4 py-6">
|
||||
<div class="flex flex-1 flex-row justify-between items-center">
|
||||
<ProseH1>{{ page.title }}</ProseH1>
|
||||
<div class="flex gap-4">
|
||||
<NuxtLink :href="{ name: 'explore-edit-path', params: { path: 'index' } }"><Button v-if="isOwner">Modifier la page</Button></NuxtLink>
|
||||
<NuxtLink :href="{ name: 'explore-edit' }"><Button v-if="isOwner">Configurer le projet</Button></NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
<Markdown :content="page.content" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="status === 'error'">
|
||||
<Head>
|
||||
<Title>d[any] - Erreur</Title>
|
||||
</Head>
|
||||
<span>{{ error?.message }}</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<Head>
|
||||
<Title>d[any] - Erreur</Title>
|
||||
</Head>
|
||||
<div><ProseH2>Impossible d'afficher le contenu demandé</ProseH2></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { loggedIn, user } = useUserSession();
|
||||
|
||||
const { data: page, status, error } = await useFetch(`/api/file/index`);
|
||||
const isOwner = computed(() => user.value?.id === page.value?.owner);
|
||||
</script>
|
||||
|
|
@ -1,14 +1,3 @@
|
|||
<script setup lang="ts">
|
||||
const open = ref(false), username = ref(""), price = ref(750), disabled = ref(false), loading = ref(false);
|
||||
|
||||
watch(loading, (value) => {
|
||||
if(value)
|
||||
{
|
||||
setTimeout(() => { open.value = true; loading.value = false }, 1500);
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head>
|
||||
<Title>d[any] - Accueil</Title>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { hasPermissions } from "#shared/auth.util";
|
||||
|
||||
definePageMeta({
|
||||
guestsGoesTo: '/user/login',
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ import { z } from "zod";
|
|||
|
||||
export const schema = z.object({
|
||||
path: z.string(),
|
||||
owner: z.number(),
|
||||
owner: z.number().finite(),
|
||||
title: z.string(),
|
||||
type: z.enum(['folder', 'file', 'markdown', 'canvas']),
|
||||
content: z.string(),
|
||||
navigable: z.boolean(),
|
||||
private: z.boolean(),
|
||||
order: z.number().finite(),
|
||||
});
|
||||
|
||||
export type File = z.infer<typeof schema>;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import { z } from "zod";
|
||||
|
||||
export const single = z.object({
|
||||
path: z.string(),
|
||||
owner: z.number().finite(),
|
||||
title: z.string(),
|
||||
type: z.enum(['folder', 'file', 'markdown', 'canvas']),
|
||||
navigable: z.boolean(),
|
||||
private: z.boolean(),
|
||||
order: z.number().finite(),
|
||||
});
|
||||
export const table = z.array(single);
|
||||
|
||||
export type Navigation = z.infer<typeof table>;
|
||||
export type NavigationItem = z.infer<typeof single>;
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import { hasPermissions } from "#shared/auth.util";
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const session = await getUserSession(e);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import useDatabase from '~/composables/useDatabase';
|
||||
import { explorerContentTable } from '~/db/schema';
|
||||
import type { Navigation } from '~/types/api';
|
||||
import type { Navigation, NavigationItem } from '~/schemas/navigation';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const { user } = await getUserSession(e);
|
||||
|
|
@ -11,11 +11,19 @@ export default defineEventHandler(async (e) => {
|
|||
}*/
|
||||
|
||||
const db = useDatabase();
|
||||
const content = db.select({ path: explorerContentTable.path, title: explorerContentTable.title, type: explorerContentTable.type, private: explorerContentTable.private, navigable: explorerContentTable.navigable, owner: explorerContentTable.owner }).from(explorerContentTable).prepare().all() as (Navigation & { owner: number, navigable: boolean })[];
|
||||
const content = db.select({
|
||||
path: explorerContentTable.path,
|
||||
type: explorerContentTable.type,
|
||||
owner: explorerContentTable.owner,
|
||||
title: explorerContentTable.title,
|
||||
navigable: explorerContentTable.navigable,
|
||||
private: explorerContentTable.private,
|
||||
order: explorerContentTable.order,
|
||||
}).from(explorerContentTable).prepare().all();
|
||||
|
||||
if(content.length > 0)
|
||||
{
|
||||
const navigation: Navigation[] = [];
|
||||
const navigation: Navigation = [];
|
||||
|
||||
for(const idx in content)
|
||||
{
|
||||
|
|
@ -45,7 +53,7 @@ export default defineEventHandler(async (e) => {
|
|||
setResponseStatus(e, 404);
|
||||
});
|
||||
|
||||
function addChild(arr: Navigation[], e: Navigation): void
|
||||
function addChild(arr: Navigation, e: NavigationItem): void
|
||||
{
|
||||
const parent = arr.find(f => e.path.startsWith(f.path));
|
||||
|
||||
|
|
@ -58,6 +66,11 @@ function addChild(arr: Navigation[], e: Navigation): void
|
|||
}
|
||||
else
|
||||
{
|
||||
arr.push({ title: e.title, path: e.path, type: e.type, private: e.private });
|
||||
arr.push({ title: e.title, path: e.path, type: e.type, order: e.order, private: e.private });
|
||||
arr.sort((a, b) => {
|
||||
if(a.order && b.order)
|
||||
return a.order - b.order;
|
||||
return a.title.localeCompare(b.title);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import { hasPermissions } from "#shared/auth.util";
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { explorerContentTable } from '~/db/schema';
|
||||
import { schema } from '~/schemas/navigation';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const { user } = await getUserSession(e);
|
||||
|
||||
if(!user || !hasPermissions(user.permissions, ['admin', 'editor']))
|
||||
{
|
||||
throw createError({ statusCode: 401, statusText: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const body = await readValidatedBody(e, schema.safeParse);
|
||||
|
||||
if(!body.success)
|
||||
{
|
||||
throw body.error;
|
||||
}
|
||||
|
||||
const db = useDatabase();
|
||||
db.transaction((tx) => {
|
||||
for(let i = 0; i < body.data.length; i++)
|
||||
{
|
||||
tx.insert(explorerContentTable).values({
|
||||
path: body.data[i].path,
|
||||
owner: body.data[i].owner,
|
||||
title: body.data[i].title,
|
||||
type: body.data[i].type,
|
||||
navigable: body.data[i].navigable,
|
||||
private: body.data[i].private,
|
||||
order: body.data[i].order,
|
||||
content: Buffer.from('', 'utf-8'),
|
||||
}).onConflictDoUpdate({
|
||||
set: {
|
||||
owner: body.data[i].owner,
|
||||
title: body.data[i].title,
|
||||
type: body.data[i].type,
|
||||
navigable: body.data[i].navigable,
|
||||
private: body.data[i].private,
|
||||
order: body.data[i].order,
|
||||
},
|
||||
target: explorerContentTable.path,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
setResponseStatus(e, 404);
|
||||
});
|
||||
Loading…
Reference in New Issue