You've already forked obsidian-visualiser
Refactoring search, navigation, canvas and others to fit the new data model
This commit is contained in:
@@ -1,166 +1,95 @@
|
||||
<template>
|
||||
<Teleport to="body" v-if="!external && hovered && status !== 'pending'">
|
||||
<div class="popover hover-popover is-loaded" :style="pos" @mouseenter="(e) => showPreview(e, false)"
|
||||
@mouseleave="hidePreview">
|
||||
<template v-if="!!page && !!page[0]">
|
||||
<div class="markdown-embed" v-if="page[0].type === 'Markdown' && page[0].content.length > 0">
|
||||
<div class="markdown-embed-content">
|
||||
<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 v-if="page[0]?.title">{{ page[0]?.title }}</h1>
|
||||
<Markdown :content="page[0].content" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="page[0].type === 'Canvas'" class="canvas-embed is-loaded">
|
||||
<CanvasRenderer :canvas="JSON.parse(page[0].content)" />
|
||||
</div>
|
||||
<div class="markdown-embed" v-else>
|
||||
<div class="not-found-container">
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="loading"></div>
|
||||
</div>
|
||||
</Teleport>
|
||||
<NuxtLink :to="path" :class="class" noPrefetch @mouseenter="(e) => showPreview(e, true)" @mouseleave="hidePreview">
|
||||
<slot v-bind="$attrs"></slot>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { MarkdownRoot, ParsedContent, TocLink } from '@nuxt/content/dist/runtime/types';
|
||||
import type { Canvas, CanvasContent } from '~/types/canvas';
|
||||
import { parseURL } from 'ufo';
|
||||
|
||||
import { stringifyParsedURL, parseURL } from 'ufo';
|
||||
|
||||
interface ParsedContentExtended extends Omit<ParsedContent, 'body'> {
|
||||
body: MarkdownRoot | CanvasContent | null;
|
||||
}
|
||||
|
||||
const tags = inject('tags/descriptions') as ParsedContent;
|
||||
const props = defineProps({
|
||||
href: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
target: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
required: false
|
||||
}
|
||||
});
|
||||
|
||||
function sluggify(s: string): string {
|
||||
return s
|
||||
.split("/")
|
||||
.map((segment) => segment.normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/^\d\. */g, '').replace(/\s/g, "-").replace(/%/g, "-percent").replace(/\?/g, "-q").toLowerCase()) // slugify all segments
|
||||
.filter(e => !!e)
|
||||
.join("/") // always use / as sep
|
||||
.replace(/\/$/, "")
|
||||
}
|
||||
function flatten(val: TocLink[]): TocLink[] {
|
||||
return val.flatMap ? val?.flatMap((e: TocLink) => e.children ? [e, ...flatten(e.children)] : e) : val;
|
||||
}
|
||||
function handleResult(result: ParsedContentExtended, tag: boolean = false) {
|
||||
loading.value = false;
|
||||
let body: MarkdownRoot | CanvasContent | null = JSON.parse(JSON.stringify(result.body));
|
||||
|
||||
if (anchor && result && body) {
|
||||
if (result._type == 'markdown' && (body as MarkdownRoot).children) {
|
||||
body = body as MarkdownRoot;
|
||||
const id = flatten(body?.toc?.links ?? []).find(e => "#" + sluggify(e.id) === anchor.replaceAll('/', ''));
|
||||
const tag = `h${id?.depth ?? 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}]`)));
|
||||
|
||||
body.children = [...body.children].splice(startIdx + (tag ? 1 : 0), (nbr === -1 ? body.children.length : nbr) - (startIdx + (tag ? 1 : 0)));
|
||||
}
|
||||
else if (result._type == 'canvas') {
|
||||
body = body as CanvasContent;
|
||||
const nodes = body?.groups?.find(e => "#" + sluggify(e.name) === anchor)?.nodes;
|
||||
|
||||
body.nodes = body.nodes.filter(e => nodes?.includes(e.id));
|
||||
body.edges = body.edges.filter(e => nodes?.includes(e.fromNode) && nodes?.includes(e.toNode));
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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>();
|
||||
let timeout: NodeJS.Timeout;
|
||||
function showPreview(e: Event, bbox: boolean) {
|
||||
clearTimeout(timeout);
|
||||
if(bbox)
|
||||
{
|
||||
if (bbox) {
|
||||
const target = e.currentTarget as HTMLElement;
|
||||
const rect = target?.getBoundingClientRect();
|
||||
const r: any = {};
|
||||
if(rect.bottom + 450 < window.innerHeight)
|
||||
if (rect.bottom + 450 < window.innerHeight)
|
||||
r.top = (rect.bottom + 4) + "px";
|
||||
else
|
||||
r.bottom = (window.innerHeight - rect.top + 4) + "px";
|
||||
if(rect.right + 550 < window.innerWidth)
|
||||
if (rect.right + 550 < window.innerWidth)
|
||||
r.left = rect.left + "px";
|
||||
else
|
||||
r.right = (window.innerWidth - rect.right + 4) + "px";
|
||||
pos.value = r;
|
||||
}
|
||||
hovered.value = true;
|
||||
|
||||
if(content.value === null)
|
||||
loadContent();
|
||||
}
|
||||
function hidePreview(e: Event) {
|
||||
timeout = setTimeout(() => hovered.value = false, 300);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="href !== ''">
|
||||
<a @mouseenter="(e) => showPreview(e, true)" @mouseleave="hidePreview" v-bind="$attrs"
|
||||
:href="stringifyParsedURL({ host: '/explorer', pathname: (content?._path ?? href) + (isTag && anchor ? '/' + anchor.substring(1) : ''), hash: !isTag ? anchor : '', search: '' })"
|
||||
:target="target">
|
||||
<slot></slot>
|
||||
</a>
|
||||
<Teleport to="body" v-if="hovered && (loading || !!content)">
|
||||
<div class="popover hover-popover is-loaded" :style="pos" @mouseenter="(e) => showPreview(e, false)"
|
||||
@mouseleave="hidePreview">
|
||||
<template v-if="!!content">
|
||||
<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-preview-view markdown-rendered node-insert-event hide-title">
|
||||
<div class="markdown-preview-sizer markdown-preview-section"
|
||||
style="padding-bottom: 0px;">
|
||||
<h1 v-if="content?.title && !isTag">{{ content?.title }}</h1>
|
||||
<ContentRenderer :key="content._id" :value="content" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="content._type == 'canvas'" class="canvas-embed is-loaded">
|
||||
<CanvasRenderer :key="content._id" :canvas="(content as Canvas)" />
|
||||
</div>
|
||||
<div class="markdown-embed" v-else>
|
||||
<div class="not-found-container">
|
||||
<div class="not-found-image"></div>
|
||||
<div class="not-found-title">Impossible de prévisualiser</div>
|
||||
<div v-if="!isTag" class="not-found-description">Cliquez sur le lien pour accéder au contenu
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<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>
|
||||
</Teleport>
|
||||
</template>
|
||||
<NuxtLink v-bind="$attrs" :to="href" v-else>
|
||||
<slot></slot>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
interface Prop
|
||||
{
|
||||
href?: string;
|
||||
class?: string;
|
||||
project?: number;
|
||||
}
|
||||
const props = defineProps<Prop>();
|
||||
const path = ref(props.href), external = ref(false), hovered = ref(false), pos = ref<any>();
|
||||
let timeout: NodeJS.Timeout;
|
||||
|
||||
if (parseURL(props.href).protocol !== undefined)
|
||||
{
|
||||
external.value = true;
|
||||
}
|
||||
|
||||
let id = ref(props.project);
|
||||
if(id.value === undefined)
|
||||
{
|
||||
id = useProject().id;
|
||||
}
|
||||
|
||||
const { data: page, status, error, execute } = await useLazyFetch(`/api/project/${id.value}/file`, {
|
||||
query: {
|
||||
search: "%" + parseURL(props.href).pathname,
|
||||
}
|
||||
});
|
||||
|
||||
if(external.value)
|
||||
{
|
||||
execute();
|
||||
}
|
||||
|
||||
if (page.value && page.value[0])
|
||||
{
|
||||
path.value = `/explorer/${id.value}${page.value[0].path}`;
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user