Project list and starting editing
This commit is contained in:
parent
a3d0b3b5bd
commit
aba56bb034
27
app.vue
27
app.vue
|
|
@ -9,6 +9,15 @@ function hideLeftPanel(_: Event): void {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
const { id: project, home, get } = useProject();
|
||||||
|
|
||||||
|
if(project.value !== 0 && home.value === null)
|
||||||
|
{
|
||||||
|
const { data: useless } = await useAsyncData(`project:get:${project}`, get);
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggled = ref(false);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
icon = document.querySelector('.site-nav-bar .clickable-icon');
|
icon = document.querySelector('.site-nav-bar .clickable-icon');
|
||||||
icon?.removeEventListener('click', toggleLeftPanel);
|
icon?.removeEventListener('click', toggleLeftPanel);
|
||||||
|
|
@ -30,12 +39,18 @@ onMounted(() => {
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="gapx-3 flex align-stretch">
|
<div class="gapx-3 flex align-stretch">
|
||||||
<NuxtLink @click="hideLeftPanel" class="site-nav-bar-text" aria-label="Accueil" to="/">
|
<NuxtLink @click="hideLeftPanel" class="site-nav-bar-text" aria-label="Accueil" :to="{ path: '/', force: true }">
|
||||||
<ThemeIcon icon="logo" :width=40 :height=40 />
|
<ThemeIcon icon="logo" :width=40 :height=40 />
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink class="site-nav-bar-text mobile-hidden" aria-label="Projets" to="/explorer"
|
<div class="site-nav-bar-text mobile-hidden">
|
||||||
:class="{'mod-active': $route.path.startsWith('/explorer')}">Projets</NuxtLink>
|
<NuxtLink aria-label="Projet" :to="{ path: project === 0 ? `/explorer` : `/explorer/${project}${home}`, force: true }"
|
||||||
<NuxtLink class="site-nav-bar-text mobile-hidden" aria-label="Outils" to="/tools"
|
: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="Outils" :to="{ path: '/tools', force: true }"
|
||||||
:class="{'mod-active': $route.path.startsWith('/tools')}">Outils</NuxtLink>
|
:class="{'mod-active': $route.path.startsWith('/tools')}">Outils</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -44,7 +59,7 @@ onMounted(() => {
|
||||||
</div>
|
</div>
|
||||||
<div class="ps-1 gapx-1 flex align-center">
|
<div class="ps-1 gapx-1 flex align-center">
|
||||||
<ThemeSwitch class="mobile-hidden" />
|
<ThemeSwitch class="mobile-hidden" />
|
||||||
<NuxtLink class="site-login" to="/user/profile">
|
<NuxtLink class="site-login" :to="{ path: '/user/profile', force: true }">
|
||||||
<ThemeIcon icon="user" :width=32 :height=32 />
|
<ThemeIcon icon="user" :width=32 :height=32 />
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -55,7 +70,7 @@ onMounted(() => {
|
||||||
</div>
|
</div>
|
||||||
<div class="site-footer">
|
<div class="site-footer">
|
||||||
<p>Copyright Peaceultime - 2024</p>
|
<p>Copyright Peaceultime - 2024</p>
|
||||||
<NuxtLink href="/third-party">Applications tierces et crédits</NuxtLink>
|
<NuxtLink :to="{ path: '/third-party', force: true }">Applications tierces et crédits</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -286,6 +286,88 @@ html.light-mode .light-block {
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-size: var(--font-smaller);
|
font-size: var(--font-smaller);
|
||||||
/* font-style: italic; */
|
|
||||||
font-variant: all-petite-caps;
|
font-variant: all-petite-caps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.suggestion-subtext {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: var(--font-smaller);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-down {
|
||||||
|
border: 2px solid var(--background-modifier-border-focus);
|
||||||
|
border-top-color: transparent;
|
||||||
|
border-left-color: transparent;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
position: relative;
|
||||||
|
left: 8px;
|
||||||
|
top: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.projects-container {
|
||||||
|
flex: 1 1;
|
||||||
|
padding: 2em 4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-list {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-item {
|
||||||
|
margin: 1em;
|
||||||
|
padding: .5em;
|
||||||
|
border: 1px solid var(--background-modifier-border);
|
||||||
|
width: 45%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-title {
|
||||||
|
font-size: var(--font-ui-large);
|
||||||
|
font-weight: var(--font-bold);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-user {
|
||||||
|
font-size: var(--font-small);
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-pages {
|
||||||
|
display: inline;
|
||||||
|
float: inline-end;
|
||||||
|
font-size: var(--font-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-group {
|
||||||
|
position: absolute;
|
||||||
|
left: -1em;
|
||||||
|
top: calc(100% + 7px);
|
||||||
|
border: 1px solid var(--background-modifier-border);
|
||||||
|
background-color: var(--background-primary);
|
||||||
|
z-index: 99;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-down.active + .arrow-group {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-group .arrow-group-item {
|
||||||
|
padding: 8px 1em;
|
||||||
|
font-weight: var(--font-medium);
|
||||||
|
color: var(--text-normal);
|
||||||
|
border-bottom: 1px solid var(--background-modifier-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-group .arrow-group-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-group .arrow-group-item:hover {
|
||||||
|
background-color: var(--background-primary-alt);
|
||||||
|
}
|
||||||
|
|
@ -2636,7 +2636,7 @@ body:not(.native-scrollbars) * {
|
||||||
}
|
}
|
||||||
|
|
||||||
.suggestion-item {
|
.suggestion-item {
|
||||||
cursor: var(--cursor);
|
cursor: pointer;
|
||||||
padding: var(--size-2-3) var(--size-4-3);
|
padding: var(--size-2-3) var(--size-4-3);
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,4 @@ const afterText = computed(() => {
|
||||||
const pos = props.text.toLowerCase().indexOf(props.matched.toLowerCase()) + props.matched.length;
|
const pos = props.text.toLowerCase().indexOf(props.matched.toLowerCase()) + props.matched.length;
|
||||||
return props.text.substring(pos);
|
return props.text.substring(pos);
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(props, beforeText.value, afterText.value);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -5,14 +5,27 @@ function hideLeftPanel(_: Event)
|
||||||
}
|
}
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const showing = ref(false);
|
||||||
|
|
||||||
const project = parseInt(Array.isArray(route.params.projectId) ? '' : route.params.projectId);
|
const project = computed(() => parseInt(Array.isArray(route.params.projectId) ? '0' : route.params.projectId));
|
||||||
|
|
||||||
const { data: navigation } = await useFetch(() => isNaN(project) ? '' : `/api/project/${project}/navigation`);
|
const { data: navigation, execute, status, error } = await useFetch(() => `/api/project/${project.value}/navigation`, {
|
||||||
|
immediate: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if(route.params.projectId && project.value !== 0)
|
||||||
|
{
|
||||||
|
showing.value = true;
|
||||||
|
execute();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
showing.value = false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="site-body-left-column">
|
<div :class="{'desktop-hidden': !showing}" class="site-body-left-column">
|
||||||
<div class="site-body-left-column-inner">
|
<div class="site-body-left-column-inner">
|
||||||
<div class="nav-view-outer">
|
<div class="nav-view-outer">
|
||||||
<div class="nav-view">
|
<div class="nav-view">
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,32 @@
|
||||||
|
<template>
|
||||||
|
<slot
|
||||||
|
:data="data?.data"
|
||||||
|
:body="data?.body"
|
||||||
|
:toc="data?.toc"
|
||||||
|
:excerpt="data?.excerpt"
|
||||||
|
:error="error"
|
||||||
|
>
|
||||||
|
<MDCRenderer
|
||||||
|
v-if="body"
|
||||||
|
:tag="tag"
|
||||||
|
:class="props.class"
|
||||||
|
:body="body"
|
||||||
|
:data="data?.data"
|
||||||
|
:unwrap="props.unwrap"
|
||||||
|
:components="{
|
||||||
|
a: ProseA,
|
||||||
|
h1: ProseH1,
|
||||||
|
h2: ProseH2,
|
||||||
|
h3: ProseH3,
|
||||||
|
h4: ProseH4,
|
||||||
|
h5: ProseH5,
|
||||||
|
h6: ProseH6,
|
||||||
|
blockquote: ProseBlockquote,
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ProseA from "~/components/content/prose/ProseA.vue";
|
import ProseA from "~/components/content/prose/ProseA.vue";
|
||||||
import ProseH1 from "~/components/content/prose/ProseH1.vue";
|
import ProseH1 from "~/components/content/prose/ProseH1.vue";
|
||||||
|
|
@ -7,27 +36,54 @@ import ProseH4 from "~/components/content/prose/ProseH4.vue";
|
||||||
import ProseH5 from "~/components/content/prose/ProseH5.vue";
|
import ProseH5 from "~/components/content/prose/ProseH5.vue";
|
||||||
import ProseH6 from "~/components/content/prose/ProseH6.vue";
|
import ProseH6 from "~/components/content/prose/ProseH6.vue";
|
||||||
import ProseBlockquote from "~/components/content/prose/ProseBlockquote.vue";
|
import ProseBlockquote from "~/components/content/prose/ProseBlockquote.vue";
|
||||||
const props = defineProps<{
|
|
||||||
content: string
|
|
||||||
}>();
|
|
||||||
const parser = useMarkdown();
|
|
||||||
const ast = await parser(props.content);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
import { hash } from 'ohash'
|
||||||
<Suspense>
|
import { useAsyncData } from 'nuxt/app'
|
||||||
<template #fallback>
|
import { watch, computed } from 'vue'
|
||||||
<div class="loading"></div>
|
|
||||||
</template>
|
const props = defineProps({
|
||||||
<MDCRenderer :body="ast?.body" :data="ast?.data" :components="{
|
tag: {
|
||||||
a: ProseA,
|
type: [String, Boolean],
|
||||||
h1: ProseH1,
|
default: 'div'
|
||||||
h2: ProseH2,
|
},
|
||||||
h3: ProseH3,
|
content: {
|
||||||
h4: ProseH4,
|
type: [String, Object],
|
||||||
h5: ProseH5,
|
required: true
|
||||||
h6: ProseH6,
|
},
|
||||||
blockquote: ProseBlockquote,
|
excerpt: {
|
||||||
}" />
|
type: Boolean,
|
||||||
</Suspense>
|
default: false
|
||||||
</template>
|
},
|
||||||
|
class: {
|
||||||
|
type: [String, Array, Object],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
unwrap: {
|
||||||
|
type: [Boolean, String],
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const model = defineModel<number>({
|
||||||
|
default: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const parser = useMarkdown();
|
||||||
|
const key = computed(() => hash(props.content))
|
||||||
|
|
||||||
|
const { data, refresh, error } = await useAsyncData(key.value, async () => {
|
||||||
|
const timer = performance.now();
|
||||||
|
if (typeof props.content !== 'string') {
|
||||||
|
model.value = performance.now() - timer;
|
||||||
|
return props.content
|
||||||
|
}
|
||||||
|
const result = await parser(props.content);
|
||||||
|
model.value = performance.now() - timer;
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
|
||||||
|
const body = computed(() => props.excerpt ? data.value?.excerpt : data.value?.body)
|
||||||
|
|
||||||
|
watch(() => props.content, () => {
|
||||||
|
refresh()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -54,8 +54,10 @@ async function debounced()
|
||||||
@mouseleave="(e) => (e.target as HTMLElement).classList.remove('is-selected')"
|
@mouseleave="(e) => (e.target as HTMLElement).classList.remove('is-selected')"
|
||||||
@mousedown.prevent="navigateTo(`/explorer/${result.id}${result.home}`); input = ''">
|
@mousedown.prevent="navigateTo(`/explorer/${result.id}${result.home}`); input = ''">
|
||||||
<div class="suggestion-content">
|
<div class="suggestion-content">
|
||||||
<div class="suggestion-title">
|
<BoldContent class="suggestion-title" :text="result.name" :matched="input" />
|
||||||
<BoldContent :text="result.name" :matched="input" />
|
<div class="suggestion-subtext">
|
||||||
|
<div class="suggestion-text">{{ result.username }}</div>
|
||||||
|
<div class="suggestion-text">{{ result.pages }} pages</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -64,10 +66,10 @@ async function debounced()
|
||||||
@mouseleave="(e) => (e.target as HTMLElement).classList.remove('is-selected')"
|
@mouseleave="(e) => (e.target as HTMLElement).classList.remove('is-selected')"
|
||||||
@mousedown.prevent="navigateTo(`/explorer/${result.project}${result.path}`); input = ''">
|
@mousedown.prevent="navigateTo(`/explorer/${result.project}${result.path}`); input = ''">
|
||||||
<div class="suggestion-content">
|
<div class="suggestion-content">
|
||||||
<div class="suggestion-title">
|
<BoldContent class="suggestion-title" :text="result.title" :matched="input" />
|
||||||
<ProseA :href="result.path" :project="result.project">
|
<div class="suggestion-subtext">
|
||||||
<BoldContent :text="result.title" :matched="input" />
|
<div class="suggestion-text">{{ result.username }}</div>
|
||||||
</ProseA>
|
<div class="suggestion-text">{{ result.comments }} commentaires</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -76,11 +78,7 @@ async function debounced()
|
||||||
@mouseleave="(e) => (e.target as HTMLElement).classList.remove('is-selected')"
|
@mouseleave="(e) => (e.target as HTMLElement).classList.remove('is-selected')"
|
||||||
@mousedown.prevent="navigateTo(`/user/${result.id}`); input = ''">
|
@mousedown.prevent="navigateTo(`/user/${result.id}`); input = ''">
|
||||||
<div class="suggestion-content">
|
<div class="suggestion-content">
|
||||||
<div class="suggestion-title">
|
<BoldContent class="suggestion-title" :text="result.username" :matched="input" />
|
||||||
<div>
|
|
||||||
<BoldContent :text="result.username" :matched="input" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="suggestion-empty"
|
<div class="suggestion-empty"
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
<div v-else class="loading"></div>
|
<div v-else class="loading"></div>
|
||||||
</div>
|
</div>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
<NuxtLink :to="path" :class="class" noPrefetch @mouseenter="(e) => showPreview(e, true)" @mouseleave="hidePreview">
|
<NuxtLink :to="{ path, force: true }" :class="class" noPrefetch @mouseenter="(e) => showPreview(e, true)" @mouseleave="hidePreview">
|
||||||
<slot v-bind="$attrs"></slot>
|
<slot v-bind="$attrs"></slot>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -74,22 +74,30 @@ if (parseURL(props.href).protocol !== undefined)
|
||||||
let id = ref(props.project);
|
let id = ref(props.project);
|
||||||
if(id.value === undefined)
|
if(id.value === undefined)
|
||||||
{
|
{
|
||||||
id = useProject().id;
|
id.value = useProject().id.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: page, status, error, execute } = await useLazyFetch(`/api/project/${id.value}/file`, {
|
const { data: page, status, error, execute } = await useLazyFetch(`/api/project/${id.value}/file`, {
|
||||||
query: {
|
query: {
|
||||||
search: "%" + parseURL(props.href).pathname,
|
search: "%" + parseURL(props.href).pathname,
|
||||||
}
|
},
|
||||||
|
immediate: false,
|
||||||
|
dedupe: 'defer',
|
||||||
});
|
});
|
||||||
|
|
||||||
if(external.value)
|
if(!external.value)
|
||||||
{
|
{
|
||||||
execute();
|
await execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(status.value === 'error')
|
||||||
|
{
|
||||||
|
console.error(error.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (page.value && page.value[0])
|
if (page.value && page.value[0])
|
||||||
{
|
{
|
||||||
path.value = `/explorer/${id.value}${page.value[0].path}`;
|
path.value = `/explorer/${id.value}${page.value[0].path}`;
|
||||||
|
console.log(path.value);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -51,7 +51,7 @@ function hideLeftPanel(_: Event)
|
||||||
<NavigationLink v-if="hasChildren" v-for="l of link.children" :link="l" :project="project" />
|
<NavigationLink v-if="hasChildren" v-for="l of link.children" :link="l" :project="project" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<NuxtLink @click="hideLeftPanel" v-else class="tree-item-self" :to="`/explorer/${project}${link.path}`"
|
<NuxtLink @click="hideLeftPanel" v-else class="tree-item-self" :to="{ path: `/explorer/${project}${link.path}`, force: true }"
|
||||||
:active-class="'mod-active'" :data-type="link.type === 'Canvas' ? 'graph' : undefined">
|
:active-class="'mod-active'" :data-type="link.type === 'Canvas' ? 'graph' : undefined">
|
||||||
<div class="tree-item-inner">{{ link.title }}</div>
|
<div class="tree-item-inner">{{ link.title }}</div>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
|
||||||
|
|
@ -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 }}">
|
||||||
<NuxtLink no-prefetch class="tree-item-inner" :href="{hash: '#' + props.link.id}">{{ props.link.text }}</NuxtLink>
|
<NuxtLink no-prefetch class="tree-item-inner" :to="{ hash: '#' + props.link.id, force: true }">{{ 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" />
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,50 @@
|
||||||
import type { Project } from "~/server/api/project.get";
|
import type { Project } from "~/types/api";
|
||||||
|
|
||||||
export default function useProject()
|
export default function useProject()
|
||||||
{
|
{
|
||||||
const id = useState<number>("projectId", () => 1);
|
const project = useCookie('project');
|
||||||
|
|
||||||
|
const id = useState<number>("projectId", () => parseInt(project.value ?? '0'));
|
||||||
const name = useState<string>("projectName", undefined);
|
const name = useState<string>("projectName", undefined);
|
||||||
const owner = useState<number>("projectOwner", undefined);
|
const owner = useState<number>("projectOwner", undefined);
|
||||||
const home = useState<string | null>("projectHomepage", () => null);
|
const home = useState<string | null>("projectHomepage", () => null);
|
||||||
|
const summary = useState<string | null>("projectSummary", () => null);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id, name, owner, home, get, set
|
id, name, owner, home, summary, get, set
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function get(): Promise<void> {
|
async function get(): Promise<boolean> {
|
||||||
const id = useState<number>("projectId");
|
const id = useState<number>("projectId");
|
||||||
|
|
||||||
if (!id.value)
|
if (!id.value)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await $fetch(`/api/project/${id}`) as Project;
|
const result = await $fetch(`/api/project/${id.value}`) as Project;
|
||||||
|
|
||||||
const name = useState<string>("projectName");
|
const name = useState<string>("projectName");
|
||||||
const owner = useState<number>("projectOwner");
|
const owner = useState<number>("projectOwner");
|
||||||
const home = useState<string | null>("projectHomepage");
|
const home = useState<string | null>("projectHomepage");
|
||||||
|
const summary = useState<string | null>("projectSummary");
|
||||||
|
|
||||||
name.value = result.name;
|
name.value = result.name;
|
||||||
owner.value = result.owner;
|
owner.value = result.owner;
|
||||||
home.value = result.home;
|
home.value = result.home;
|
||||||
} catch(e) {}
|
summary.value = result.summary;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch(e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function set(id: number): void {
|
async function set(id: number): Promise<boolean> {
|
||||||
const _id = useState<number>("projectId");
|
const _id = useState<number>("projectId");
|
||||||
|
|
||||||
_id.value = id;
|
_id.value = id;
|
||||||
|
const project = useCookie('project');
|
||||||
|
project.value = id.toString();
|
||||||
|
|
||||||
|
return await get();
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<template>
|
||||||
|
<div class="column">
|
||||||
|
<textarea v-model="input"></textarea>
|
||||||
|
<pre>{{ timing }}ms</pre>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<Suspense>
|
||||||
|
<template #fallback>
|
||||||
|
<div class="loading"></div>
|
||||||
|
</template>
|
||||||
|
<Markdown v-if="input.length > 0" :content="input" v-model="timing"/>
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const input = ref(""), timing = ref(0);
|
||||||
|
</script>
|
||||||
|
|
@ -6,6 +6,10 @@ const { data: content } = await useFetch(`/api/project/${route.params.projectId}
|
||||||
path: unifySlug(route.params.slug)
|
path: unifySlug(route.params.slug)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { set } = useProject();
|
||||||
|
|
||||||
|
await set(parseInt(route.params.projectId as string));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ onMounted(() => {
|
||||||
<ContentRenderer :value="getContent(descriptions)" />
|
<ContentRenderer :value="getContent(descriptions)" />
|
||||||
<h2>Pages contenant le tag</h2>
|
<h2>Pages contenant le tag</h2>
|
||||||
<div class="card-container">
|
<div class="card-container">
|
||||||
<NuxtLink :key="item._id" :href="'/explorer' + item._path" v-for="item of list" class="tag"> {{ item.title }} </NuxtLink>
|
<NuxtLink :key="item._id" :to="{ path: '/explorer' + item._path, force: true }" v-for="item of list" class="tag"> {{ item.title }} </NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { id: project } = useProject();
|
||||||
|
|
||||||
|
const { data: projects, status, error } = await useFetch('/api/project');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Head>
|
||||||
|
<Title>Liste des projets</Title>
|
||||||
|
</Head>
|
||||||
|
<div class="site-body-center-column">
|
||||||
|
<div class="projects-container">
|
||||||
|
<div v-if="status === 'success'" class="project-list">
|
||||||
|
<div v-for="p of projects" class="project-item">
|
||||||
|
<NuxtLink class="project-title" :to="{ path: `/explorer/${p.id}${p.home}`, force: true }">{{ p.name }}</NuxtLink>
|
||||||
|
<div class="project-user">Par {{ p.username }}</div>
|
||||||
|
<div class="project-pages">{{ p.pages }} pages</div>
|
||||||
|
<div class="project-summary">{{ p.summary ?? "Sans contenu" }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="status === 'pending'" class="loading"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -59,7 +59,7 @@ async function submit()
|
||||||
placeholder="" title="Mot de passe"
|
placeholder="" title="Mot de passe"
|
||||||
:error="passwordError" />
|
:error="passwordError" />
|
||||||
<button>Se connecter</button>
|
<button>Se connecter</button>
|
||||||
<NuxtLink to="/user/register">Pas de compte ?</NuxtLink>
|
<NuxtLink :to="{ path: `/user/register`, force: true }">Pas de compte ?</NuxtLink>
|
||||||
</form>
|
</form>
|
||||||
<div v-else-if="status === AuthStatus.loading" class="input-form"><div class="loading"></div></div>
|
<div v-else-if="status === AuthStatus.loading" class="input-form"><div class="loading"></div></div>
|
||||||
<div v-else class="not-found-container">
|
<div v-else class="not-found-container">
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ async function submit()
|
||||||
title="Confirmer le mot de passe"
|
title="Confirmer le mot de passe"
|
||||||
:error="confirmPassword === '' || confirmPassword === state.password ? '' : 'Les mots de passe saisies ne sont pas identique'" />
|
:error="confirmPassword === '' || confirmPassword === state.password ? '' : 'Les mots de passe saisies ne sont pas identique'" />
|
||||||
<button>S'inscrire</button>
|
<button>S'inscrire</button>
|
||||||
<NuxtLink to="/user/login">Se connecter</NuxtLink>
|
<NuxtLink :to="{ path: `/user/login`, force: true }">Se connecter</NuxtLink>
|
||||||
</form>
|
</form>
|
||||||
<div v-else-if="status === AuthStatus.loading" class="input-form"><div class="loading"></div></div>
|
<div v-else-if="status === AuthStatus.loading" class="input-form"><div class="loading"></div></div>
|
||||||
<div v-else class="not-found-container">
|
<div v-else class="not-found-container">
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -3,8 +3,8 @@ import useDatabase from '~/composables/useDatabase';
|
||||||
export default defineEventHandler(async (e) => {
|
export default defineEventHandler(async (e) => {
|
||||||
const project = getRouterParam(e, "projectId");
|
const project = getRouterParam(e, "projectId");
|
||||||
|
|
||||||
const where = ["project = $project"];
|
const where = ["id = $id"];
|
||||||
const criteria: Record<string, any> = { $project: project };
|
const criteria: Record<string, any> = { $id: project };
|
||||||
|
|
||||||
if (!!project) {
|
if (!!project) {
|
||||||
const db = useDatabase();
|
const db = useDatabase();
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ export default defineEventHandler(async (e) => {
|
||||||
if (query.search) {
|
if (query.search) {
|
||||||
const db = useDatabase();
|
const db = useDatabase();
|
||||||
|
|
||||||
const projects = db.query(`SELECT * FROM explorer_projects WHERE name LIKE ?1`).all(query.search) as Project[];
|
const projects = db.query(`SELECT p.*, u.username, COUNT(f.path) as pages FROM explorer_projects p LEFT JOIN users u ON p.owner = u.id LEFT JOIN explorer_files f ON f.project = p.id WHERE name LIKE ?1 AND f.type != "Folder" GROUP BY p.id`).all(query.search) as ProjectSearch[];
|
||||||
const files = db.query(`SELECT * FROM explorer_files WHERE title LIKE ?1 `).all(query.search) as File[];
|
const files = db.query(`SELECT f.*, u.username, count(c.path) as comments FROM explorer_files f LEFT JOIN users u ON f.owner = u.id LEFT JOIN explorer_comments c ON c.project = f.project AND c.path = f.path WHERE title LIKE ?1 AND private = 0 AND type != "Folder" GROUP BY f.project, f.path`).all(query.search) as FileSearch[];
|
||||||
const users = db.query(`SELECT id, username FROM users WHERE username LIKE ?1 `).all(query.search) as User[];
|
const users = db.query(`SELECT id, username FROM users WHERE username LIKE ?1`).all(query.search) as UserSearch[];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
projects,
|
projects,
|
||||||
|
|
|
||||||
26
types/api.ts
26
types/api.ts
|
|
@ -3,6 +3,7 @@ export interface Project {
|
||||||
name: string;
|
name: string;
|
||||||
owner: number;
|
owner: number;
|
||||||
home: string;
|
home: string;
|
||||||
|
summary: string;
|
||||||
}
|
}
|
||||||
export interface Navigation {
|
export interface Navigation {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -32,8 +33,25 @@ export interface User {
|
||||||
id: number;
|
id: number;
|
||||||
username: string;
|
username: string;
|
||||||
}
|
}
|
||||||
export interface Search {
|
export interface ProjectSearch extends Project
|
||||||
projects: Project[];
|
{
|
||||||
files: File[];
|
pages: number;
|
||||||
users: User[];
|
username: string;
|
||||||
|
}
|
||||||
|
export interface FileSearch extends File
|
||||||
|
{
|
||||||
|
comments: number;
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
export interface CommentSearch extends Comment
|
||||||
|
{
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
export interface UserSearch extends User
|
||||||
|
{
|
||||||
|
}
|
||||||
|
export interface Search {
|
||||||
|
projects: ProjectSearch[];
|
||||||
|
files: FileSearch[];
|
||||||
|
users: UserSearch[];
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue