Filter ProseA hover cards with the provided hash
This commit is contained in:
parent
cb2c19fada
commit
7bdf6ccd13
|
|
@ -1,26 +1,49 @@
|
||||||
<template>
|
<template>
|
||||||
<template v-if="content && content.length > 0">
|
<template v-if="content && content.length > 0">
|
||||||
<Suspense :timeout="0">
|
<div><MarkdownRenderer #default v-if="data" :node="data" :proses="proses"></MarkdownRenderer></div>
|
||||||
<MarkdownRenderer #default :key="key" v-if="node" :node="node" :proses="proses"></MarkdownRenderer>
|
</template>
|
||||||
<template #fallback><Loading /></template>
|
</template>
|
||||||
</Suspense>
|
|
||||||
</template>
|
<script setup lang="ts">
|
||||||
</template>
|
import type { Component } from 'vue';
|
||||||
|
import { heading } from 'hast-util-heading';
|
||||||
<script setup lang="ts">
|
import { headingRank } from 'hast-util-heading-rank';
|
||||||
import { hash } from 'ohash'
|
import { parseId } from '~/shared/general.utils';
|
||||||
|
import type { Root } from 'hast';
|
||||||
const { content } = defineProps({
|
|
||||||
content: {
|
const { content, proses, filter } = defineProps<{
|
||||||
type: String,
|
content: string
|
||||||
required: true,
|
proses?: Array<string | Component>
|
||||||
},
|
filter?: string
|
||||||
proses: {
|
}>();
|
||||||
type: Object
|
|
||||||
}
|
const parser = useMarkdown(), data = ref<Root>();
|
||||||
})
|
const node = computed(() => content ? parser(content) : undefined);
|
||||||
|
watch([node], () => {
|
||||||
const parser = useMarkdown();
|
if(!node.value)
|
||||||
const key = computed(() => hash(content));
|
data.value = undefined;
|
||||||
const node = computed(() => content ? parser(content) : undefined);
|
else if(!filter)
|
||||||
</script>
|
{
|
||||||
|
data.value = node.value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const start = node.value?.children.findIndex(e => heading(e) && parseId(e.properties.id as string | undefined) === filter) ?? -1;
|
||||||
|
|
||||||
|
if(start === -1)
|
||||||
|
data.value = node.value;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
let end = start;
|
||||||
|
const rank = headingRank(node.value.children[start])!;
|
||||||
|
while(end < node.value.children.length)
|
||||||
|
{
|
||||||
|
end++;
|
||||||
|
if(heading(node.value.children[end]) && headingRank(node.value.children[end])! >= rank)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
data.value = { ...node.value, children: node.value.children.slice(start, end) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { immediate: true, });
|
||||||
|
</script>
|
||||||
|
|
@ -154,71 +154,63 @@ const pinchHandler = usePinch(({ event, offset: [z] }: { event: Event, offset: n
|
||||||
zoom.value = clamp(z / 2048, minZoom.value, 3);
|
zoom.value = clamp(z / 2048, minZoom.value, 3);
|
||||||
}, {
|
}, {
|
||||||
domTarget: canvas,
|
domTarget: canvas,
|
||||||
eventOptions: { passive: false, }
|
|
||||||
})
|
})
|
||||||
const dragHandler = useDrag(({ event, delta: [x, y] }: { event: Event, delta: number[] }) => {
|
const dragHandler = useDrag(({ event, delta: [x, y] }: { event: Event, delta: number[] }) => {
|
||||||
dispX.value += x / zoom.value;
|
dispX.value += x / zoom.value;
|
||||||
dispY.value += y / zoom.value;
|
dispY.value += y / zoom.value;
|
||||||
}, {
|
}, {
|
||||||
domTarget: canvas,
|
domTarget: canvas,
|
||||||
eventOptions: { passive: false, }
|
|
||||||
})
|
})
|
||||||
const wheelHandler = useWheel(({ event, delta: [x, y] }: { event: Event, delta: number[] }) => {
|
const wheelHandler = useWheel(({ event, delta: [x, y] }: { event: Event, delta: number[] }) => {
|
||||||
zoom.value = clamp(zoom.value + y * -0.001, minZoom.value, 3);
|
zoom.value = clamp(zoom.value + y * -0.001, minZoom.value, 3);
|
||||||
}, {
|
}, {
|
||||||
domTarget: canvas,
|
domTarget: canvas,
|
||||||
eventOptions: { passive: false, }
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Suspense>
|
<div>
|
||||||
<template #default>
|
<div id="canvas" ref="canvas" class="absolute top-0 left-0 overflow-hidden w-full h-full touch-none"
|
||||||
<div id="canvas" ref="canvas" class="absolute top-0 left-0 overflow-hidden w-full h-full touch-none"
|
:style="{ '--zoom-multiplier': (1 / Math.pow(zoom, 0.7)) }">
|
||||||
:style="{ '--zoom-multiplier': (1 / Math.pow(zoom, 0.7)) }">
|
<div class="border border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10 absolute sm:top-2 top-10 left-2 z-[35] overflow-hidden">
|
||||||
<div class="border border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10 absolute sm:top-2 top-10 left-2 z-[35] overflow-hidden">
|
<Tooltip message="Zoom avant" side="right">
|
||||||
<Tooltip message="Zoom avant" side="right">
|
<div @click="zoom = clamp(zoom * 1.1, minZoom, 3)" class="w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer">
|
||||||
<div @click="zoom = clamp(zoom * 1.1, minZoom, 3)" class="w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer">
|
<Icon icon="radix-icons:plus" />
|
||||||
<Icon icon="radix-icons:plus" />
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip message="Reset" side="right">
|
|
||||||
<div @click="zoom = 1" class="w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer">
|
|
||||||
<Icon icon="radix-icons:reload" />
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip message="Tout contenir" side="right">
|
|
||||||
<div @click="reset" class="w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer">
|
|
||||||
<Icon icon="radix-icons:corners" />
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip message="Zoom arrière" side="right">
|
|
||||||
<div @click="zoom = clamp(zoom * 0.9, minZoom, 3)" class="w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer">
|
|
||||||
<Icon icon="radix-icons:minus" />
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
<div class="absolute top-0 left-0 w-full h-full origin-center pointer-events-none *:pointer-events-auto *:select-none"
|
|
||||||
:style="{transform: `scale(${zoom}) translate(${dispX}px, ${dispY}px)`}">
|
|
||||||
<div>
|
|
||||||
<CanvasNode v-for="node of props.canvas.nodes" :key="node.id" :node="node" :zoom="zoom" />
|
|
||||||
</div>
|
</div>
|
||||||
<template v-for="edge of props.canvas.edges">
|
</Tooltip>
|
||||||
<div :key="edge.id" v-if="edge.label" class="absolute z-10"
|
<Tooltip message="Reset" side="right">
|
||||||
:style="{ transform: labelCenter(getNode(edge.fromNode)!, edge.fromSide, getNode(edge.toNode)!, edge.toSide) }">
|
<div @click="zoom = 1" class="w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer">
|
||||||
<div class="relative bg-light-20 dark:bg-dark-20 border border-light-35 dark:border-dark-35 px-4 py-2 -translate-x-[50%] -translate-y-[50%]">{{ edge.label }}</div>
|
<Icon icon="radix-icons:reload" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</Tooltip>
|
||||||
<svg class="absolute top-0 left-0 overflow-visible w-full h-full origin-top pointer-events-none">
|
<Tooltip message="Tout contenir" side="right">
|
||||||
<CanvasEdge v-for="edge of props.canvas.edges" :key="edge.id"
|
<div @click="reset" class="w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer">
|
||||||
:path="path(getNode(edge.fromNode)!, edge.fromSide, getNode(edge.toNode)!, edge.toSide)"
|
<Icon icon="radix-icons:corners" />
|
||||||
:color="edge.color" :label="edge.label" />
|
</div>
|
||||||
</svg>
|
</Tooltip>
|
||||||
</div>
|
<Tooltip message="Zoom arrière" side="right">
|
||||||
|
<div @click="zoom = clamp(zoom * 0.9, minZoom, 3)" class="w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer">
|
||||||
|
<Icon icon="radix-icons:minus" />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<div class="absolute top-0 left-0 w-full h-full origin-center pointer-events-none *:pointer-events-auto *:select-none"
|
||||||
<template #fallback>
|
:style="{transform: `scale(${zoom}) translate(${dispX}px, ${dispY}px)`}">
|
||||||
<div class="loading"></div>
|
<div>
|
||||||
</template>
|
<CanvasNode v-for="node of props.canvas.nodes" :key="node.id" :node="node" :zoom="zoom" />
|
||||||
</Suspense>
|
</div>
|
||||||
|
<template v-for="edge of props.canvas.edges">
|
||||||
|
<div :key="edge.id" v-if="edge.label" class="absolute z-10"
|
||||||
|
:style="{ transform: labelCenter(getNode(edge.fromNode)!, edge.fromSide, getNode(edge.toNode)!, edge.toSide) }">
|
||||||
|
<div class="relative bg-light-20 dark:bg-dark-20 border border-light-35 dark:border-dark-35 px-4 py-2 -translate-x-[50%] -translate-y-[50%]">{{ edge.label }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<svg class="absolute top-0 left-0 overflow-visible w-full h-full origin-top pointer-events-none">
|
||||||
|
<CanvasEdge v-for="edge of props.canvas.edges" :key="edge.id"
|
||||||
|
:path="path(getNode(edge.fromNode)!, edge.fromSide, getNode(edge.toNode)!, edge.toSide)"
|
||||||
|
:color="edge.color" :label="edge.label" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -1,33 +1,18 @@
|
||||||
<template>
|
<template>
|
||||||
<NuxtLink class="text-accent-blue inline-flex items-center" v-if="overview"
|
<NuxtLink class="text-accent-blue inline-flex items-center"
|
||||||
:to="{ name: 'explore-path', params: { path: overview.path }, hash: hash }" :class="class">
|
:to="overview ? { name: 'explore-path', params: { path: overview.path }, hash: hash } : href" :class="class">
|
||||||
<HoverCard class="max-w-[600px] max-h-[600px] w-full overflow-auto z-[45]" :class="{'overflow-hidden !p-0': overview.type === 'canvas'}" @open="load">
|
<HoverCard class="max-w-[600px] max-h-[600px] w-full overflow-auto z-[45]" :class="{'overflow-hidden !p-0': overview?.type === 'canvas'}" @open="load" :disabled="!overview">
|
||||||
<template #content>
|
<template #content>
|
||||||
<template v-if="loading">
|
<Loading v-if="loading" />
|
||||||
<Loading />
|
<Markdown v-else-if="overview?.type === 'markdown'" class="px-10" :content="content!.content" :filter="hash.substring(1)" />
|
||||||
</template>
|
<Canvas v-else-if="overview?.type === 'canvas'" class="w-[600px] h-[600px] relative" :canvas="JSON.parse(content!.content)" />
|
||||||
<template v-else-if="overview.type === 'markdown'">
|
</template>
|
||||||
<div class="px-10">
|
<template #default>
|
||||||
<Markdown :content="content!.content" />
|
<slot v-bind="$attrs"></slot>
|
||||||
</div>
|
<Icon class="w-4 h-4 inline-block" v-if="overview && overview.type !== 'markdown'" :icon="iconByType[overview.type]" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="overview.type === 'canvas'">
|
</HoverCard>
|
||||||
<div class="w-[600px] h-[600px] relative">
|
</NuxtLink>
|
||||||
<Canvas :canvas="JSON.parse(content!.content)" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
<template #default>
|
|
||||||
<slot v-bind="$attrs"></slot>
|
|
||||||
<Icon class="w-4 h-4 inline-block" v-if="overview && overview.type !== 'markdown'" :icon="iconByType[overview.type]" />
|
|
||||||
</template>
|
|
||||||
</HoverCard>
|
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink v-else-if="href" :to="href" :class="class" class="text-accent-blue inline-flex items-center">
|
|
||||||
<slot v-bind="$attrs"></slot>
|
|
||||||
<Icon class="w-4 h-4 inline-block" v-if="overview && overview.type !== 'markdown'" :height="20" :width="20" :icon="iconByType[overview.type]" />
|
|
||||||
</NuxtLink>
|
|
||||||
<slot :class="class" v-else v-bind="$attrs"></slot>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
@ -36,41 +21,41 @@ import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||||
import { iconByType } from '#shared/general.utils';
|
import { iconByType } from '#shared/general.utils';
|
||||||
|
|
||||||
const { href } = defineProps<{
|
const { href } = defineProps<{
|
||||||
href: string
|
href: string
|
||||||
class?: string
|
class?: string
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { hash, pathname, protocol } = parseURL(href);
|
const { hash, pathname, protocol } = parseURL(href);
|
||||||
const overview = ref<{
|
const overview = ref<{
|
||||||
path: string;
|
path: string;
|
||||||
owner: number;
|
owner: number;
|
||||||
title: string;
|
title: string;
|
||||||
type: "file" | "folder" | "markdown" | "canvas";
|
type: "file" | "folder" | "markdown" | "canvas";
|
||||||
navigable: boolean;
|
navigable: boolean;
|
||||||
private: boolean;
|
private: boolean;
|
||||||
order: number;
|
order: number;
|
||||||
visit: number;
|
visit: number;
|
||||||
}>(), content = ref<{
|
}>(), content = ref<{
|
||||||
content: string;
|
content: string;
|
||||||
}>(), loading = ref(false), fetched = ref(false);
|
}>(), loading = ref(false), fetched = ref(false);
|
||||||
|
|
||||||
if(!!pathname && !protocol)
|
if(!!pathname && !protocol)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
overview.value = await $fetch(`/api/file/overview/${encodeURIComponent(pathname)}`);
|
overview.value = await $fetch(`/api/file/overview/${encodeURIComponent(pathname)}`);
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function load()
|
async function load()
|
||||||
{
|
{
|
||||||
if(fetched.value === true)
|
if(fetched.value === true)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
fetched.value = true;
|
fetched.value = true;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
content.value = await $fetch(`/api/file/content/${encodeURIComponent(pathname)}`);
|
content.value = await $fetch(`/api/file/content/${encodeURIComponent(pathname)}`);
|
||||||
} catch(e) { }
|
} catch(e) { }
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
BIN
db.sqlite-shm
BIN
db.sqlite-shm
Binary file not shown.
BIN
db.sqlite-wal
BIN
db.sqlite-wal
Binary file not shown.
|
|
@ -19,6 +19,8 @@
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"drizzle-orm": "^0.35.3",
|
"drizzle-orm": "^0.35.3",
|
||||||
"hast": "^1.0.0",
|
"hast": "^1.0.0",
|
||||||
|
"hast-util-heading": "^3.0.0",
|
||||||
|
"hast-util-heading-rank": "^3.0.0",
|
||||||
"lodash.capitalize": "^4.2.1",
|
"lodash.capitalize": "^4.2.1",
|
||||||
"mdast-util-find-and-replace": "^3.0.1",
|
"mdast-util-find-and-replace": "^3.0.1",
|
||||||
"nodemailer": "^6.9.16",
|
"nodemailer": "^6.9.16",
|
||||||
|
|
|
||||||
4
todo.md
4
todo.md
|
|
@ -1,8 +1,8 @@
|
||||||
- [x] Mot de passe oublié
|
- [x] Mot de passe oublié
|
||||||
- [x] Rename auto des liens au changement de path
|
- [x] Rename auto des liens au changement de path
|
||||||
- [ ] Autocomplete des liens dans l'editeur
|
|
||||||
- [ ] Editeur de graphe
|
|
||||||
- [ ] Filtrage de lien avec le header id
|
- [ ] Filtrage de lien avec le header id
|
||||||
|
- [ ] Editeur de graphe
|
||||||
|
- [ ] Autocomplete des liens dans l'editeur
|
||||||
- [ ] Embed de lien (le ![[]] de Obsidian)
|
- [ ] Embed de lien (le ![[]] de Obsidian)
|
||||||
- [ ] Rework la structure projet
|
- [ ] Rework la structure projet
|
||||||
- [ ] Limite de taille par projet (100 Mo ?)
|
- [ ] Limite de taille par projet (100 Mo ?)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue