Fix Canvas initial position and add tags popover

This commit is contained in:
Peaceultime 2024-01-10 18:04:05 +01:00
parent a55b98e07a
commit a228b7d222
11 changed files with 149 additions and 108 deletions

View File

@ -1,8 +1,13 @@
<script setup lang="ts">
const { data: tags } = await useAsyncData('tags', queryContent('/tags').findOne);
provide('tags', tags);
</script>
<template> <template>
<div class="published-container print is-readable-line-width has-navigation has-graph has-outline" style=""> <div class="published-container print is-readable-line-width has-navigation has-graph has-outline" style="">
<div class="site-body"> <div class="site-body">
<LeftComponent /> <LeftComponent />
<NuxtLoadingIndicator />
<MainComponent /> <MainComponent />
</div> </div>
</div> </div>

View File

@ -1008,6 +1008,22 @@ html {
--font-mermaid: var(--font-text); --font-mermaid: var(--font-text);
} }
.preload {
padding: 20px;
white-space: pre-wrap;
overflow-wrap: break-word
}
@keyframes rotate {
from {
transform: rotate(0)
}
to {
transform: rotate(360deg)
}
}
audio { audio {
outline: none; outline: none;
} }
@ -2778,7 +2794,7 @@ body:not(.native-scrollbars) * {
position: absolute; position: absolute;
z-index: var(--layer-popover); z-index: var(--layer-popover);
max-width: 80vw; max-width: 80vw;
min-height: 60px; min-height: 30px;
overflow: hidden; overflow: hidden;
width: fit-content; width: fit-content;
padding: 0; padding: 0;
@ -2835,6 +2851,18 @@ body:not(.native-scrollbars) * {
overflow: auto; overflow: auto;
} }
.popover.hover-popover>.tag-embed {
min-height: initial;
height: initial;
max-height: initial;
width: 400px;
}
.popover.hover-popover>.tag-embed {
min-height: initial;
height: initial;
}
.popover.hover-popover>.canvas-embed { .popover.hover-popover>.canvas-embed {
min-height: 350px; min-height: 350px;
} }
@ -2848,6 +2876,15 @@ body:not(.native-scrollbars) * {
padding: var(--file-margins); padding: var(--file-margins);
} }
.popover.hover-popover>.tag-embed>.markdown-embed-content>.markdown-preview-view {
padding: var(--size-4-3) var(--size-4-6);
}
.popover.hover-popover>.tag-embed p {
padding: 0;
margin: 0;
}
.popover.hover-popover>.markdown-embed .mod-header+div>*:first-child { .popover.hover-popover>.markdown-embed .mod-header+div>*:first-child {
margin-top: 0; margin-top: 0;
} }

View File

@ -11,29 +11,40 @@ const props = defineProps<Props>();
let dragging = false, posX = 0, posY = 0, dispX = ref(0), dispY = ref(0), minZoom = ref(0.3), zoom = ref(1); let dragging = false, posX = 0, posY = 0, dispX = ref(0), dispY = ref(0), minZoom = ref(0.3), zoom = ref(1);
let centerX = ref(0), centerY = ref(0), canvas = ref<HTMLDivElement>(); let centerX = ref(0), centerY = ref(0), canvas = ref<HTMLDivElement>();
let minX = ref(+Infinity), minY = ref(+Infinity), maxX = ref(-Infinity), maxY = ref(-Infinity);
let bbox = ref<DOMRect>();
onMounted(async () => { onMounted(async () => {
await nextTick(); await nextTick();
let minX = +Infinity, minY = +Infinity, maxX = -Infinity, maxY = -Infinity; let _minX = +Infinity, _minY = +Infinity, _maxX = -Infinity, _maxY = -Infinity;
props.canvas.body.nodes.forEach((e) => { props.canvas.body.nodes.forEach((e) => {
minX = Math.min(minX, e.x); _minX = Math.min(_minX, e.x);
minY = Math.min(minY, e.y); _minY = Math.min(_minY, e.y);
maxX = Math.max(maxX, e.x + e.width); _maxX = Math.max(_maxX, e.x + e.width);
maxY = Math.max(maxY, e.y + e.height); _maxY = Math.max(_maxY, e.y + e.height);
}); });
minZoom.value = Math.min((canvas.value?.clientWidth ?? 0) / (maxX - minX), (canvas.value?.clientHeight ?? 0) / (maxY - minY)) * 0.9; minX.value = _minX = _minX - 32;
minY.value = _minY = _minY - 32;
maxX.value = _maxX = _maxX + 32;
maxY.value = _maxY = _maxY + 32;
centerX.value = (canvas.value?.clientWidth ?? 0) / 2; minZoom.value = zoom.value = Math.min((canvas.value?.clientWidth ?? 0) / (_maxX - _minX), (canvas.value?.clientHeight ?? 0) / (_maxY - _minY)) * 0.9;
centerY.value = (canvas.value?.clientHeight ?? 0) / 2;
dispX.value = -(canvas.value?.clientWidth ?? 0) / 2; bbox.value = canvas.value?.getBoundingClientRect();
dispY.value = -(canvas.value?.clientHeight ?? 0) / 2;
await nextTick();
centerX.value = (bbox.value?.width ?? 0) / 2;
centerY.value = (bbox.value?.height ?? 0) / 2;
dispX.value = -(_maxX + _minX) / 2;
dispY.value = -(_maxY + _minY) / 2;
}) })
const onPointerDown = (event) => { const onPointerDown = (event: PointerEvent) => {
if (event.isPrimary === false) return; if (event.isPrimary === false) return;
event.target.setPointerCapture(event.pointerId);
dragging = true; dragging = true;
posX = event.clientX; posX = event.clientX;
@ -43,7 +54,7 @@ const onPointerDown = (event) => {
document.addEventListener('pointerup', onPointerUp); document.addEventListener('pointerup', onPointerUp);
} }
const onPointerMove = (event) => { const onPointerMove = (event: PointerEvent) => {
if (event.isPrimary === false) return; if (event.isPrimary === false) return;
dispX.value -= (posX - event.clientX) / zoom.value; dispX.value -= (posX - event.clientX) / zoom.value;
dispY.value -= (posY - event.clientY) / zoom.value; dispY.value -= (posY - event.clientY) / zoom.value;
@ -52,7 +63,7 @@ const onPointerMove = (event) => {
posY = event.clientY; posY = event.clientY;
} }
const onPointerUp = (event) => { const onPointerUp = (event: PointerEvent) => {
if (event.isPrimary === false) return; if (event.isPrimary === false) return;
dragging = false; dragging = false;
document.removeEventListener('pointermove', onPointerMove); document.removeEventListener('pointermove', onPointerMove);
@ -68,6 +79,11 @@ const onWheel = (event: WheelEvent) => {
zoom.value = minZoom.value; zoom.value = minZoom.value;
} }
const reset = (event: PointerEvent) => {
zoom.value = minZoom.value;
dispX.value = -(maxX.value + minX.value) / 2;
dispY.value = -(maxY.value + minY.value) / 2;
}
function clamp(x: number, min: number, max: number): number { function clamp(x: number, min: number, max: number): number {
if (x > max) if (x > max)
return max; return max;
@ -115,7 +131,7 @@ function mK(e: { minX: number, minY: number, maxX: number, maxY: number }, t: 'b
return { x: e.minX, y: (e.minY + e.maxY) / 2 }; return { x: e.minX, y: (e.minY + e.maxY) / 2 };
} }
} }
function bbox(node: CanvasNode): { minX: number, minY: number, maxX: number, maxY: number } { 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 }; 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 { function path(from: CanvasNode, fromSide: 'bottom' | 'top' | 'left' | 'right', to: CanvasNode, toSide: 'bottom' | 'top' | 'left' | 'right'): any {
@ -127,8 +143,8 @@ function path(from: CanvasNode, fromSide: 'bottom' | 'top' | 'left' | 'right', t
toSide: '', toSide: '',
} }
} }
const a = mK(bbox(from), fromSide), const a = mK(getBbox(from), fromSide),
l = mK(bbox(to), toSide); l = mK(getBbox(to), toSide);
return bezier(a, fromSide, l, toSide); return bezier(a, fromSide, l, toSide);
} }
function bezier(from: { x: number, y: number }, fromSide: 'bottom' | 'top' | 'left' | 'right', to: { x: number, y: number }, toSide: 'bottom' | 'top' | 'left' | 'right'): any { function bezier(from: { x: number, y: number }, fromSide: 'bottom' | 'top' | 'left' | 'right', to: { x: number, y: number }, toSide: 'bottom' | 'top' | 'left' | 'right'): any {
@ -142,7 +158,7 @@ function bezier(from: { x: number, y: number }, fromSide: 'bottom' | 'top' | 'le
</script> </script>
<template> <template>
<div @pointerdown="onPointerDown" @wheel.passive="onWheel" <div ref="canvas" @pointerdown="onPointerDown" @wheel.passive="onWheel"
@touchstart.prevent="" @dragstart.prevent="" class="canvas-wrapper node-insert-event mod-zoomed-out"> @touchstart.prevent="" @dragstart.prevent="" class="canvas-wrapper node-insert-event mod-zoomed-out">
<div class="canvas-controls" style="z-index: 421;"> <div class="canvas-controls" style="z-index: 421;">
<div class="canvas-control-group"> <div class="canvas-control-group">
@ -158,7 +174,7 @@ function bezier(from: { x: number, y: number }, fromSide: 'bottom' | 'top' | 'le
<path d="M21 3v5h-5"></path> <path d="M21 3v5h-5"></path>
</svg> </svg>
</div> </div>
<div @click="zoom = minZoom; dispX = -(canvas?.clientWidth ?? 0) / 2; dispY = -(canvas?.clientHeight ?? 0) / 2;" class="canvas-control-item" aria-label="Zoom to fit" data-tooltip-position="left"> <div @click="reset" class="canvas-control-item" aria-label="Zoom to fit" data-tooltip-position="left">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-maximize"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-maximize">
<path d="M8 3H5a2 2 0 0 0-2 2v3"></path> <path d="M8 3H5a2 2 0 0 0-2 2v3"></path>
<path d="M21 8V5a2 2 0 0 0-2-2h-3"></path> <path d="M21 8V5a2 2 0 0 0-2-2h-3"></path>
@ -173,7 +189,7 @@ function bezier(from: { x: number, y: number }, fromSide: 'bottom' | 'top' | 'le
</div> </div>
</div> </div>
</div> </div>
<div ref="canvas" class="canvas" :style="{transform: `translate(${centerX}px, ${centerY}px) scale(${zoom}) translate(${dispX}px, ${dispY}px)`}"> <div class="canvas" :style="{transform: `translate(${centerX}px, ${centerY}px) scale(${zoom}) translate(${dispX}px, ${dispY}px)`}">
<svg class="canvas-edges"> <svg class="canvas-edges">
<CanvasEdge v-for="edge of props.canvas.body.edges" :key="edge.id" :path="path(getNode(edge.fromNode)!, edge.fromSide, getNode(edge.toNode)!, edge.toSide)" :color="edge.color"/> <CanvasEdge v-for="edge of props.canvas.body.edges" :key="edge.id" :path="path(getNode(edge.fromNode)!, edge.fromSide, getNode(edge.toNode)!, edge.toSide)" :color="edge.color"/>
</svg> </svg>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="site-body-left-column"> <div class="site-body-left-column">
<div class="site-body-left-column-inner"> <div class="site-body-left-column-inner">
<NuxtLink class="site-body-left-column-site-logo" aria-label="Developer Documentation logo" :href="'/Home'"> <NuxtLink class="site-body-left-column-site-logo" aria-label="Developer Documentation logo" :href="'/'">
<img aria-hidden="true" src="https://publish-01.obsidian.md/access/caa27d6312fe5c26ebc657cc609543be/Assets/obsidian-lockup-docs.svg" style=""> <img aria-hidden="true" src="https://publish-01.obsidian.md/access/caa27d6312fe5c26ebc657cc609543be/Assets/obsidian-lockup-docs.svg" style="">
</NuxtLink> </NuxtLink>
<NuxtLink class="site-body-left-column-site-name" aria-label="Accueil" :href="'/Home'">Accueil</NuxtLink> <NuxtLink class="site-body-left-column-site-name" aria-label="Accueil" :href="'/Home'">Accueil</NuxtLink>
@ -15,7 +15,7 @@
</div> </div>
<div class="tree-item-children"> <div class="tree-item-children">
<ContentNavigation v-slot="{ navigation }"> <ContentNavigation v-slot="{ navigation }">
<NavigationLink v-if="!!navigation" v-for="link of navigation" :link="link" /> <NavigationLink v-if="!!navigation" v-for="link of navigation.filter(e => !['/tags', '/'].includes(e?._path ?? ''))" :link="link" />
</ContentNavigation> </ContentNavigation>
</div> </div>
</div> </div>

View File

@ -31,14 +31,14 @@ const collapsed = ref(!useRoute().path.startsWith(props.link._path));
<path d="M3 8L12 17L21 8"></path> <path d="M3 8L12 17L21 8"></path>
</svg> </svg>
</div> </div>
<div class="tree-item-inner">{{ props.link.title }}</div> <div class="tree-item-inner">{{ link.title }}</div>
</div> </div>
<div v-if="!collapsed" class="tree-item-children"> <div v-if="!collapsed" class="tree-item-children">
<NavigationLink v-if="hasChildren" v-for="link of props.link.children" :link="link"/> <NavigationLink v-if="hasChildren" v-for="l of link.children" :link="l"/>
</div> </div>
</template> </template>
<NuxtLink v-else class="tree-item-self" :to="props.link._path" :active-class="'mod-active'"> <NuxtLink v-else class="tree-item-self" :to="link._path" :active-class="'mod-active'">
<div class="tree-item-inner">{{ props.link.title }}</div> <div class="tree-item-inner">{{ link.title }}<span v-if="link"></span></div>
</NuxtLink> </NuxtLink>
</div> </div>
</template> </template>

View File

@ -19,7 +19,7 @@ const hasChildren = computed(() => {
<template> <template>
<div class="tree-item"> <div class="tree-item">
<div class="tree-item-self" :class="{'is-clickable': hasChildren}" data-path="{{ props.link.title }}"> <div class="tree-item-self" :class="{'is-clickable': hasChildren}" data-path="{{ props.link.title }}">
<a class="tree-item-inner" :href="'#' + props.link.id">{{ props.link.text }}</a> <NuxtLink no-prefetch class="tree-item-inner" :href="{hash: '#' + props.link.id}">{{ props.link.text }}</NuxtLink>
</div> </div>
<div class="tree-item-children"> <div class="tree-item-children">
<TocLink v-if="hasChildren" v-for="link of props.link.children" :link="link" /> <TocLink v-if="hasChildren" v-for="link of props.link.children" :link="link" />

View File

@ -6,6 +6,7 @@ interface ParsedContentExtended extends Omit<ParsedContent, 'body'> {
body: MarkdownRoot | CanvasContent | null; body: MarkdownRoot | CanvasContent | null;
} }
const tags = inject('tags') as ParsedContent;
const props = defineProps({ const props = defineProps({
href: { href: {
type: String, type: String,
@ -16,7 +17,7 @@ const props = defineProps({
default: undefined, default: undefined,
required: false required: false
} }
}) });
function sluggify(s: string): string { function sluggify(s: string): string {
return s return s
@ -29,28 +30,20 @@ function sluggify(s: string): string {
function flatten(val: TocLink[]): TocLink[] { function flatten(val: TocLink[]): TocLink[] {
return val.flatMap ? val?.flatMap((e: TocLink) => e.children ? [e, ...flatten(e.children)] : e) : val; return val.flatMap ? val?.flatMap((e: TocLink) => e.children ? [e, ...flatten(e.children)] : e) : val;
} }
function handleResult(result: ParsedContentExtended, tag: boolean = false) {
const link = (props.href.startsWith('/') ? '' : '/') + (props.href.includes('#') ? props.href.substring(0, props.href.indexOf('#')) : props.href).replace(/\..*$/, '').replace("/index", '');
const anchor = props.href.includes('#') ? props.href.substring(props.href.indexOf('#'), props.href.length) : '';
let content = ref<ParsedContentExtended>(), loading = ref(true);
if(props.href !== '')
{
queryContent().where({ _path: new RegExp("/" + sluggify(link) + '$', 'i') }).findOne().then((result: ParsedContentExtended) => {
content.value = result;
loading.value = false; loading.value = false;
let body = result.body; let body: MarkdownRoot | CanvasContent | null = JSON.parse(JSON.stringify(result.body));
if (anchor && result && body) { if (anchor && result && body) {
if (result._type == 'markdown' && (body as MarkdownRoot).children) { if (result._type == 'markdown' && (body as MarkdownRoot).children) {
body = body as MarkdownRoot; body = body as MarkdownRoot;
const id = flatten(body?.toc?.links ?? []).find(e => "#" + sluggify(e.id) === anchor); const id = flatten(body?.toc?.links ?? []).find(e => "#" + sluggify(e.id) === anchor.replaceAll('/', ''));
const tag = `h${id?.depth ?? 0}`; const tag = `h${id?.depth ?? 0}`;
const startIdx = body.children.findIndex(e => e.tag === tag && e?.props?.id === id?.id) ?? 0; const startIdx = body.children.findIndex(e => e.tag === tag && e?.props?.id === id?.id) ?? 0;
const nbr = body.children.findIndex((e, i) => i > startIdx && e.tag?.match(new RegExp(`h[1-${id?.depth ?? 1}]`))); const nbr = body.children.findIndex((e, i) => i > startIdx && e.tag?.match(new RegExp(`h[1-${id?.depth ?? 1}]`)));
body.children = body.children.splice(startIdx, (nbr === -1 ? body.children.length : nbr) - startIdx); body.children = [...body.children].splice(startIdx + (tag ? 1 : 0), (nbr === -1 ? body.children.length : nbr) - (startIdx + (tag ? 1 : 0)));
} }
else if (result._type == 'canvas') { else if (result._type == 'canvas') {
body = body as CanvasContent; body = body as CanvasContent;
@ -60,9 +53,36 @@ if(props.href !== '')
body.edges = body.edges.filter(e => nodes?.includes(e.fromNode) && nodes?.includes(e.toNode)); body.edges = body.edges.filter(e => nodes?.includes(e.fromNode) && nodes?.includes(e.toNode));
} }
} }
}).catch(() => {
content.value = JSON.parse(JSON.stringify({ ...result, body: body }));
}
const link = (props.href.startsWith('/') ? '' : '/') + (props.href.includes('#') ? props.href.substring(0, props.href.indexOf('#')) : props.href).replace(/\..*$/, '').replace("/index", '');
const anchor = props.href.includes('#') ? props.href.substring(props.href.indexOf('#'), props.href.length) : '';
let content = ref<ParsedContentExtended | null | undefined>(null), loading = ref(false), isTag = ref(false);
function loadContent()
{
loading.value = true;
if(props.href !== '' && !props.href.startsWith('/tags#'))
{
queryContent().where({ _path: new RegExp("/" + sluggify(link) + '$', 'i') }).findOne().then(handleResult).catch(() => {
loading.value = false; loading.value = false;
content.value = undefined;
}); });
}
else
{
isTag.value = true;
if(tags.value)
handleResult(tags.value, true);
else
{
loading.value = false;
content.value = undefined;
}
}
} }
const hovered = ref(false), pos = ref<any>(); const hovered = ref(false), pos = ref<any>();
@ -81,10 +101,13 @@ function showPreview(e: Event, bbox: boolean) {
if(rect.right + 550 < window.innerWidth) if(rect.right + 550 < window.innerWidth)
r.left = rect.left + "px"; r.left = rect.left + "px";
else else
r.right = (window.innerWidth - rect.left + 4) + "px"; r.right = (window.innerWidth - rect.right + 4) + "px";
pos.value = r; pos.value = r;
} }
hovered.value = true; hovered.value = true;
if(content.value === null)
loadContent();
} }
function hidePreview(e: Event) { function hidePreview(e: Event) {
timeout = setTimeout(() => hovered.value = false, 300); timeout = setTimeout(() => hovered.value = false, 300);
@ -93,17 +116,17 @@ function hidePreview(e: Event) {
<template > <template >
<template v-if="href !== ''"> <template v-if="href !== ''">
<NuxtLink custom no-prefetch v-slot="{ href: hrefSlot, navigate }" :href="{ path: content?._path ?? link, hash: anchor }" :target="target"> <NuxtLink custom no-prefetch v-slot="{ href: hrefSlot, navigate }" :href="isTag ? undefined : { path: content?._path ?? link, hash: anchor }" :target="target">
<a :href="hrefSlot" @click="navigate" @mouseenter="(e) => showPreview(e, true)" @mouseleave="hidePreview" v-bind="$attrs"><slot ></slot></a> <a :href="hrefSlot" @click="(isTag ? (e: Event) => e.preventDefault() : navigate)" @mouseenter="(e) => showPreview(e, true)" @mouseleave="hidePreview" v-bind="$attrs"><slot ></slot></a>
</NuxtLink> </NuxtLink>
<Teleport to="body" v-if="hovered && (loading || !!content)"> <Teleport to="body" v-if="hovered && (loading || !!content)">
<div class="popover hover-popover is-loaded" :style="pos" @mouseenter="(e) => showPreview(e, false)" @mouseleave="hidePreview"> <div class="popover hover-popover is-loaded" :style="pos" @mouseenter="(e) => showPreview(e, false)" @mouseleave="hidePreview">
<template v-if="!!content"> <template v-if="!!content">
<div class="markdown-embed" v-if="content._type == 'markdown' && ((content as ParsedContent)?.body?.children?.length ?? 0) > 0"> <div class="markdown-embed" :class="{'tag-embed': isTag}" v-if="content._type == 'markdown' && ((content as ParsedContent)?.body?.children?.length ?? 0) > 0">
<div class="markdown-embed-content"> <div class="markdown-embed-content">
<div class="markdown-preview-view markdown-rendered node-insert-event hide-title"> <div class="markdown-preview-view markdown-rendered node-insert-event hide-title">
<div class="markdown-preview-sizer markdown-preview-section" style="padding-bottom: 0px;"> <div class="markdown-preview-sizer markdown-preview-section" style="padding-bottom: 0px;">
<h1 v-if="content?.title">{{ content?.title }}</h1> <h1 v-if="content?.title && !isTag">{{ content?.title }}</h1>
<ContentRenderer :key="content._id" :value="content"/> <ContentRenderer :key="content._id" :value="content"/>
</div> </div>
</div> </div>
@ -116,12 +139,14 @@ function hidePreview(e: Event) {
<div class="not-found-container"> <div class="not-found-container">
<div class="not-found-image"></div> <div class="not-found-image"></div>
<div class="not-found-title">Impossible de prévisualiser</div> <div class="not-found-title">Impossible de prévisualiser</div>
<div class="not-found-description">Cliquez sur le lien pour accéder au contenu</div> <div v-if="!isTag" class="not-found-description">Cliquez sur le lien pour accéder au contenu</div>
</div> </div>
</div> </div>
</template> </template>
<div v-else> <div v-else class="preload" style="text-align:center">
<svg style="width:50px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<path style="transform-origin:50px 50px;animation:1s linear infinite rotate" fill="currentColor" d="M73,50c0-12.7-10.3-23-23-23S27,37.3,27,50 M30.9,50c0-10.5,8.5-19.1,19.1-19.1S69.1,39.5,69.1,50"/>
</svg>
</div> </div>
</div> </div>
</Teleport> </Teleport>

View File

@ -1,20 +0,0 @@
<template>
<div class="render-container-inner" >
<div class="publish-renderer">
<div class="markdown-preview-view markdown-rendered node-insert-event hide-title">
<div class="markdown-preview-sizer markdown-preview-section" style="padding-bottom: 0px;">
<h1>Bonjour :)</h1>
</div>
</div>
<div class="extra-title">
<span class="extra-title-text">Home</span>
<span aria-label="Close page" role="button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-x">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</span>
</div>
</div>
</div>
</template>

View File

@ -1,13 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
const { page } = useContent() const { page } = useContent()
useContentHead(page);
/*function toggleCollapse(e: HTMLElement)
{
e.classList.toggle('is-collapsed');
const children = [...e.children];
children.splice(0, 1);
children.forEach(e => e.classList);
}*/
onMounted(() => { onMounted(() => {
document.querySelectorAll('.callout.is-collapsible .callout-title').forEach(e => { document.querySelectorAll('.callout.is-collapsible .callout-title').forEach(e => {

View File

@ -1,3 +0,0 @@
<template>
Tags
</template>

View File

@ -1,12 +0,0 @@
<script setup lang="ts">
const query = await queryContent().where({
tags: { $exists: true }
}).find();
</script>
<template>
<div v-for="article in query" :key="article._path">
<h2>{{ article.title }}</h2>
<p>{{ article.description }}</p>
</div>
</template>