project pages

This commit is contained in:
MiniDigger | Martin 2024-08-09 11:57:16 +02:00
parent 3cb54729de
commit 089a3e396e
7 changed files with 64 additions and 48 deletions

View File

@ -3,17 +3,23 @@ import type { ExtendedProjectPage, HangarProject, HangarProjectPage } from "~/ty
const props = defineProps<{
project?: HangarProject;
page?: ExtendedProjectPage;
mainPage: boolean;
}>();
const route = useRoute("user-project-pages-all");
const route = useRoute("user-project-pages-page");
const router = useRouter();
const updateProjectPages = inject<(pages: HangarProjectPage[]) => void>("updateProjectPages");
const { editingPage, changeEditingPage, page, savePage, deletePage } = await useProjectPage(route, router, props.project, props.mainPage);
const { editingPage, changeEditingPage, savePage, deletePage } = useProjectPage(
route,
router,
props.project,
props.mainPage ? props.project?.mainPage : props.page
);
if (!props.mainPage) {
useHead(useSeo(page.value?.name + " | " + props.project?.name, props.project?.description, route, props.project?.avatarUrl));
useHead(useSeo(props.page?.name + " | " + props.project?.name, props.project?.description, route, props.project?.avatarUrl));
}
async function deletePageAndUpdateProject() {
@ -29,22 +35,10 @@ async function deletePageAndUpdateProject() {
}
defineSlots<{
default: (props: {
page?: ExtendedProjectPage | null;
editingPage: boolean;
changeEditingPage: (editing: boolean) => void;
savePage: (content: string) => void;
deletePage: () => void;
}) => any;
default: (props: { editingPage: boolean; changeEditingPage: (editing: boolean) => void; savePage: (content: string) => void; deletePage: () => void }) => any;
}>();
</script>
<template>
<slot
:page="page"
:save-page="savePage"
:delete-page="deletePageAndUpdateProject"
:editing-page="editingPage"
:change-editing-page="changeEditingPage"
></slot>
<slot :save-page="savePage" :delete-page="deletePageAndUpdateProject" :editing-page="editingPage" :change-editing-page="changeEditingPage"></slot>
</template>

View File

@ -1,7 +1,7 @@
import type { RouteLocationNormalized } from "vue-router";
type dataLoaders = "user" | "project" | "version" | "organization";
type routeParams = "user" | "project" | "version" | "organization";
type dataLoaders = "user" | "project" | "version" | "organization" | "page";
type routeParams = "user" | "project" | "version" | "organization" | "page";
// TODO check every handling of the reject stuff (for both composables)

View File

@ -1,7 +1,7 @@
import type { HangarProject } from "~/types/backend";
import type { RouteLocationTyped, RouteMapGeneric } from "vue-router";
export function useOpenProjectPages(route: RouteLocationTyped<RouteMapGeneric, "user-project-pages-all">, project?: HangarProject) {
export function useOpenProjectPages(route: RouteLocationTyped<RouteMapGeneric, "user-project-pages-page">, project?: HangarProject) {
const open = ref<string[]>([]);
watch(

View File

@ -1,14 +1,8 @@
import type { HangarProject } from "~/types/backend";
import type { ExtendedProjectPage, HangarProject } from "~/types/backend";
import type { Router } from "vue-router";
import type { RouteLocationNormalized } from "vue-router/auto";
export async function useProjectPage(route: RouteLocationNormalized<"user-project-pages-all">, router: Router, project?: HangarProject, mainPage?: boolean) {
const page = mainPage ? computed(() => project?.mainPage) : usePage(() => ({ project: route.params.project, path: route.params.all?.toString() })).page;
// TODO fix this
// if (!page?.value) {
// throw useErrorRedirect(404, "Not found");
// }
export function useProjectPage(route: RouteLocationNormalized<"user-project-pages-page">, router: Router, project?: HangarProject, page?: ExtendedProjectPage) {
const editingPage = ref<boolean>(false);
// Helper setter function, v-model cannot directly edit from inside a slot.
@ -17,21 +11,21 @@ export async function useProjectPage(route: RouteLocationNormalized<"user-projec
}
async function savePage(content: string) {
if (!page?.value) return;
await useInternalApi(`pages/save/${project?.id}/${page.value?.id}`, "post", {
if (!page) return;
await useInternalApi(`pages/save/${project?.id}/${page?.id}`, "post", {
content,
}).catch((e) => handleRequestError(e, "page.new.error.save"));
if (page.value && "contents" in page.value) {
page.value.contents = content;
if (page && "contents" in page) {
page.contents = content;
}
editingPage.value = false;
}
async function deletePage() {
if (!page?.value) return;
await useInternalApi(`pages/delete/${project?.id}/${page.value?.id}`, "post").catch((e) => handleRequestError(e, "page.new.error.save"));
if (!page) return;
await useInternalApi(`pages/delete/${project?.id}/${page?.id}`, "post").catch((e) => handleRequestError(e, "page.new.error.save"));
await router.replace(`/${route.params.user}/${route.params.project}`);
}
return { editingPage, changeEditingPage, page, savePage, deletePage };
return { editingPage, changeEditingPage, savePage, deletePage };
}

View File

@ -1,4 +1,4 @@
import type { HangarOrganization, HangarProject, HangarVersion, User } from "~/types/backend";
import type { ExtendedProjectPage, HangarOrganization, HangarProject, HangarVersion, User } from "~/types/backend";
import { useDataLoader } from "~/composables/useDataLoader";
import { isAxiosError } from "axios";
@ -44,6 +44,20 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
promises
);
const { loader: pageLoader, data: page } = useDataLoader<ExtendedProjectPage>("page");
const pageName = pageLoader(
"page",
to,
from,
async (pagePath) => {
if ("project" in to.params) {
return useInternalApi<ExtendedProjectPage>(`pages/page/${to.params.project}/` + pagePath.toString().replaceAll(",", "/"));
}
throw createError({ statusCode: 500, statusMessage: "No project param?!" });
},
promises
) as string[] | undefined;
if (import.meta.server && promises?.length) {
try {
await Promise.all(promises);
@ -60,17 +74,23 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
let newPath = to.fullPath;
if (userName) {
if (user.value && user.value.name !== userName) {
newPath = newPath.replace(userName, user.value?.name);
newPath = newPath.replace(userName, user.value.name);
}
}
if (projectName) {
if (project.value && project.value.name !== projectName) {
newPath = newPath.replace(projectName, project.value?.name);
newPath = newPath.replace(projectName, project.value.name);
}
}
if (versionName) {
if (version.value && version.value.name !== versionName) {
newPath = newPath.replace(versionName, version.value?.name);
newPath = newPath.replace(versionName, version.value.name);
}
}
if (pageName) {
const pageSlug = pageName.join("/");
if (page.value && page.value.slug !== pageSlug) {
newPath = newPath.replace(pageSlug, page.value.slug);
}
}
if (newPath != to.fullPath) {

View File

@ -9,7 +9,7 @@ const props = defineProps<{
const config = useConfig();
const i18n = useI18n();
const route = useRoute("user-project-pages-all");
const route = useRoute("user-project-pages-page");
const openProjectPages = useOpenProjectPages(route, props.project);
const sponsors = ref(props.project?.settings?.sponsors);
@ -65,12 +65,12 @@ useHead(
<template>
<div class="flex flex-wrap md:flex-nowrap gap-4">
<section class="basis-full md:basis-11/15 flex-grow overflow-auto">
<ProjectPageMarkdown v-slot="{ page, editingPage, changeEditingPage, savePage }" :project="props.project" main-page>
<Card v-if="page?.contents" class="pb-0 overflow-clip overflow-hidden">
<ProjectPageMarkdown v-slot="{ editingPage, changeEditingPage, savePage }" :project="props.project" :page="props.project?.mainPage" main-page>
<Card v-if="project?.mainPage?.contents" class="pb-0 overflow-clip overflow-hidden">
<ClientOnly v-if="hasPerms(NamedPermission.EditPage)">
<MarkdownEditor
:editing="editingPage"
:raw="page.contents"
:raw="project?.mainPage.contents"
:deletable="false"
:saveable="true"
:cancellable="true"
@ -80,11 +80,11 @@ useHead(
@save="savePage"
/>
<template #fallback>
<Markdown :raw="page.contents" />
<Markdown :raw="project?.mainPage.contents" />
</template>
</ClientOnly>
<!--We have to blow up v-model:editing into :editing and @update:editing as we are inside a scope--->
<Markdown v-else :raw="page.contents" />
<Markdown v-else :raw="project?.mainPage.contents" />
</Card>
</ProjectPageMarkdown>
<Card v-if="sponsors || hasPerms(NamedPermission.EditSubjectSettings)" class="mt-2 pb-0 overflow-clip overflow-visible">

View File

@ -1,12 +1,19 @@
<script lang="ts" setup>
import { type HangarProject, NamedPermission, type User } from "~/types/backend";
import { type ExtendedProjectPage, type HangarProject, NamedPermission, type User } from "~/types/backend";
import { useDataLoader } from "~/composables/useDataLoader";
const props = defineProps<{
user?: User;
project?: HangarProject;
}>();
const route = useRoute("user-project-pages-all");
const route = useRoute("user-project-pages-page");
const { data: page } = useDataLoader<ExtendedProjectPage>("page");
definePageMeta({
dataLoader_page: true,
});
const open = useOpenProjectPages(route, props.project);
// useSeo is in ProjectPageMarkdown
@ -17,8 +24,9 @@ const open = useOpenProjectPages(route, props.project);
<section class="basis-full md:basis-9/12 flex-grow overflow-auto">
<ProjectPageMarkdown
:key="route.fullPath"
v-slot="{ page, editingPage, changeEditingPage, savePage, deletePage }"
v-slot="{ editingPage, changeEditingPage, savePage, deletePage }"
:project="props.project"
:page="page"
:main-page="false"
>
<Card v-if="page" class="pb-0 overflow-clip overflow-hidden">