Starting new file format "Map". Preparing editor for Map and Canvas editor with metadata folding UI. Fix comments filtering.
This commit is contained in:
parent
896af11fa7
commit
6f305397a8
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<template v-for="(item, idx) of options">
|
<template v-for="(item, idx) of options">
|
||||||
<template v-if="item.type === 'item'">
|
<template v-if="item.type === 'item'">
|
||||||
<DropdownMenuItem :disabled="item.disabled" :textValue="item.label" @select="item.select" :class="{'!pe-1': item.kbd}" class="group cursor-pointer text-base text-light-100 dark:text-dark-100 leading-none flex items-center py-1.5 relative ps-7 pe-4 select-none outline-none data-[disabled]:text-light-60 dark:data-[disabled]:text-dark-60 data-[disabled]:pointer-events-none data-[highlighted]:bg-light-35 dark:data-[highlighted]:bg-dark-35">
|
<DropdownMenuItem :disabled="item.disabled" :textValue="item.label" @select="item.select" :class="{'!pe-1': item.kbd}" class="cursor-pointer text-base text-light-100 dark:text-dark-100 leading-none flex items-center py-1.5 relative ps-7 pe-4 select-none outline-none data-[disabled]:text-light-60 dark:data-[disabled]:text-dark-60 data-[disabled]:pointer-events-none data-[highlighted]:bg-light-35 dark:data-[highlighted]:bg-dark-35">
|
||||||
<Icon v-if="item.icon" :icon="item.icon" class="absolute left-1.5" />
|
<Icon v-if="item.icon" :icon="item.icon" class="absolute left-1.5" />
|
||||||
<div class="flex flex-1 justify-between">
|
<div class="flex flex-1 justify-between">
|
||||||
<span>{{ item.label }}</span>
|
<span>{{ item.label }}</span>
|
||||||
|
|
@ -10,14 +10,17 @@
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- TODO -->
|
|
||||||
<template v-else-if="item.type === 'checkbox'">
|
<template v-else-if="item.type === 'checkbox'">
|
||||||
<DropdownMenuCheckboxItem :disabled="item.disabled" :textValue="item.label" @update:checked="item.select">
|
<DropdownMenuCheckboxItem :disabled="item.disabled" :textValue="item.label" v-model:checked="item.checked" @update:checked="item.select" class="cursor-pointer text-base text-light-100 dark:text-dark-100 leading-none flex items-center py-1.5 relative pe-4 select-none outline-none data-[disabled]:text-light-60 dark:data-[disabled]:text-dark-60 data-[disabled]:pointer-events-none data-[highlighted]:bg-light-35 dark:data-[highlighted]:bg-dark-35">
|
||||||
<DropdownMenuItemIndicator>
|
<span class="w-6 flex items-center justify-center">
|
||||||
<Icon icon="radix-icons:check" />
|
<DropdownMenuItemIndicator>
|
||||||
</DropdownMenuItemIndicator>
|
<Icon icon="radix-icons:check" />
|
||||||
<span>{{ item.label }}</span>
|
</DropdownMenuItemIndicator>
|
||||||
<span v-if="item.kbd"> {{ item.kbd }} </span>
|
</span>
|
||||||
|
<div class="flex flex-1 justify-between">
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
<span v-if="item.kbd" class="mx-2 text-xs font-mono text-light-70 dark:text-dark-70 relative top-0.5"> {{ item.kbd }} </span>
|
||||||
|
</div>
|
||||||
</DropdownMenuCheckboxItem>
|
</DropdownMenuCheckboxItem>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
<HoverCardPortal v-if="!disabled">
|
<HoverCardPortal v-if="!disabled">
|
||||||
<HoverCardContent :class="$attrs.class" :side="side" class="data-[side=bottom]:animate-slideUpAndFade data-[side=right]:animate-slideLeftAndFade data-[side=left]:animate-slideRightAndFade data-[side=top]:animate-slideDownAndFade w-[300px] bg-light-10 dark:bg-dark-10 border border-light-35 dark:border-dark-35 p-5 data-[state=open]:transition-all text-light-100 dark:text-dark-100" >
|
<HoverCardContent :class="$attrs.class" :side="side" class="max-h-[var(--radix-hover-card-content-available-height)] data-[side=bottom]:animate-slideUpAndFade data-[side=right]:animate-slideLeftAndFade data-[side=left]:animate-slideRightAndFade data-[side=top]:animate-slideDownAndFade w-[300px] bg-light-10 dark:bg-dark-10 border border-light-35 dark:border-dark-35 p-5 data-[state=open]:transition-all text-light-100 dark:text-dark-100" >
|
||||||
<slot name="content"></slot>
|
<slot name="content"></slot>
|
||||||
<HoverCardArrow class="fill-light-35 dark:fill-dark-35" />
|
<HoverCardArrow class="fill-light-35 dark:fill-dark-35" />
|
||||||
</HoverCardContent>
|
</HoverCardContent>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<NuxtLink class="text-accent-blue inline-flex items-center" :to="overview ? { name: 'explore-path', params: { path: overview.path }, hash: hash } : href" :class="class">
|
<NuxtLink class="text-accent-blue inline-flex items-center" :to="overview ? { name: 'explore-path', params: { path: overview.path }, hash: hash } : href" :class="class">
|
||||||
<HoverCard nuxt-client class="max-w-[600px] max-h-[600px] w-full overflow-auto z-[45]" :class="{'overflow-hidden !p-0': overview?.type === 'canvas'}" :disabled="!overview">
|
<HoverCard nuxt-client class="max-w-[600px] max-h-[600px] w-full overflow-auto z-[45]" :class="{'overflow-hidden !p-0': overview?.type === 'canvas'}" :disabled="!overview">
|
||||||
<template #content>
|
<template #content>
|
||||||
<Markdown v-if="overview?.type === 'markdown'" class="!px-10" :path="pathname" :filter="hash.substring(1)" popover />
|
<Markdown v-if="overview?.type === 'markdown'" class="!px-6" :path="pathname" :filter="hash.substring(1)" popover />
|
||||||
<template v-else-if="overview?.type === 'canvas'"><div class="w-[600px] h-[600px] relative"><Canvas :path="pathname" /></div></template>
|
<template v-else-if="overview?.type === 'canvas'"><div class="w-[600px] h-[600px] relative"><Canvas :path="pathname" /></div></template>
|
||||||
</template>
|
</template>
|
||||||
<span>
|
<span>
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
<CollapsibleRoot :disabled="disabled" :defaultOpen="fold === true || fold === undefined" class="callout group overflow-hidden my-4 p-3 ps-4 bg-blend-lighten !bg-opacity-25 border-l-4 inline-block pe-8 bg-light-blue dark:bg-dark-blue" :data-type="type">
|
<CollapsibleRoot :disabled="disabled" :defaultOpen="fold === true || fold === undefined" class="callout group overflow-hidden my-4 p-3 ps-4 bg-blend-lighten !bg-opacity-25 border-l-4 inline-block pe-8 bg-light-blue dark:bg-dark-blue" :data-type="type">
|
||||||
<CollapsibleTrigger>
|
<CollapsibleTrigger>
|
||||||
<div :class="{ 'cursor-pointer': fold !== undefined }" class="flex flex-row items-center justify-start ps-2">
|
<div :class="{ 'cursor-pointer': fold !== undefined }" class="flex flex-row items-center justify-start ps-2">
|
||||||
<Icon :icon="calloutIconByType[type] ?? defaultCalloutIcon" class="w-6 h-6 stroke-2 float-start me-2" />
|
<Icon :icon="calloutIconByType[type] ?? defaultCalloutIcon" inline class="w-6 h-6 stroke-2 float-start me-2 flex-shrink-0" />
|
||||||
<span v-if="title" class="block font-bold">{{ title }}</span>
|
<span v-if="title" class="block font-bold text-start">{{ title }}</span>
|
||||||
<Icon icon="radix-icons:caret-right" v-if="fold !== undefined" class="transition-transform group-data-[state=open]:rotate-90 w-6 h-6 mx-6" />
|
<Icon icon="radix-icons:caret-right" v-if="fold !== undefined" class="transition-transform group-data-[state=open]:rotate-90 w-6 h-6 mx-6" />
|
||||||
</div>
|
</div>
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import RemarkParse from "remark-parse";
|
||||||
import RemarkRehype from 'remark-rehype';
|
import RemarkRehype from 'remark-rehype';
|
||||||
import RemarkOfm from 'remark-ofm';
|
import RemarkOfm from 'remark-ofm';
|
||||||
import RemarkGfm from 'remark-gfm';
|
import RemarkGfm from 'remark-gfm';
|
||||||
|
import RemarkBreaks from 'remark-breaks';
|
||||||
import RemarkFrontmatter from 'remark-frontmatter';
|
import RemarkFrontmatter from 'remark-frontmatter';
|
||||||
|
|
||||||
export default function useMarkdown(): (md: string) => Root
|
export default function useMarkdown(): (md: string) => Root
|
||||||
|
|
@ -14,7 +15,7 @@ export default function useMarkdown(): (md: string) => Root
|
||||||
const parse = (markdown: string) => {
|
const parse = (markdown: string) => {
|
||||||
if (!processor)
|
if (!processor)
|
||||||
{
|
{
|
||||||
processor = unified().use([RemarkParse, RemarkGfm, RemarkOfm, RemarkFrontmatter]);
|
processor = unified().use([RemarkParse, RemarkGfm, RemarkOfm, RemarkBreaks, RemarkFrontmatter]);
|
||||||
processor.use(RemarkRehype);
|
processor.use(RemarkRehype);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
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,7 +39,7 @@ export const explorerContentTable = sqliteTable("explorer_content", {
|
||||||
path: text().primaryKey(),
|
path: text().primaryKey(),
|
||||||
owner: int().notNull().references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
owner: int().notNull().references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||||
title: text().notNull(),
|
title: text().notNull(),
|
||||||
type: text({ enum: ['file', 'folder', 'markdown', 'canvas'] }).notNull(),
|
type: text({ enum: ['file', 'folder', 'markdown', 'canvas', 'map'] }).notNull(),
|
||||||
content: blob({ mode: 'buffer' }),
|
content: blob({ mode: 'buffer' }),
|
||||||
navigable: int({ mode: 'boolean' }).notNull().default(true),
|
navigable: int({ mode: 'boolean' }).notNull().default(true),
|
||||||
private: int({ mode: 'boolean' }).notNull().default(false),
|
private: int({ mode: 'boolean' }).notNull().default(false),
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
<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">
|
<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>
|
<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">
|
<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>
|
<AlertDialogAction asChild @click="() => { navigation = tree.remove(navigation, getPath(selected!)); selected = undefined; }"><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>
|
<AlertDialogCancel asChild><Button>Non</Button></AlertDialogCancel>
|
||||||
</div>
|
</div>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
|
|
@ -58,7 +58,7 @@
|
||||||
type: 'item',
|
type: 'item',
|
||||||
label: 'Markdown',
|
label: 'Markdown',
|
||||||
kbd: 'Ctrl+N',
|
kbd: 'Ctrl+N',
|
||||||
icon: 'radix-icons:file',
|
icon: 'radix-icons:file-text',
|
||||||
select: () => add('markdown'),
|
select: () => add('markdown'),
|
||||||
}, {
|
}, {
|
||||||
type: 'item',
|
type: 'item',
|
||||||
|
|
@ -71,10 +71,15 @@
|
||||||
label: 'Canvas',
|
label: 'Canvas',
|
||||||
icon: 'ph:graph-light',
|
icon: 'ph:graph-light',
|
||||||
select: () => add('canvas'),
|
select: () => add('canvas'),
|
||||||
|
}, {
|
||||||
|
type: 'item',
|
||||||
|
label: 'Carte',
|
||||||
|
icon: 'lucide:map',
|
||||||
|
select: () => add('map'),
|
||||||
}, {
|
}, {
|
||||||
type: 'item',
|
type: 'item',
|
||||||
label: 'Fichier',
|
label: 'Fichier',
|
||||||
icon: 'radix-icons:file-text',
|
icon: 'radix-icons:file',
|
||||||
select: () => add('file'),
|
select: () => add('file'),
|
||||||
}]">
|
}]">
|
||||||
<Button icon><Icon class="w-5 h-5" icon="radix-icons:plus" /></Button>
|
<Button icon><Icon class="w-5 h-5" icon="radix-icons:plus" /></Button>
|
||||||
|
|
@ -120,27 +125,45 @@
|
||||||
</div>
|
</div>
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
<div class="flex flex-1 flex-row max-h-full overflow-hidden">
|
<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">
|
<div v-if="selected" class="flex flex-1 flex-col items-start justify-start max-h-full relative">
|
||||||
<Head>
|
<Head>
|
||||||
<Title>d[any] - Modification de {{ selected.title }}</Title>
|
<Title>d[any] - Modification de {{ selected.title }}</Title>
|
||||||
</Head>
|
</Head>
|
||||||
<div>
|
<CollapsibleRoot v-model:open="topOpen" class="my-4 w-full relative">
|
||||||
<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" />
|
<CollapsibleTrigger asChild>
|
||||||
<div class="flex flex-col justify-start items-start">
|
<Button class="absolute left-1/2 -translate-x-1/2 -bottom-3" icon>
|
||||||
<Switch label="Chemin personnalisé" v-model="selected.customPath" />
|
<Icon v-if="topOpen" icon="radix-icons:caret-up" class="h-4 w-4" />
|
||||||
<span>
|
<Icon v-else icon="radix-icons:caret-down" class="h-4 w-4" />
|
||||||
<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) => {
|
</Button>
|
||||||
if(selected && selected.customPath)
|
</CollapsibleTrigger>
|
||||||
{
|
<CollapsibleContent class="xl:px-12 lg:px-8 px-6">
|
||||||
selected.name = parsePath(selected.name);
|
<div class="pb-2 grid lg:grid-cols-2 grid-cols-1 lg:items-center justify-between gap-x-4 flex-1 border-b border-light-35 dark:border-dark-35">
|
||||||
rebuildPath(selected.children, getPath(selected));
|
<input type="text" v-model="selected.title" placeholder="Titre" style="line-height: normal;" class="flex-1 md:text-5xl text-4xl md:h-14 h-12 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 pb-3 font-thin bg-transparent"/>
|
||||||
}
|
<div class="flex flex-row justify-between items-center gap-x-4">
|
||||||
}" class="mx-0"/></pre>
|
<div v-if="selected.customPath" class="flex lg:items-center truncate">
|
||||||
<pre v-else class="md:text-base text-smmd:text-nowrap text-wrap ">/{{ getPath(selected) }}</pre>
|
<pre class="md:text-base text-sm truncate" style="direction: rtl">/{{ selected.parent !== '' ? selected.parent + '/' : '' }}</pre>
|
||||||
</span>
|
<TextInput v-model="selected.name" @input="(e) => {
|
||||||
</div>
|
if(selected && selected.customPath)
|
||||||
</div>
|
{
|
||||||
<div class="py-4 flex-1 w-full max-h-full flex overflow-hidden">
|
selected.name = parsePath(selected.name);
|
||||||
|
rebuildPath(selected.children, getPath(selected));
|
||||||
|
}
|
||||||
|
}" class="mx-0 font-mono"/>
|
||||||
|
</div>
|
||||||
|
<pre v-else class="md:text-base text-sm truncate" style="direction: rtl">{{ getPath(selected) }}/</pre>
|
||||||
|
<DropdownMenu align="end" :options="[{
|
||||||
|
type: 'checkbox',
|
||||||
|
label: 'URL custom',
|
||||||
|
select: (state: boolean) => selected!.customPath = state,
|
||||||
|
checked: selected.customPath
|
||||||
|
}]">
|
||||||
|
<Button class="" icon><Icon icon="radix-icons:dots-vertical"/></Button>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</CollapsibleRoot>
|
||||||
|
<div class="py-4 flex-1 w-full max-h-full flex overflow-hidden xl:px-12 lg:px-8 px-6">
|
||||||
<template v-if="selected.type === 'markdown'">
|
<template v-if="selected.type === 'markdown'">
|
||||||
<div v-if="contentStatus === 'pending'" class="flex flex-1 justify-center items-center">
|
<div v-if="contentStatus === 'pending'" class="flex flex-1 justify-center items-center">
|
||||||
<Loading />
|
<Loading />
|
||||||
|
|
@ -159,8 +182,11 @@
|
||||||
<template v-else-if="selected.type === 'canvas'">
|
<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>
|
<span class="flex flex-1 justify-center items-center"><ProseH3>Editeur de graphe en cours de développement</ProseH3></span>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="selected.type === 'map'">
|
||||||
|
<span class="flex flex-1 justify-center items-center"><ProseH3>Editeur de carte en cours de développement</ProseH3></span>
|
||||||
|
</template>
|
||||||
<template v-else-if="selected.type === 'file'">
|
<template v-else-if="selected.type === 'file'">
|
||||||
<span>Modifier le contenu :</span><input type="file" @change="(e) => console.log(e)" />
|
<span>Modifier le contenu :</span><input type="file" @change="(e) => console.log((e.target as HTMLInputElement).files?.length)" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -193,7 +219,7 @@ definePageMeta({
|
||||||
});
|
});
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const open = ref(true);
|
const open = ref(true), topOpen = ref(true);
|
||||||
|
|
||||||
const toaster = useToast();
|
const toaster = useToast();
|
||||||
const saveStatus = ref<'idle' | 'pending' | 'success' | 'error'>('idle');
|
const saveStatus = ref<'idle' | 'pending' | 'success' | 'error'>('idle');
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export const fileType = z.enum(['folder', 'file', 'markdown', 'canvas']);
|
export const fileType = z.enum(['folder', 'file', 'markdown', 'canvas', 'map']);
|
||||||
export const schema = z.object({
|
export const schema = z.object({
|
||||||
path: z.string(),
|
path: z.string(),
|
||||||
owner: z.number().finite(),
|
owner: z.number().finite(),
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,7 @@ export default defineEventHandler(async (e) => {
|
||||||
{
|
{
|
||||||
const session = await getUserSession(e);
|
const session = await getUserSession(e);
|
||||||
|
|
||||||
if(content.private && (!session || !session.user))
|
if(!session || !session.user || session.user.id !== content.owner)
|
||||||
{
|
|
||||||
setResponseStatus(e, 404);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(session && session.user && session.user.id !== content.owner)
|
|
||||||
{
|
{
|
||||||
if(content.private)
|
if(content.private)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -51,5 +51,6 @@ export const iconByType: Record<FileType, string> = {
|
||||||
'folder': 'lucide:folder',
|
'folder': 'lucide:folder',
|
||||||
'canvas': 'ph:graph-light',
|
'canvas': 'ph:graph-light',
|
||||||
'file': 'radix-icons:file',
|
'file': 'radix-icons:file',
|
||||||
'markdown': 'radix-icons:file',
|
'markdown': 'radix-icons:file-text',
|
||||||
|
'map': 'lucide:map',
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue