Change explorer pages structure to isolates them from there renderers allowing to fetch data outside of the renderers
This commit is contained in:
parent
7bdf6ccd13
commit
031a51c2fe
|
|
@ -1,49 +0,0 @@
|
||||||
<template>
|
|
||||||
<template v-if="content && content.length > 0">
|
|
||||||
<div><MarkdownRenderer #default v-if="data" :node="data" :proses="proses"></MarkdownRenderer></div>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { Component } from 'vue';
|
|
||||||
import { heading } from 'hast-util-heading';
|
|
||||||
import { headingRank } from 'hast-util-heading-rank';
|
|
||||||
import { parseId } from '~/shared/general.utils';
|
|
||||||
import type { Root } from 'hast';
|
|
||||||
|
|
||||||
const { content, proses, filter } = defineProps<{
|
|
||||||
content: string
|
|
||||||
proses?: Array<string | Component>
|
|
||||||
filter?: string
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const parser = useMarkdown(), data = ref<Root>();
|
|
||||||
const node = computed(() => content ? parser(content) : undefined);
|
|
||||||
watch([node], () => {
|
|
||||||
if(!node.value)
|
|
||||||
data.value = undefined;
|
|
||||||
else if(!filter)
|
|
||||||
{
|
|
||||||
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>
|
|
||||||
|
|
@ -1,115 +1,49 @@
|
||||||
<script lang="ts">
|
<template>
|
||||||
import type { RootContent, Root } from 'hast';
|
<div v-if="content && content.length > 0">
|
||||||
import { Text, Comment } from 'vue';
|
<ProsesRenderer #default v-if="data" :node="data" :proses="proses" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
import ProseP from '~/components/prose/ProseP.vue';
|
<script setup lang="ts">
|
||||||
import ProseA from '~/components/prose/ProseA.vue';
|
import type { Component } from 'vue';
|
||||||
import ProseBlockquote from '~/components/prose/ProseBlockquote.vue';
|
import { heading } from 'hast-util-heading';
|
||||||
import ProseCallout from './prose/ProseCallout.vue';
|
import { headingRank } from 'hast-util-heading-rank';
|
||||||
import ProseCode from '~/components/prose/ProseCode.vue';
|
import { parseId } from '~/shared/general.utils';
|
||||||
import ProsePre from '~/components/prose/ProsePre.vue';
|
import type { Root } from 'hast';
|
||||||
import ProseEm from '~/components/prose/ProseEm.vue';
|
|
||||||
import ProseH1 from '~/components/prose/ProseH1.vue';
|
|
||||||
import ProseH2 from '~/components/prose/ProseH2.vue';
|
|
||||||
import ProseH3 from '~/components/prose/ProseH3.vue';
|
|
||||||
import ProseH4 from '~/components/prose/ProseH4.vue';
|
|
||||||
import ProseH5 from '~/components/prose/ProseH5.vue';
|
|
||||||
import ProseH6 from '~/components/prose/ProseH6.vue';
|
|
||||||
import ProseHr from '~/components/prose/ProseHr.vue';
|
|
||||||
import ProseImg from '~/components/prose/ProseImg.vue';
|
|
||||||
import ProseUl from '~/components/prose/ProseUl.vue';
|
|
||||||
import ProseOl from '~/components/prose/ProseOl.vue';
|
|
||||||
import ProseLi from '~/components/prose/ProseLi.vue';
|
|
||||||
import ProseSmall from './prose/ProseSmall.vue';
|
|
||||||
import ProseStrong from '~/components/prose/ProseStrong.vue';
|
|
||||||
import ProseTable from '~/components/prose/ProseTable.vue';
|
|
||||||
import ProseTag from '~/components/prose/ProseTag.vue';
|
|
||||||
import ProseThead from '~/components/prose/ProseThead.vue';
|
|
||||||
import ProseTbody from '~/components/prose/ProseTbody.vue';
|
|
||||||
import ProseTd from '~/components/prose/ProseTd.vue';
|
|
||||||
import ProseTh from '~/components/prose/ProseTh.vue';
|
|
||||||
import ProseTr from '~/components/prose/ProseTr.vue';
|
|
||||||
import ProseScript from '~/components/prose/ProseScript.vue';
|
|
||||||
|
|
||||||
const proseList = {
|
const { content, proses, filter } = defineProps<{
|
||||||
"p": ProseP,
|
content: string
|
||||||
"a": ProseA,
|
proses?: Array<string | Component>
|
||||||
"blockquote": ProseBlockquote,
|
filter?: string
|
||||||
"callout": ProseCallout,
|
}>();
|
||||||
"code": ProseCode,
|
|
||||||
"pre": ProsePre,
|
|
||||||
"em": ProseEm,
|
|
||||||
"h1": ProseH1,
|
|
||||||
"h2": ProseH2,
|
|
||||||
"h3": ProseH3,
|
|
||||||
"h4": ProseH4,
|
|
||||||
"h5": ProseH5,
|
|
||||||
"h6": ProseH6,
|
|
||||||
"hr": ProseHr,
|
|
||||||
"img": ProseImg,
|
|
||||||
"ul": ProseUl,
|
|
||||||
"ol": ProseOl,
|
|
||||||
"li": ProseLi,
|
|
||||||
"small": ProseSmall,
|
|
||||||
"strong": ProseStrong,
|
|
||||||
"table": ProseTable,
|
|
||||||
"tag": ProseTag,
|
|
||||||
"thead": ProseThead,
|
|
||||||
"tbody": ProseTbody,
|
|
||||||
"td": ProseTd,
|
|
||||||
"th": ProseTh,
|
|
||||||
"tr": ProseTr,
|
|
||||||
"script": ProseScript
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defineComponent({
|
const parser = useMarkdown(), data = ref<Root>();
|
||||||
name: 'MarkdownRenderer',
|
const node = computed(() => content ? parser(content) : undefined);
|
||||||
props: {
|
watch([node], () => {
|
||||||
node: {
|
if(!node.value)
|
||||||
type: Object,
|
data.value = undefined;
|
||||||
required: true
|
else if(!filter)
|
||||||
},
|
{
|
||||||
proses: {
|
data.value = node.value;
|
||||||
type: Object,
|
}
|
||||||
default: () => ({})
|
else
|
||||||
}
|
{
|
||||||
},
|
const start = node.value?.children.findIndex(e => heading(e) && parseId(e.properties.id as string | undefined) === filter) ?? -1;
|
||||||
async setup(props) {
|
|
||||||
if(props.proses)
|
|
||||||
{
|
|
||||||
for(const prose of Object.keys(props.proses))
|
|
||||||
{
|
|
||||||
if(typeof props.proses[prose] === 'string')
|
|
||||||
props.proses[prose] = await resolveComponent(props.proses[prose]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { tags: Object.assign({}, proseList, props.proses) };
|
|
||||||
},
|
|
||||||
render(ctx: any) {
|
|
||||||
const { node, tags } = ctx;
|
|
||||||
|
|
||||||
if(!node)
|
if(start === -1)
|
||||||
return null;
|
data.value = node.value;
|
||||||
|
else
|
||||||
return h('div', null, {default: () => (node as Root).children.map(e => renderNode(e, tags)).filter(e => !!e)});
|
{
|
||||||
}
|
let end = start;
|
||||||
});
|
const rank = headingRank(node.value.children[start])!;
|
||||||
|
while(end < node.value.children.length)
|
||||||
function renderNode(node: RootContent, tags: Record<string, any>): VNode | undefined
|
{
|
||||||
{
|
end++;
|
||||||
if(node.type === 'text' && node.value.length > 0 && node.value !== '\n')
|
if(heading(node.value.children[end]) && headingRank(node.value.children[end])! >= rank)
|
||||||
{
|
break;
|
||||||
return h(Text, node.value);
|
}
|
||||||
}
|
data.value = { ...node.value, children: node.value.children.slice(start, end) };
|
||||||
else if(node.type === 'comment' && node.value.length > 0 && node.value !== '\n')
|
}
|
||||||
{
|
}
|
||||||
return h(Comment, node.value);
|
}, { immediate: true, });
|
||||||
}
|
|
||||||
else if(node.type === 'element')
|
|
||||||
{
|
|
||||||
return h(tags[node.tagName] ?? node.tagName, { ...node.properties, class: node.properties.className }, { default: () => node.children.map(e => renderNode(e, tags)).filter(e => !!e) });
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { RootContent, Root } from 'hast';
|
||||||
|
import { Text, Comment } from 'vue';
|
||||||
|
|
||||||
|
import ProseP from '~/components/prose/ProseP.vue';
|
||||||
|
import ProseA from '~/components/prose/ProseA.vue';
|
||||||
|
import ProseBlockquote from '~/components/prose/ProseBlockquote.vue';
|
||||||
|
import ProseCallout from './prose/ProseCallout.vue';
|
||||||
|
import ProseCode from '~/components/prose/ProseCode.vue';
|
||||||
|
import ProsePre from '~/components/prose/ProsePre.vue';
|
||||||
|
import ProseEm from '~/components/prose/ProseEm.vue';
|
||||||
|
import ProseH1 from '~/components/prose/ProseH1.vue';
|
||||||
|
import ProseH2 from '~/components/prose/ProseH2.vue';
|
||||||
|
import ProseH3 from '~/components/prose/ProseH3.vue';
|
||||||
|
import ProseH4 from '~/components/prose/ProseH4.vue';
|
||||||
|
import ProseH5 from '~/components/prose/ProseH5.vue';
|
||||||
|
import ProseH6 from '~/components/prose/ProseH6.vue';
|
||||||
|
import ProseHr from '~/components/prose/ProseHr.vue';
|
||||||
|
import ProseImg from '~/components/prose/ProseImg.vue';
|
||||||
|
import ProseUl from '~/components/prose/ProseUl.vue';
|
||||||
|
import ProseOl from '~/components/prose/ProseOl.vue';
|
||||||
|
import ProseLi from '~/components/prose/ProseLi.vue';
|
||||||
|
import ProseSmall from './prose/ProseSmall.vue';
|
||||||
|
import ProseStrong from '~/components/prose/ProseStrong.vue';
|
||||||
|
import ProseTable from '~/components/prose/ProseTable.vue';
|
||||||
|
import ProseTag from '~/components/prose/ProseTag.vue';
|
||||||
|
import ProseThead from '~/components/prose/ProseThead.vue';
|
||||||
|
import ProseTbody from '~/components/prose/ProseTbody.vue';
|
||||||
|
import ProseTd from '~/components/prose/ProseTd.vue';
|
||||||
|
import ProseTh from '~/components/prose/ProseTh.vue';
|
||||||
|
import ProseTr from '~/components/prose/ProseTr.vue';
|
||||||
|
import ProseScript from '~/components/prose/ProseScript.vue';
|
||||||
|
|
||||||
|
const proseList = {
|
||||||
|
"p": ProseP,
|
||||||
|
"a": ProseA,
|
||||||
|
"blockquote": ProseBlockquote,
|
||||||
|
"callout": ProseCallout,
|
||||||
|
"code": ProseCode,
|
||||||
|
"pre": ProsePre,
|
||||||
|
"em": ProseEm,
|
||||||
|
"h1": ProseH1,
|
||||||
|
"h2": ProseH2,
|
||||||
|
"h3": ProseH3,
|
||||||
|
"h4": ProseH4,
|
||||||
|
"h5": ProseH5,
|
||||||
|
"h6": ProseH6,
|
||||||
|
"hr": ProseHr,
|
||||||
|
"img": ProseImg,
|
||||||
|
"ul": ProseUl,
|
||||||
|
"ol": ProseOl,
|
||||||
|
"li": ProseLi,
|
||||||
|
"small": ProseSmall,
|
||||||
|
"strong": ProseStrong,
|
||||||
|
"table": ProseTable,
|
||||||
|
"tag": ProseTag,
|
||||||
|
"thead": ProseThead,
|
||||||
|
"tbody": ProseTbody,
|
||||||
|
"td": ProseTd,
|
||||||
|
"th": ProseTh,
|
||||||
|
"tr": ProseTr,
|
||||||
|
"script": ProseScript
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'MarkdownRenderer',
|
||||||
|
props: {
|
||||||
|
node: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
proses: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async setup(props) {
|
||||||
|
if(props.proses)
|
||||||
|
{
|
||||||
|
for(const prose of Object.keys(props.proses))
|
||||||
|
{
|
||||||
|
if(typeof props.proses[prose] === 'string')
|
||||||
|
props.proses[prose] = await resolveComponent(props.proses[prose]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { tags: Object.assign({}, proseList, props.proses) };
|
||||||
|
},
|
||||||
|
render(ctx: any) {
|
||||||
|
const { node, tags } = ctx;
|
||||||
|
|
||||||
|
if(!node)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return h('div', null, {default: () => (node as Root).children.map(e => renderNode(e, tags)).filter(e => !!e)});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderNode(node: RootContent, tags: Record<string, any>): VNode | undefined
|
||||||
|
{
|
||||||
|
if(node.type === 'text' && node.value.length > 0 && node.value !== '\n')
|
||||||
|
{
|
||||||
|
return h(Text, node.value);
|
||||||
|
}
|
||||||
|
else if(node.type === 'comment' && node.value.length > 0 && node.value !== '\n')
|
||||||
|
{
|
||||||
|
return h(Comment, node.value);
|
||||||
|
}
|
||||||
|
else if(node.type === 'element')
|
||||||
|
{
|
||||||
|
return h(tags[node.tagName] ?? node.tagName, { ...node.properties, class: node.properties.className }, { default: () => node.children.map(e => renderNode(e, tags)).filter(e => !!e) });
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -1,216 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
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 '#shared/general.utils';
|
|
||||||
|
|
||||||
interface Props
|
|
||||||
{
|
|
||||||
canvas: CanvasContent;
|
|
||||||
}
|
|
||||||
const props = defineProps<Props>();
|
|
||||||
|
|
||||||
const dispX = ref(0), dispY = ref(0), minZoom = ref(0.1), zoom = ref(0.5);
|
|
||||||
const canvas = useTemplateRef('canvas');
|
|
||||||
|
|
||||||
const reset = (_: MouseEvent) => {
|
|
||||||
zoom.value = minZoom.value;
|
|
||||||
|
|
||||||
dispX.value = 0;
|
|
||||||
dispY.value = 0;
|
|
||||||
}
|
|
||||||
function edgePos(side: 'bottom' | 'top' | 'left' | 'right', pos: { x: number, y: number }, offset: number): { x: number, y: number } {
|
|
||||||
switch (side) {
|
|
||||||
case "left":
|
|
||||||
return {
|
|
||||||
x: pos.x - offset,
|
|
||||||
y: pos.y
|
|
||||||
};
|
|
||||||
case "right":
|
|
||||||
return {
|
|
||||||
x: pos.x + offset,
|
|
||||||
y: pos.y
|
|
||||||
};
|
|
||||||
case "top":
|
|
||||||
return {
|
|
||||||
x: pos.x,
|
|
||||||
y: pos.y - offset
|
|
||||||
};
|
|
||||||
case "bottom":
|
|
||||||
return {
|
|
||||||
x: pos.x,
|
|
||||||
y: pos.y + offset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function getNode(id: string): CanvasNode | undefined
|
|
||||||
{
|
|
||||||
return props.canvas.nodes.find(e => e.id === id);
|
|
||||||
}
|
|
||||||
function posFromDir(e: { minX: number, minY: number, maxX: number, maxY: number }, t: 'bottom' | 'top' | 'left' | 'right'): { x: number, y: number } {
|
|
||||||
switch (t) {
|
|
||||||
case "top":
|
|
||||||
return { x: (e.minX + e.maxX) / 2, y: e.minY };
|
|
||||||
case "right":
|
|
||||||
return { x: e.maxX, y: (e.minY + e.maxY) / 2 };
|
|
||||||
case "bottom":
|
|
||||||
return { x: (e.minX + e.maxX) / 2, y: e.maxY };
|
|
||||||
case "left":
|
|
||||||
return { x: e.minX, y: (e.minY + e.maxY) / 2 };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function getBbox(node: CanvasNode): { minX: number, minY: number, maxX: number, maxY: number } {
|
|
||||||
return { minX: node.x, minY: node.y, maxX: node.x + node.width, maxY: node.y + node.height };
|
|
||||||
}
|
|
||||||
function path(from: CanvasNode, fromSide: 'bottom' | 'top' | 'left' | 'right', to: CanvasNode, toSide: 'bottom' | 'top' | 'left' | 'right'): any {
|
|
||||||
if(from === undefined || to === undefined)
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
path: '',
|
|
||||||
from: {},
|
|
||||||
to: {},
|
|
||||||
toSide: '',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const start = posFromDir(getBbox(from), fromSide), end = posFromDir(getBbox(to), toSide);
|
|
||||||
return bezier(start, fromSide, end, toSide);
|
|
||||||
}
|
|
||||||
function bezier(from: { x: number, y: number }, fromSide: 'bottom' | 'top' | 'left' | 'right', to: { x: number, y: number }, toSide: 'bottom' | 'top' | 'left' | 'right'): any {
|
|
||||||
const r = Math.hypot(from.x - to.x, from.y - to.y), o = clamp(r / 2, 70, 150), a = edgePos(fromSide, from, o), s = edgePos(toSide, to, o);
|
|
||||||
return {
|
|
||||||
path: `M${from.x},${from.y} C${a.x},${a.y} ${s.x},${s.y} ${to.x},${to.y}`,
|
|
||||||
from: from,
|
|
||||||
to: to,
|
|
||||||
side: toSide,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function labelCenter(from: CanvasNode, fromSide: 'bottom' | 'top' | 'left' | 'right', to: CanvasNode, toSide: 'bottom' | 'top' | 'left' | 'right'): string {
|
|
||||||
const start = posFromDir(getBbox(from), fromSide), end = posFromDir(getBbox(to), toSide);
|
|
||||||
const len = Math.hypot(start.x - end.x, start.y - end.y), offset = clamp(len / 2, 70, 150), b = edgePos(fromSide, start, offset), s = edgePos(toSide, end, offset);
|
|
||||||
const center = getCenter(start, end, b, s, 0.5);
|
|
||||||
return `translate(${center.x}px, ${center.y}px)`;
|
|
||||||
}
|
|
||||||
function getCenter(n: { x: number, y: number }, i: { x: number, y: number }, r: { x: number, y: number }, o: { x: number, y: number }, e: number): { x: number, y: number } {
|
|
||||||
const a = 1 - e, s = a * a * a, l = 3 * e * a * a, c = 3 * e * e * a, u = e * e * e;
|
|
||||||
return {
|
|
||||||
x: s * n.x + l * r.x + c * o.x + u * i.x,
|
|
||||||
y: s * n.y + l * r.y + c * o.y + u * i.y
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
|
|
||||||
stroke-light-red
|
|
||||||
stroke-light-orange
|
|
||||||
stroke-light-yellow
|
|
||||||
stroke-light-green
|
|
||||||
stroke-light-cyan
|
|
||||||
stroke-light-purple
|
|
||||||
dark:stroke-dark-red
|
|
||||||
dark:stroke-dark-orange
|
|
||||||
dark:stroke-dark-yellow
|
|
||||||
dark:stroke-dark-green
|
|
||||||
dark:stroke-dark-cyan
|
|
||||||
dark:stroke-dark-purple
|
|
||||||
fill-light-red
|
|
||||||
fill-light-orange
|
|
||||||
fill-light-yellow
|
|
||||||
fill-light-green
|
|
||||||
fill-light-cyan
|
|
||||||
fill-light-purple
|
|
||||||
dark:fill-dark-red
|
|
||||||
dark:fill-dark-orange
|
|
||||||
dark:fill-dark-yellow
|
|
||||||
dark:fill-dark-green
|
|
||||||
dark:fill-dark-cyan
|
|
||||||
dark:fill-dark-purple
|
|
||||||
bg-light-red
|
|
||||||
bg-light-orange
|
|
||||||
bg-light-yellow
|
|
||||||
bg-light-green
|
|
||||||
bg-light-cyan
|
|
||||||
bg-light-purple
|
|
||||||
dark:bg-dark-red
|
|
||||||
dark:bg-dark-orange
|
|
||||||
dark:bg-dark-yellow
|
|
||||||
dark:bg-dark-green
|
|
||||||
dark:bg-dark-cyan
|
|
||||||
dark:bg-dark-purple
|
|
||||||
border-light-red
|
|
||||||
border-light-orange
|
|
||||||
border-light-yellow
|
|
||||||
border-light-green
|
|
||||||
border-light-cyan
|
|
||||||
border-light-purple
|
|
||||||
dark:border-dark-red
|
|
||||||
dark:border-dark-orange
|
|
||||||
dark:border-dark-yellow
|
|
||||||
dark:border-dark-green
|
|
||||||
dark:border-dark-cyan
|
|
||||||
dark:border-dark-purple
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const pinchHandler = usePinch(({ event, offset: [z] }: { event: Event, offset: number[] }) => {
|
|
||||||
zoom.value = clamp(z / 2048, minZoom.value, 3);
|
|
||||||
}, {
|
|
||||||
domTarget: canvas,
|
|
||||||
})
|
|
||||||
const dragHandler = useDrag(({ event, delta: [x, y] }: { event: Event, delta: number[] }) => {
|
|
||||||
dispX.value += x / zoom.value;
|
|
||||||
dispY.value += y / zoom.value;
|
|
||||||
}, {
|
|
||||||
domTarget: canvas,
|
|
||||||
})
|
|
||||||
const wheelHandler = useWheel(({ event, delta: [x, y] }: { event: Event, delta: number[] }) => {
|
|
||||||
zoom.value = clamp(zoom.value + y * -0.001, minZoom.value, 3);
|
|
||||||
}, {
|
|
||||||
domTarget: canvas,
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<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)) }">
|
|
||||||
<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">
|
|
||||||
<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" />
|
|
||||||
</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>
|
|
||||||
<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>
|
|
||||||
|
|
@ -37,7 +37,7 @@ const colors = computed(() => {
|
||||||
<div class="w-full h-full py-2 px-4 flex !bg-opacity-[0.07]" :class="colors.bg">
|
<div class="w-full h-full py-2 px-4 flex !bg-opacity-[0.07]" :class="colors.bg">
|
||||||
<template v-if="node.type === 'group' || zoom > Math.min(0.4, 1000 / size)">
|
<template v-if="node.type === 'group' || zoom > Math.min(0.4, 1000 / size)">
|
||||||
<div v-if="node.text?.length > 0" class="flex items-center">
|
<div v-if="node.text?.length > 0" class="flex items-center">
|
||||||
<Markdown :content="node.text" />
|
<MarkdownRenderer :content="node.text" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
<template>
|
||||||
|
<div class="absolute top-0 left-0 w-full h-full origin-center pointer-events-none *:pointer-events-auto *:select-none">
|
||||||
|
<div>
|
||||||
|
<CanvasNode v-for="node of canvas.nodes" :key="node.id" :node="node" :zoom="zoom" />
|
||||||
|
</div>
|
||||||
|
<template v-for="edge of canvas.edges">
|
||||||
|
<div :key="edge.id" v-if="edge.label" class="absolute z-10"
|
||||||
|
:style="{ transform: labelCenter(getNode(canvas.nodes, edge.fromNode)!, edge.fromSide, getNode(canvas.nodes, 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 canvas.edges" :key="edge.id"
|
||||||
|
:path="path(getNode(canvas.nodes, edge.fromNode)!, edge.fromSide, getNode(canvas.nodes, edge.toNode)!, edge.toSide)"
|
||||||
|
:color="edge.color" :label="edge.label" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { canvas, zoom } = defineProps<{
|
||||||
|
canvas: CanvasContent
|
||||||
|
zoom: number
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { clamp } from '~/shared/general.utils';
|
||||||
|
import type { CanvasContent, CanvasNode } from '~/types/canvas';
|
||||||
|
|
||||||
|
function edgePos(side: 'bottom' | 'top' | 'left' | 'right', pos: { x: number, y: number }, offset: number): { x: number, y: number } {
|
||||||
|
switch (side) {
|
||||||
|
case "left":
|
||||||
|
return {
|
||||||
|
x: pos.x - offset,
|
||||||
|
y: pos.y
|
||||||
|
};
|
||||||
|
case "right":
|
||||||
|
return {
|
||||||
|
x: pos.x + offset,
|
||||||
|
y: pos.y
|
||||||
|
};
|
||||||
|
case "top":
|
||||||
|
return {
|
||||||
|
x: pos.x,
|
||||||
|
y: pos.y - offset
|
||||||
|
};
|
||||||
|
case "bottom":
|
||||||
|
return {
|
||||||
|
x: pos.x,
|
||||||
|
y: pos.y + offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getNode(nodes: CanvasNode[], id: string): CanvasNode | undefined
|
||||||
|
{
|
||||||
|
return nodes.find(e => e.id === id);
|
||||||
|
}
|
||||||
|
function posFromDir(e: { minX: number, minY: number, maxX: number, maxY: number }, t: 'bottom' | 'top' | 'left' | 'right'): { x: number, y: number } {
|
||||||
|
switch (t) {
|
||||||
|
case "top":
|
||||||
|
return { x: (e.minX + e.maxX) / 2, y: e.minY };
|
||||||
|
case "right":
|
||||||
|
return { x: e.maxX, y: (e.minY + e.maxY) / 2 };
|
||||||
|
case "bottom":
|
||||||
|
return { x: (e.minX + e.maxX) / 2, y: e.maxY };
|
||||||
|
case "left":
|
||||||
|
return { x: e.minX, y: (e.minY + e.maxY) / 2 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getBbox(node: CanvasNode): { minX: number, minY: number, maxX: number, maxY: number } {
|
||||||
|
return { minX: node.x, minY: node.y, maxX: node.x + node.width, maxY: node.y + node.height };
|
||||||
|
}
|
||||||
|
function path(from: CanvasNode, fromSide: 'bottom' | 'top' | 'left' | 'right', to: CanvasNode, toSide: 'bottom' | 'top' | 'left' | 'right'): any {
|
||||||
|
if(from === undefined || to === undefined)
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
path: '',
|
||||||
|
from: {},
|
||||||
|
to: {},
|
||||||
|
toSide: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const start = posFromDir(getBbox(from), fromSide), end = posFromDir(getBbox(to), toSide);
|
||||||
|
return bezier(start, fromSide, end, toSide);
|
||||||
|
}
|
||||||
|
function bezier(from: { x: number, y: number }, fromSide: 'bottom' | 'top' | 'left' | 'right', to: { x: number, y: number }, toSide: 'bottom' | 'top' | 'left' | 'right'): any {
|
||||||
|
const r = Math.hypot(from.x - to.x, from.y - to.y), o = clamp(r / 2, 70, 150), a = edgePos(fromSide, from, o), s = edgePos(toSide, to, o);
|
||||||
|
return {
|
||||||
|
path: `M${from.x},${from.y} C${a.x},${a.y} ${s.x},${s.y} ${to.x},${to.y}`,
|
||||||
|
from: from,
|
||||||
|
to: to,
|
||||||
|
side: toSide,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function labelCenter(from: CanvasNode, fromSide: 'bottom' | 'top' | 'left' | 'right', to: CanvasNode, toSide: 'bottom' | 'top' | 'left' | 'right'): string {
|
||||||
|
const start = posFromDir(getBbox(from), fromSide), end = posFromDir(getBbox(to), toSide);
|
||||||
|
const len = Math.hypot(start.x - end.x, start.y - end.y), offset = clamp(len / 2, 70, 150), b = edgePos(fromSide, start, offset), s = edgePos(toSide, end, offset);
|
||||||
|
const center = getCenter(start, end, b, s, 0.5);
|
||||||
|
return `translate(${center.x}px, ${center.y}px)`;
|
||||||
|
}
|
||||||
|
function getCenter(n: { x: number, y: number }, i: { x: number, y: number }, r: { x: number, y: number }, o: { x: number, y: number }, e: number): { x: number, y: number } {
|
||||||
|
const a = 1 - e, s = a * a * a, l = 3 * e * a * a, c = 3 * e * e * a, u = e * e * e;
|
||||||
|
return {
|
||||||
|
x: s * n.x + l * r.x + c * o.x + u * i.x,
|
||||||
|
y: s * n.y + l * r.y + c * o.y + u * i.y
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
<script lang="ts">
|
||||||
|
function cancelEvent(e: Event)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useDrag, useHover, usePinch, useWheel } from '@vueuse/gesture';
|
||||||
|
import type { CanvasContent } from '~/types/canvas';
|
||||||
|
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||||
|
import { clamp } from '#shared/general.utils';
|
||||||
|
|
||||||
|
type CanvasOverview = any;
|
||||||
|
const { overview } = defineProps<{
|
||||||
|
overview: CanvasOverview
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { data: canvas, status } = await useFetch(`/api/file/content/${encodeURIComponent(overview.path)}`, {
|
||||||
|
transform: (input) => input && input.content ? JSON.parse(input?.content) as CanvasContent : undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
const { user } = useUserSession();
|
||||||
|
const isOwner = computed(() => user.value?.id === overview.owner);
|
||||||
|
|
||||||
|
const dispX = ref(0), dispY = ref(0), minZoom = ref(0.1), zoom = ref(0.5);
|
||||||
|
const canvasRef = useTemplateRef('canvasRef');
|
||||||
|
|
||||||
|
const reset = (_: MouseEvent) => {
|
||||||
|
zoom.value = minZoom.value;
|
||||||
|
|
||||||
|
dispX.value = 0;
|
||||||
|
dispY.value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
stroke-light-red
|
||||||
|
stroke-light-orange
|
||||||
|
stroke-light-yellow
|
||||||
|
stroke-light-green
|
||||||
|
stroke-light-cyan
|
||||||
|
stroke-light-purple
|
||||||
|
dark:stroke-dark-red
|
||||||
|
dark:stroke-dark-orange
|
||||||
|
dark:stroke-dark-yellow
|
||||||
|
dark:stroke-dark-green
|
||||||
|
dark:stroke-dark-cyan
|
||||||
|
dark:stroke-dark-purple
|
||||||
|
fill-light-red
|
||||||
|
fill-light-orange
|
||||||
|
fill-light-yellow
|
||||||
|
fill-light-green
|
||||||
|
fill-light-cyan
|
||||||
|
fill-light-purple
|
||||||
|
dark:fill-dark-red
|
||||||
|
dark:fill-dark-orange
|
||||||
|
dark:fill-dark-yellow
|
||||||
|
dark:fill-dark-green
|
||||||
|
dark:fill-dark-cyan
|
||||||
|
dark:fill-dark-purple
|
||||||
|
bg-light-red
|
||||||
|
bg-light-orange
|
||||||
|
bg-light-yellow
|
||||||
|
bg-light-green
|
||||||
|
bg-light-cyan
|
||||||
|
bg-light-purple
|
||||||
|
dark:bg-dark-red
|
||||||
|
dark:bg-dark-orange
|
||||||
|
dark:bg-dark-yellow
|
||||||
|
dark:bg-dark-green
|
||||||
|
dark:bg-dark-cyan
|
||||||
|
dark:bg-dark-purple
|
||||||
|
border-light-red
|
||||||
|
border-light-orange
|
||||||
|
border-light-yellow
|
||||||
|
border-light-green
|
||||||
|
border-light-cyan
|
||||||
|
border-light-purple
|
||||||
|
dark:border-dark-red
|
||||||
|
dark:border-dark-orange
|
||||||
|
dark:border-dark-yellow
|
||||||
|
dark:border-dark-green
|
||||||
|
dark:border-dark-cyan
|
||||||
|
dark:border-dark-purple
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const cancelEvent = (e: Event) => e.preventDefault()
|
||||||
|
useHover(({ hovering }) => {
|
||||||
|
if (!hovering) {
|
||||||
|
//@ts-ignore
|
||||||
|
window.removeEventListener('wheel', cancelEvent, { passive: false });
|
||||||
|
document.removeEventListener('gesturestart', cancelEvent)
|
||||||
|
document.removeEventListener('gesturechange', cancelEvent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('wheel', cancelEvent, { passive: false });
|
||||||
|
document.addEventListener('gesturestart', cancelEvent)
|
||||||
|
document.addEventListener('gesturechange', cancelEvent)
|
||||||
|
}, {
|
||||||
|
domTarget: canvasRef,
|
||||||
|
})
|
||||||
|
|
||||||
|
const dragHandler = useDrag(({ delta: [x, y] }: { delta: number[] }) => {
|
||||||
|
dispX.value += x / zoom.value;
|
||||||
|
dispY.value += y / zoom.value;
|
||||||
|
}, {
|
||||||
|
domTarget: canvasRef,
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
const wheelHandler = useWheel(({ delta: [x, y] }: { delta: number[] }) => {
|
||||||
|
zoom.value = clamp(zoom.value + y * -0.001, minZoom.value, 3);
|
||||||
|
}, {
|
||||||
|
domTarget: canvasRef,
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
const pinchHandler = usePinch(({ offset: [z] }: { offset: number[] }) => {
|
||||||
|
zoom.value = clamp(z / 2048, minZoom.value, 3);
|
||||||
|
}, {
|
||||||
|
domTarget: canvasRef,
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Loading v-if="status === 'pending'" />
|
||||||
|
<div v-else-if="canvas">
|
||||||
|
<div ref="canvasRef" class="absolute top-0 left-0 overflow-hidden w-full h-full touch-none" :style="{ '--zoom-multiplier': (1 / Math.pow(zoom, 0.7)) }">
|
||||||
|
<div class="flex flex-col absolute sm:top-2 top-10 left-2 z-[35] overflow-hidden gap-4">
|
||||||
|
<div class="border border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10">
|
||||||
|
<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">
|
||||||
|
<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="border border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10" v-if="isOwner">
|
||||||
|
<Tooltip message="Modifier" side="right">
|
||||||
|
<NuxtLink :to="{ name: 'explore-edit', hash: '#' + overview.path }" 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:pencil-1" />
|
||||||
|
</NuxtLink>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<CanvasRenderer :style="{transform: `scale(${zoom}) translate(${dispX}px, ${dispY}px)`}" :canvas="canvas" :zoom="zoom" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
type MarkdownOverview = any;
|
||||||
|
const { overview } = defineProps<{
|
||||||
|
overview: MarkdownOverview
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { data: content, status } = await useFetch(`/api/file/content/${encodeURIComponent(overview.path)}`, { watch: [overview] });
|
||||||
|
|
||||||
|
const { user } = useUserSession();
|
||||||
|
const isOwner = computed(() => user.value?.id === overview.owner);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-1 justify-start items-start flex-col xl:px-24 md:px-8 px-4 py-6">
|
||||||
|
<Loading v-if="status === 'pending'" />
|
||||||
|
<template v-else>
|
||||||
|
<div class="flex flex-1 flex-row justify-between items-center">
|
||||||
|
<ProseH1>{{ overview.title }}</ProseH1>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<NuxtLink :href="{ name: 'explore-edit', hash: '#' + overview.path }" v-if="isOwner"><Button>Modifier</Button></NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<MarkdownRenderer v-if="content" :content="content.content" />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<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">
|
<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>
|
||||||
<Loading v-if="loading" />
|
<Loading v-if="loading" />
|
||||||
<Markdown v-else-if="overview?.type === 'markdown'" class="px-10" :content="content!.content" :filter="hash.substring(1)" />
|
<MarkdownRenderer v-else-if="overview?.type === 'markdown'" class="px-10" :content="content!.content" :filter="hash.substring(1)" />
|
||||||
<Canvas v-else-if="overview?.type === 'canvas'" class="w-[600px] h-[600px] relative" :canvas="JSON.parse(content!.content)" />
|
<Canvas v-else-if="overview?.type === 'canvas'" class="w-[600px] h-[600px] relative" :canvas="JSON.parse(content!.content)" />
|
||||||
</template>
|
</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
|
|
|
||||||
BIN
db.sqlite-shm
BIN
db.sqlite-shm
Binary file not shown.
BIN
db.sqlite-wal
BIN
db.sqlite-wal
Binary file not shown.
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="overviewStatus === 'pending'" class="flex">
|
<div v-if="status === 'pending'" class="flex">
|
||||||
<Head>
|
<Head>
|
||||||
<Title>d[any] - Chargement</Title>
|
<Title>d[any] - Chargement</Title>
|
||||||
</Head>
|
</Head>
|
||||||
|
|
@ -9,37 +9,15 @@
|
||||||
<Head>
|
<Head>
|
||||||
<Title>d[any] - {{ overview.title }}</Title>
|
<Title>d[any] - {{ overview.title }}</Title>
|
||||||
</Head>
|
</Head>
|
||||||
<template v-if="overview.type === 'markdown'">
|
<Markdown v-if="overview.type === 'markdown'" :overview="overview" />
|
||||||
<div class="flex flex-1 justify-start items-start flex-col xl:px-24 md:px-8 px-4 py-6">
|
<Canvas v-else-if="overview.type === 'canvas'" :overview="overview" />
|
||||||
<div class="flex flex-1 flex-row justify-between items-center">
|
<ProseH2 v-else class="flex-1 text-center">Impossible d'afficher le contenu demandé</ProseH2>
|
||||||
<ProseH1>{{ overview.title }}</ProseH1>
|
|
||||||
<div class="flex gap-4">
|
|
||||||
<NuxtLink :href="{ name: 'explore-edit', hash: '#' + overview.path }" v-if="isOwner"><Button>Modifier</Button></NuxtLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Markdown v-if="content" :content="content.content" />
|
|
||||||
<Loading v-else />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="overview.type === 'canvas'">
|
|
||||||
<Canvas v-if="content" :canvas="JSON.parse(content.content)" />
|
|
||||||
<Loading v-else />
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<ProseH2 class="flex-1 text-center">Impossible d'afficher le contenu demandé</ProseH2>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="overviewStatus === 'error'">
|
<div v-else-if="status === 'error'">
|
||||||
<Head>
|
<Head>
|
||||||
<Title>d[any] - Erreur</Title>
|
<Title>d[any] - Erreur</Title>
|
||||||
</Head>
|
</Head>
|
||||||
<span>{{ overviewError?.message }}</span>
|
<span>{{ error?.message }}</span>
|
||||||
</div>
|
|
||||||
<div v-else-if="contentStatus === 'error'">
|
|
||||||
<Head>
|
|
||||||
<Title>d[any] - Erreur</Title>
|
|
||||||
</Head>
|
|
||||||
<span>{{ contentError?.message }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<Head>
|
<Head>
|
||||||
|
|
@ -53,9 +31,5 @@
|
||||||
const route = useRouter().currentRoute;
|
const route = useRouter().currentRoute;
|
||||||
const path = computed(() => Array.isArray(route.value.params.path) ? route.value.params.path[0] : route.value.params.path);
|
const path = computed(() => Array.isArray(route.value.params.path) ? route.value.params.path[0] : route.value.params.path);
|
||||||
|
|
||||||
const { user } = useUserSession();
|
const { data: overview, status, error } = await useFetch(`/api/file/overview/${encodeURIComponent(path.value)}`, { watch: [route, path], });
|
||||||
|
|
||||||
const { data: overview, status: overviewStatus, error: overviewError } = await useFetch(`/api/file/overview/${encodeURIComponent(path.value)}`, { watch: [route, path], });
|
|
||||||
const { data: content, status: contentStatus, error: contentError } = await useFetch(`/api/file/content/${encodeURIComponent(path.value)}`, { watch: [route, path], });
|
|
||||||
const isOwner = computed(() => user.value?.id === overview.value?.owner);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -152,7 +152,7 @@
|
||||||
</SplitterPanel>
|
</SplitterPanel>
|
||||||
<SplitterResizeHandle class="bg-light-35 dark:bg-dark-35 w-px xl!mx-4 mx-2" />
|
<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 }">
|
<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>
|
<div class="flex-1 max-h-full !overflow-y-auto px-8" :class="{ 'hidden': isCollapsed }"><MarkdownRenderer :content="debounced" :proses="{ 'a': FakeA }" /></div>
|
||||||
</SplitterPanel>
|
</SplitterPanel>
|
||||||
</SplitterGroup>
|
</SplitterGroup>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
2
todo.md
2
todo.md
|
|
@ -1,6 +1,6 @@
|
||||||
- [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
|
||||||
- [ ] Filtrage de lien avec le header id
|
- [x] Filtrage de lien avec le header id
|
||||||
- [ ] Editeur de graphe
|
- [ ] Editeur de graphe
|
||||||
- [ ] Autocomplete des liens dans l'editeur
|
- [ ] Autocomplete des liens dans l'editeur
|
||||||
- [ ] Embed de lien (le ![[]] de Obsidian)
|
- [ ] Embed de lien (le ![[]] de Obsidian)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue