Finish PreviewContent and add ProseA icon for none markdown files

This commit is contained in:
Peaceultime 2024-08-20 23:51:00 +02:00
parent 04785ecf27
commit 7e318d4a12
22 changed files with 270 additions and 278 deletions

25
app.vue
View File

@ -1,8 +1,25 @@
<script setup lang="ts">
const layout = ref();
const toggled = ref(false);
</script>
<template>
<div class="published-container">
<NuxtLayout>
<NuxtPage></NuxtPage>
</NuxtLayout>
<div class="published-container" :class="{'has-navigation': layout?.layoutRef?.navigation ?? false}">
<div class="site-nav-bar">
<div>
<div class="gapx-3 flex align-stretch">
<NuxtLink class="site-nav-bar-text" aria-label="Accueil" :to="{ path: '/', force: true }"><ThemeIcon icon="logo" :width=40 :height=40 /></NuxtLink>
<NuxtLink class="site-nav-bar-text mobile-hidden" aria-label="Projets" :to="{ path: `/explorer`, force: true }" active-class="mod-active">Projets</NuxtLink>
<NuxtLink class="site-nav-bar-text mobile-hidden" aria-label="Editeur" :to="{ path: '/editing', force: true }" :class="{ 'mod-active': $route.path.startsWith('/editing') }">Editeur</NuxtLink>
</div>
</div>
<div class="mobile-bigger"><SearchView /></div>
<div class="ps-1 gapx-1 flex align-center">
<ThemeSwitch class="mobile-hidden" />
<NuxtLink class="site-login" :to="{ path: '/user/profile', force: true }"><ThemeIcon icon="user" :width=32 :height=32 /></NuxtLink>
</div>
</div>
<NuxtLayout ref="layout"><NuxtPage/></NuxtLayout>
<div class="site-footer">
<p>Copyright Peaceultime - 2024</p>
<NuxtLink :to="{ path: '/third-party', force: true }">Applications tierces et crédits</NuxtLink>

View File

@ -263,6 +263,15 @@ html.light-mode .light-block {
}
.loading {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.loading:after {
content: '';
width: 24px;
height: 24px;
border: 4px solid var(--color-purple);
@ -371,3 +380,8 @@ html.light-mode .light-block {
.arrow-group .arrow-group-item:hover {
background-color: var(--background-primary-alt);
}
.link-icon {
display: inline-block;
vertical-align: sub;
}

View File

@ -9,6 +9,8 @@ defineProps<Prop>();
</script>
<template>
<span>
<img :src="`/icons/${icon}.light.svg`" class="dark-hidden light-block" :width="width" :height="height" />
<img :src="`/icons/${icon}.dark.svg`" class="dark-block light-hidden" :width="width" :height="height" />
</span>
</template>

View File

@ -36,14 +36,16 @@ if(props.node.color !== undefined)
</script>
<template>
<div class="canvas-node" :class="classes" :style="{transform: `translate(${node.x}px, ${node.y}px)`, width: `${node.width}px`, height: `${node.height}px`, '--canvas-node-width': `${node.width}px`, '--canvas-node-height': `${node.height}px`, '--canvas-color': node?.color?.startsWith('#') ? hexToRgb(node.color) : undefined}">
<div class="canvas-node" :class="classes"
:style="{transform: `translate(${node.x}px, ${node.y}px)`, width: `${node.width}px`, height: `${node.height}px`, '--canvas-node-width': `${node.width}px`, '--canvas-node-height': `${node.height}px`, '--canvas-color': node?.color?.startsWith('#') ? hexToRgb(node.color) : undefined}">
<div class="canvas-node-container">
<template v-if="node.type === 'group' || zoom > Math.min(0.38, 1000 / size)">
<div class="canvas-node-content markdown-embed">
<div v-if="node.text?.length > 0" class="markdown-embed-content node-insert-event" style="">
<div class="markdown-preview-view markdown-rendered node-insert-event show-indentation-guide allow-fold-headings allow-fold-lists">
<div
class="markdown-preview-view markdown-rendered node-insert-event show-indentation-guide allow-fold-headings allow-fold-lists">
<div class="markdown-preview-sizer markdown-preview-section">
<Markdown v-model="node.text"/>
<Markdown v-model="node.text" />
</div>
</div>
</div>
@ -52,7 +54,9 @@ if(props.node.color !== undefined)
<template v-else>
<div class="canvas-node-placeholder">
<div class="canvas-icon-placeholder">
<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-align-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-align-left">
<line x1="21" y1="6" x2="3" y2="6"></line>
<line x1="15" y1="12" x2="3" y2="12"></line>
<line x1="17" y1="18" x2="3" y2="18"></line>
@ -61,6 +65,8 @@ if(props.node.color !== undefined)
</div>
</template>
</div>
<div v-if="node.type === 'group' && node.label !== undefined" class="canvas-group-label" :class="{'mod-foreground-dark': darken(getColor(node?.color ?? '')), 'mod-foreground-light': !darken(getColor(node?.color ?? ''))}">{{ node.label }}</div>
<div v-if="node.type === 'group' && node.label !== undefined" class="canvas-group-label"
:class="{ 'mod-foreground-dark': darken(getColor(props.node?.color ?? '')), 'mod-foreground-light': !darken(getColor(props.node?.color ?? ''))}">
{{ node.label }}</div>
</div>
</template>

View File

@ -19,6 +19,8 @@ let lastPinchLength = 0;
let _minX = +Infinity, _minY = +Infinity, _maxX = -Infinity, _maxY = -Infinity;
onMounted(async () => {
await nextTick();
props.canvas.nodes.forEach((e) => {
_minX = Math.min(_minX, e.x);
_minY = Math.min(_minY, e.y);
@ -47,10 +49,10 @@ const onResize = (event?: Event) => {
maxX.value = _maxX = _maxX + 32;
maxY.value = _maxY = _maxY + 32;
minZoom.value = Math.min((canvas.value?.clientWidth ?? 0) / (_maxX - _minX), (canvas.value?.clientHeight ?? 0) / (_maxY - _minY)) * 0.9;
minZoom.value = Math.min((canvas.value?.clientWidth ?? 1920) / (_maxX - _minX), (canvas.value?.clientHeight ?? 1080) / (_maxY - _minY)) * 0.9;
zoom.value = clamp(zoom.value, minZoom.value, 3);
bbox.value = canvas.value?.getBoundingClientRect();
bbox.value = (canvas.value ?? document.getElementById('canvas'))?.getBoundingClientRect();
centerX.value = (bbox.value?.width ?? 0) / 2;
centerY.value = (bbox.value?.height ?? 0) / 2;
@ -213,43 +215,47 @@ function getCenter(n: { x: number, y: number }, i: { x: number, y: number }, r:
</script>
<template>
<div ref="canvas" @pointerdown="onPointerDown" @wheel.passive="onWheel" @touchstart.passive="onTouchStart" @dragstart.prevent=""
class="canvas-wrapper node-insert-event mod-zoomed-out"
<Suspense>
<template #default>
<div id="canvas" ref="canvas" @pointerdown="onPointerDown" @wheel.passive="onWheel" @touchstart.passive="onTouchStart"
@dragstart.prevent="" class="canvas-wrapper node-insert-event mod-zoomed-out"
:style="{ '--zoom-multiplier': (1 / Math.pow(zoom, 0.7)) }">
<div class="canvas-controls" style="z-index: 999;">
<div class="canvas-control-group">
<div @click="zoom = clamp(zoom * 1.1, minZoom, 3)" class="canvas-control-item" aria-label="Zoom in"
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-plus">
<div @click="zoom = clamp(zoom * 1.1, minZoom, 3)" class="canvas-control-item"
aria-label="Zoom in" 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-plus">
<path d="M5 12h14"></path>
<path d="M12 5v14"></path>
</svg>
</div>
<div @click="zoom = 1" class="canvas-control-item" aria-label="Reset zoom" 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-rotate-cw">
<div @click="zoom = 1" class="canvas-control-item" aria-label="Reset zoom"
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-rotate-cw">
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"></path>
<path d="M21 3v5h-5"></path>
</svg>
</div>
<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">
<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">
<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="M3 16v3a2 2 0 0 0 2 2h3"></path>
<path d="M16 21h3a2 2 0 0 0 2-2v-3"></path>
</svg>
</div>
<div @click="zoom = clamp(zoom * 0.9, minZoom, 3)" class="canvas-control-item" aria-label="Zoom out"
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-minus">
<div @click="zoom = clamp(zoom * 0.9, minZoom, 3)" class="canvas-control-item"
aria-label="Zoom out" 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-minus">
<path d="M5 12h14"></path>
</svg>
</div>
@ -271,4 +277,9 @@ function getCenter(n: { x: number, y: number }, i: { x: number, y: number }, r:
</template>
</div>
</div>
</template>
<template #fallback>
<div class="loading"></div>
</template>
</Suspense>
</template>

View File

@ -1,11 +1,14 @@
<script setup lang="ts">
const route = useRoute();
const project = computed(() => parseInt(Array.isArray(route.params.projectId) ? '0' : route.params.projectId));
const project = computed(() => parseInt(route.params.projectId as string));
const { data: navigation, execute, status, error } = await useFetch(() => `/api/project/${project.value}/navigation`, {
immediate: false,
});
const { data: navigation, refresh } = await useLazyFetch(() => `/api/project/${project.value}/navigation`, { immediate: false });
if(!isNaN(project.value))
{
await refresh();
}
</script>
<template>

View File

@ -1,76 +0,0 @@
<template>
<Teleport to="#teleports">
<div class="popover hover-popover" :class="{'is-loaded': pending === false && content !== ''}" :style="{'top': (pos?.y ?? 0) - 4, 'left': (pos?.y ?? 0) + 4}">
<template v-if="display">
<div v-if="pending" class="loading"></div>
<div class="markdown-embed" v-else-if="content !== ''">
<div class="markdown-preview-view markdown-rendered">
<div class="markdown-embed-content">
<h1>{{ title }}</h1>
<Markdown v-model="content"></Markdown>
</div>
</div>
</div>
<div class="not-found-container" v-else>
<div class="not-found-image"></div>
<div class="not-found-title">Impossible d'afficher (ou vide)</div>
<div class="not-found-description">Cette page est actuellement vide ou impossible à traiter</div>
</div>
</template>
</div>
</Teleport>
<span ref="el" @mouseenter="show" @mouseleave="hide">
<slot></slot>
</span>
</template>
<script setup lang="ts">
import { useDebounceFn as debounce } from '@vueuse/core';
const props = defineProps({
project: {
type: Number,
required: true,
},
path: {
type: String,
required: true,
},
anchor: {
type: String,
required: false,
}
})
const content = ref(''), title = ref(''), display = ref(false), pending = ref(false);
const el = ref(), pos = ref<DOMRect>();
watch(display, () => display.value && !pending.value && content.value === '' && fetch());
onMounted(() => {
if(el && el.value)
{
pos.value = (el.value as HTMLDivElement).getBoundingClientRect();
debugger;
}
})
async function fetch()
{
pending.value = true;
const data = await $fetch(`/api/project/${props.project}/file`, {
method: 'get',
query: {
path: props.path,
},
});
pending.value = false;
if(data && data[0])
{
content.value = data[0].content;
title.value = data[0].title;
}
}
const show = debounce(() => display.value = true, 250), hide = debounce(() => display.value = false, 250);
</script>

View File

@ -0,0 +1,112 @@
<template>
<Teleport to="#teleports" v-if="display && (!fetched || loaded)">
<div class="popover hover-popover" :class="{'is-loaded': fetched}" :style="pos"
@mouseenter="debounce(show, 250)" @mouseleave="debounce(() => display = false, 250)">
<div v-if="pending" class="loading"></div>
<template v-else-if="content !==''">
<div v-if="type === 'Markdown'" class=" markdown-embed">
<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">
<h1>{{ title }}</h1>
<Markdown v-model="content"></Markdown>
</div>
</div>
</div>
</div>
<div v-else-if="type === 'Canvas'" class="canvas-embed is-loaded">
<CanvasRenderer :canvas="JSON.parse(content) " />
</div>
<div class="not-found-container" v-else>
<div class="not-found-image"></div>
<div class="not-found-title">Fichier vide</div>
<div class="not-found-description">Cette page est vide</div>
</div>
</template>
<div class="not-found-container" v-else>
<div class="not-found-image"></div>
<div class="not-found-title">Impossible d'afficher</div>
<div class="not-found-description">Cette page est impossible à traiter</div>
</div>
</div>
</Teleport>
<span ref="el" @mouseenter="debounce(show, 250)" @mouseleave="debounce(() => display = false, 250)">
<slot></slot>
</span>
</template>
<script setup lang="ts">
const props = defineProps({
project: {
type: Number,
required: true,
},
path: {
type: String,
required: true,
},
anchor: {
type: String,
required: false,
}
})
const content = ref(''), title = ref(''), type = ref(''), display = ref(false), pending = ref(false), fetched = ref(false);
const el = ref(), pos = ref<Record<string, string>>();
const loaded = computed(() => !pending.value && fetched.value && content.value !== '');
async function fetch()
{
fetched.value = true;
pending.value = true;
const data = await $fetch(`/api/project/${props.project}/file`, {
method: 'get',
query: {
path: props.path,
},
});
pending.value = false;
if(data && data[0])
{
content.value = data[0].content;
title.value = data[0].title;
type.value = data[0].type;
}
}
async function show()
{
if(display.value)
return;
if(!fetched.value)
await fetch();
const rect = (el.value as HTMLDivElement)?.getBoundingClientRect();
if(!rect)
return;
const r: Record<string, string> = {};
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)
r.left = rect.left + "px";
else
r.right = (window.innerWidth - rect.right + 4) + "px";
pos.value = r;
display.value = true;
}
let debounceFn: () => void, timeout: NodeJS.Timeout;
function debounce(fn: () => void, ms: number)
{
debounceFn = fn;
clearTimeout(timeout);
timeout = setTimeout(debounceFn, ms);
}
</script>

View File

@ -1,11 +1,16 @@
<template>
<NuxtLink v-if="data && data[0] && status !== 'pending'" :to="{ path: `/explorer/${project}${data[0].path}`, hash: hash }" :class="class">
<NuxtLink class="preview-link" v-if="data && data[0] && status !== 'pending'"
:to="{ path: `/explorer/${project}${data[0].path}`, hash: hash }" :class="class">
<PreviewContent :project="project" :path="data[0].path" :anchor="hash">
<slot v-bind="$attrs"></slot>
<ThemeIcon class="link-icon" v-if="data && data[0] && data[0].type !== 'Markdown'" :height="20" :width="20"
:icon="`link-${data[0].type.toLowerCase()}`" />
</PreviewContent>
</NuxtLink>
<NuxtLink v-else-if="href" :to="{ path: href }" :class="class">
<slot v-bind="$attrs"></slot>
<ThemeIcon class="link-icon" v-if="data && data[0] && data[0].type !== 'Markdown'" :height="20" :width="20"
:icon="`link-${data[0].type.toLowerCase()}`" />
</NuxtLink>
<slot :class="class" v-else v-bind="$attrs"></slot>
</template>
@ -32,7 +37,7 @@ const { data, status } = await useFetch(`/api/project/${project.value}/file`, {
query: {
search: `%${pathname}`
},
transform: (data) => data?.map(e => ({ path: e.path })),
transform: (data) => data?.map(e => ({ path: e.path, type: e.type })),
key: `file:${project.value}:%${pathname}`,
dedupe: 'defer'
});

BIN
db.sqlite

Binary file not shown.

View File

@ -1,68 +1,10 @@
<script lang="ts">
let icon: HTMLDivElement | null;
function toggleLeftPanel(_: Event): void {
document.querySelector('.published-container')?.classList.toggle('is-left-column-open');
}
function hideLeftPanel(_: Event): void {
document.querySelector('.published-container')?.classList.remove('is-left-column-open');
}
</script>
<script setup lang="ts">
const { id: project, home, get } = useProject();
if(project.value !== 0 && home.value === null)
{
await useAsyncData(`project:get:${project}`, get);
}
const toggled = ref(false);
onMounted(() => {
icon = document.querySelector('.site-nav-bar .clickable-icon');
icon?.removeEventListener('click', toggleLeftPanel);
icon?.addEventListener('click', toggleLeftPanel);
defineExpose({
navigation: false,
});
</script>
<template>
<div class="site-nav-bar">
<div>
<div class="clickable-icon">
<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-menu">
<line x1="4" y1="12" x2="20" y2="12"></line>
<line x1="4" y1="6" x2="20" y2="6"></line>
<line x1="4" y1="18" x2="20" y2="18"></line>
</svg>
</div>
<div class="gapx-3 flex align-stretch">
<NuxtLink @click="hideLeftPanel" class="site-nav-bar-text" aria-label="Accueil" :to="{ path: '/', force: true }">
<ThemeIcon icon="logo" :width=40 :height=40 />
</NuxtLink>
<div class="site-nav-bar-text mobile-hidden">
<NuxtLink aria-label="Projet" :to="{ path: project === 0 ? `/explorer` : `/explorer/${project}${home}`, force: true }"
:class="{'mod-active': $route.path.startsWith('/explorer')}">Projet</NuxtLink>
<div class="arrow-down" :class="{active: toggled}" @click="toggled = !toggled"></div>
<div class="arrow-group" @click="toggled = false">
<NuxtLink class="arrow-group-item" aria-label="Projets" :to="{ path: '/explorer', force: true }">Liste des projets</NuxtLink>
</div>
</div>
<NuxtLink class="site-nav-bar-text mobile-hidden" aria-label="Editeur" :to="{ path: '/editing', force: true }"
:class="{'mod-active': $route.path.startsWith('/editing')}">Editeur</NuxtLink>
</div>
</div>
<div class="mobile-bigger">
<SearchView />
</div>
<div class="ps-1 gapx-1 flex align-center">
<ThemeSwitch class="mobile-hidden" />
<NuxtLink class="site-login" :to="{ path: '/user/profile', force: true }">
<ThemeIcon icon="user" :width=32 :height=32 />
</NuxtLink>
</div>
</div>
<div class="site-body">
<slot />
</div>

View File

@ -1,68 +1,10 @@
<script lang="ts">
let icon: HTMLDivElement | null;
function toggleLeftPanel(_: Event): void {
document.querySelector('.published-container')?.classList.toggle('is-left-column-open');
}
function hideLeftPanel(_: Event): void {
document.querySelector('.published-container')?.classList.remove('is-left-column-open');
}
</script>
<script setup lang="ts">
const { id: project, home, get } = useProject();
if(project.value !== 0 && home.value === null)
{
await useAsyncData(`project:get:${project}`, get);
}
const toggled = ref(false);
onMounted(() => {
icon = document.querySelector('.site-nav-bar .clickable-icon');
icon?.removeEventListener('click', toggleLeftPanel);
icon?.addEventListener('click', toggleLeftPanel);
defineExpose({
navigation: true,
});
</script>
<template>
<div class="site-nav-bar">
<div>
<div class="clickable-icon">
<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-menu">
<line x1="4" y1="12" x2="20" y2="12"></line>
<line x1="4" y1="6" x2="20" y2="6"></line>
<line x1="4" y1="18" x2="20" y2="18"></line>
</svg>
</div>
<div class="gapx-3 flex align-stretch">
<NuxtLink @click="hideLeftPanel" class="site-nav-bar-text" aria-label="Accueil" :to="{ path: '/', force: true }">
<ThemeIcon icon="logo" :width=40 :height=40 />
</NuxtLink>
<div class="site-nav-bar-text mobile-hidden">
<NuxtLink aria-label="Projet" :to="{ path: project === 0 ? `/explorer` : `/explorer/${project}${home}`, force: true }"
:class="{'mod-active': $route.path.startsWith('/explorer')}">Projet</NuxtLink>
<div class="arrow-down" :class="{active: toggled}" @click="toggled = !toggled"></div>
<div class="arrow-group" @click="toggled = false">
<NuxtLink class="arrow-group-item" aria-label="Projets" :to="{ path: '/explorer', force: true }">Liste des projets</NuxtLink>
</div>
</div>
<NuxtLink class="site-nav-bar-text mobile-hidden" aria-label="Editeur" :to="{ path: '/editing', force: true }"
:class="{'mod-active': $route.path.startsWith('/editing')}">Editeur</NuxtLink>
</div>
</div>
<div class="mobile-bigger">
<SearchView />
</div>
<div class="ps-1 gapx-1 flex align-center">
<ThemeSwitch class="mobile-hidden" />
<NuxtLink class="site-login" :to="{ path: '/user/profile', force: true }">
<ThemeIcon icon="user" :width=32 :height=32 />
</NuxtLink>
</div>
</div>
<div class="site-body">
<ExplorerNavigation></ExplorerNavigation>
<slot></slot>

View File

@ -9,11 +9,18 @@ export default defineNuxtConfig({
password: '699c46bd-9aaa-4364-ad01-510ee4fe7013'
}
},
app: {
pageTransition: false,
layoutTransition: false
},
components: [
{
path: '~/components',
pathPrefix: false,
},
],
security: {
rateLimiter: false
},
compatibilityDate: '2024-07-25'
})

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><path fill="white" d="M200 156a27.87 27.87 0 0 0-19.4 7.84l-28.28-22A27.8 27.8 0 0 0 156 128a28 28 0 0 0-.45-5l19.45-6.45a28.07 28.07 0 1 0-2.53-7.58L153 115.45A28 28 0 0 0 128 100a27.7 27.7 0 0 0-7.6 1.06l-9.5-21.37A28 28 0 1 0 96 84a27.7 27.7 0 0 0 7.6-1.06l9.5 21.37a27.95 27.95 0 0 0-8.46 39.1L74 170.61a28 28 0 1 0 5.32 6l30.6-27.2a27.92 27.92 0 0 0 37.44-1.23l28.28 22A28 28 0 1 0 200 156m0-72a20 20 0 1 1-20 20a20 20 0 0 1 20-20M76 56a20 20 0 1 1 20 20a20 20 0 0 1-20-20M56 212a20 20 0 1 1 20-20a20 20 0 0 1-20 20m72-64a20 20 0 1 1 20-20a20 20 0 0 1-20 20m72 56a20 20 0 1 1 20-20a20 20 0 0 1-20 20"/></svg>

After

Width:  |  Height:  |  Size: 700 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><path fill="black" d="M200 156a27.87 27.87 0 0 0-19.4 7.84l-28.28-22A27.8 27.8 0 0 0 156 128a28 28 0 0 0-.45-5l19.45-6.45a28.07 28.07 0 1 0-2.53-7.58L153 115.45A28 28 0 0 0 128 100a27.7 27.7 0 0 0-7.6 1.06l-9.5-21.37A28 28 0 1 0 96 84a27.7 27.7 0 0 0 7.6-1.06l9.5 21.37a27.95 27.95 0 0 0-8.46 39.1L74 170.61a28 28 0 1 0 5.32 6l30.6-27.2a27.92 27.92 0 0 0 37.44-1.23l28.28 22A28 28 0 1 0 200 156m0-72a20 20 0 1 1-20 20a20 20 0 0 1 20-20M76 56a20 20 0 1 1 20 20a20 20 0 0 1-20-20M56 212a20 20 0 1 1 20-20a20 20 0 0 1-20 20m72-64a20 20 0 1 1 20-20a20 20 0 0 1-20 20m72 56a20 20 0 1 1 20-20a20 20 0 0 1-20 20"/></svg>

After

Width:  |  Height:  |  Size: 700 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><path fill="white" d="m210.83 85.17l-56-56A4 4 0 0 0 152 28H56a12 12 0 0 0-12 12v176a12 12 0 0 0 12 12h144a12 12 0 0 0 12-12V88a4 4 0 0 0-1.17-2.83M156 41.65L198.34 84H156ZM200 220H56a4 4 0 0 1-4-4V40a4 4 0 0 1 4-4h92v52a4 4 0 0 0 4 4h52v124a4 4 0 0 1-4 4m-36-84a4 4 0 0 1-4 4H96a4 4 0 0 1 0-8h64a4 4 0 0 1 4 4m0 32a4 4 0 0 1-4 4H96a4 4 0 0 1 0-8h64a4 4 0 0 1 4 4"/></svg>

After

Width:  |  Height:  |  Size: 459 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><path fill="black" d="m210.83 85.17l-56-56A4 4 0 0 0 152 28H56a12 12 0 0 0-12 12v176a12 12 0 0 0 12 12h144a12 12 0 0 0 12-12V88a4 4 0 0 0-1.17-2.83M156 41.65L198.34 84H156ZM200 220H56a4 4 0 0 1-4-4V40a4 4 0 0 1 4-4h92v52a4 4 0 0 0 4 4h52v124a4 4 0 0 1-4 4m-36-84a4 4 0 0 1-4 4H96a4 4 0 0 1 0-8h64a4 4 0 0 1 4 4m0 32a4 4 0 0 1-4 4H96a4 4 0 0 1 0-8h64a4 4 0 0 1 4 4"/></svg>

After

Width:  |  Height:  |  Size: 459 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><path fill="white" d="M241.72 113a11.88 11.88 0 0 0-9.73-5H212V88a12 12 0 0 0-12-12h-70.67l-28.8-21.6a12.05 12.05 0 0 0-7.2-2.4H40a12 12 0 0 0-12 12v144a4 4 0 0 0 4 4h179.09a4 4 0 0 0 3.79-2.74l28.49-85.47a11.86 11.86 0 0 0-1.65-10.79M40 60h53.33a4 4 0 0 1 2.4.8l29.87 22.4a4 4 0 0 0 2.4.8h72a4 4 0 0 1 4 4v20H69.76a12 12 0 0 0-11.38 8.21L36 183.35V64a4 4 0 0 1 4-4m195.78 61.26L208.2 204H37.55L66 118.74a4 4 0 0 1 3.76-2.74H232a4 4 0 0 1 3.79 5.26Z"/></svg>

After

Width:  |  Height:  |  Size: 545 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><path fill="black" d="M241.72 113a11.88 11.88 0 0 0-9.73-5H212V88a12 12 0 0 0-12-12h-70.67l-28.8-21.6a12.05 12.05 0 0 0-7.2-2.4H40a12 12 0 0 0-12 12v144a4 4 0 0 0 4 4h179.09a4 4 0 0 0 3.79-2.74l28.49-85.47a11.86 11.86 0 0 0-1.65-10.79M40 60h53.33a4 4 0 0 1 2.4.8l29.87 22.4a4 4 0 0 0 2.4.8h72a4 4 0 0 1 4 4v20H69.76a12 12 0 0 0-11.38 8.21L36 183.35V64a4 4 0 0 1 4-4m195.78 61.26L208.2 204H37.55L66 118.74a4 4 0 0 1 3.76-2.74H232a4 4 0 0 1 3.79 5.26Z"/></svg>

After

Width:  |  Height:  |  Size: 545 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><path fill="currentColor" d="M128 28a100 100 0 1 0 100 100A100.11 100.11 0 0 0 128 28M68.87 198.42a68 68 0 0 1 118.26 0a91.8 91.8 0 0 1-118.26 0m124.3-5.55a75.6 75.6 0 0 0-44.51-34a44 44 0 1 0-41.32 0a75.6 75.6 0 0 0-44.51 34a92 92 0 1 1 130.34 0M128 156a36 36 0 1 1 36-36a36 36 0 0 1-36 36"/></svg>

Before

Width:  |  Height:  |  Size: 386 B

View File

@ -19,11 +19,8 @@ export default defineEventHandler(async (e) => {
const content = db.query(`SELECT * FROM explorer_comments WHERE ${where.join(" and ")}`).all(criteria) as Comment[];
if(content.length > 0)
{
return content;
}
}
setResponseStatus(e, 404);
});

View File

@ -1,7 +1,7 @@
import useDatabase from '~/composables/useDatabase';
import type { File } from '~/types/api';
export default defineEventHandler(async (e) => {
export default defineCachedEventHandler(async (e) => {
const project = getRouterParam(e, "projectId");
const query = getQuery(e);
@ -11,6 +11,8 @@ export default defineEventHandler(async (e) => {
return;
}
console.log(query);
const where = ["project = $project"];
const criteria: Record<string, any> = { $project: project };
@ -48,4 +50,7 @@ export default defineEventHandler(async (e) => {
}
setResponseStatus(e, 404);
});
}, {
maxAge: 60*60*24,
getKey: (e) => `${getRouterParam(e, "projectId")}-${JSON.stringify(getQuery(e))}`
});