This commit is contained in:
Philipinho
2025-04-22 15:10:05 +01:00
parent a66fb875ac
commit f74688663d
9 changed files with 116 additions and 18 deletions

View File

@ -380,5 +380,8 @@
"Delete public share link": "Delete public share link", "Delete public share link": "Delete public share link",
"Delete share": "Delete share", "Delete share": "Delete share",
"Are you sure you want to delete this shared link?": "Are you sure you want to delete this shared link?", "Are you sure you want to delete this shared link?": "Are you sure you want to delete this shared link?",
"Publicly shared pages from spaces you are a member of will appear here": "Publicly shared pages from spaces you are a member of will appear here" "Publicly shared pages from spaces you are a member of will appear here": "Publicly shared pages from spaces you are a member of will appear here",
"Share deleted successfully": "Share deleted successfully",
"Share not found": "Share not found",
"Failed to share page": "Failed to share page"
} }

View File

@ -29,6 +29,7 @@ import { useRedirectToCloudSelect } from "@/ee/hooks/use-redirect-to-cloud-selec
import SharedPage from "@/pages/share/shared-page.tsx"; import SharedPage from "@/pages/share/shared-page.tsx";
import Shares from "@/pages/settings/shares/shares.tsx"; import Shares from "@/pages/settings/shares/shares.tsx";
import ShareLayout from "@/features/share/components/share-layout.tsx"; import ShareLayout from "@/features/share/components/share-layout.tsx";
import ShareRedirect from '@/pages/share/share-redirect.tsx';
export default function App() { export default function App() {
const { t } = useTranslation(); const { t } = useTranslation();
@ -58,7 +59,8 @@ export default function App() {
<Route path={"/share/:shareId/:pageSlug"} element={<SharedPage />} /> <Route path={"/share/:shareId/:pageSlug"} element={<SharedPage />} />
<Route path={"/share/p/:pageSlug"} element={<SharedPage />} /> <Route path={"/share/p/:pageSlug"} element={<SharedPage />} />
</Route> </Route>
<Route path={"/share/:shareId"} element={<ShareRedirect />} />
<Route path={"/p/:pageSlug"} element={<PageRedirect />} /> <Route path={"/p/:pageSlug"} element={<PageRedirect />} />
<Route element={<Layout />}> <Route element={<Layout />}>

View File

@ -9,6 +9,7 @@ import { notifications } from "@mantine/notifications";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
ICreateShare, ICreateShare,
IShare,
ISharedItem, ISharedItem,
ISharedPage, ISharedPage,
ISharedPageTree, ISharedPageTree,
@ -22,6 +23,7 @@ import {
getSharedPageTree, getSharedPageTree,
getShareForPage, getShareForPage,
getShareInfo, getShareInfo,
getSharePageInfo,
getShares, getShares,
updateShare, updateShare,
} from "@/features/share/services/share-service.ts"; } from "@/features/share/services/share-service.ts";
@ -39,12 +41,24 @@ export function useGetSharesQuery(
}); });
} }
export function useShareQuery( export function useGetShareByIdQuery(
shareId: string,
): UseQueryResult<IShare, Error> {
const query = useQuery({
queryKey: ["share-by-id", shareId],
queryFn: () => getShareInfo(shareId),
enabled: !!shareId,
});
return query;
}
export function useSharePageQuery(
shareInput: Partial<IShareInfoInput>, shareInput: Partial<IShareInfoInput>,
): UseQueryResult<ISharedPage, Error> { ): UseQueryResult<ISharedPage, Error> {
const query = useQuery({ const query = useQuery({
queryKey: ["shares", shareInput], queryKey: ["shares", shareInput],
queryFn: () => getShareInfo(shareInput), queryFn: () => getSharePageInfo(shareInput),
enabled: !!shareInput.pageId, enabled: !!shareInput.pageId,
}); });
@ -84,7 +98,9 @@ export function useCreateShareMutation() {
} }
export function useUpdateShareMutation() { export function useUpdateShareMutation() {
const { t } = useTranslation();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation<any, Error, IUpdateShare>({ return useMutation<any, Error, IUpdateShare>({
mutationFn: (data) => updateShare(data), mutationFn: (data) => updateShare(data),
onSuccess: (data) => { onSuccess: (data) => {
@ -99,10 +115,16 @@ export function useUpdateShareMutation() {
predicate: (item) => predicate: (item) =>
["share-for-page"].includes(item.queryKey[0] as string), ["share-for-page"].includes(item.queryKey[0] as string),
}); });
notifications.show({
message: t("Share not found"),
color: "red",
});
return;
} }
notifications.show({ notifications.show({
message: error?.["response"]?.data?.message || "Share share not found", message: error?.["response"]?.data?.message || "Share not found",
color: "red", color: "red",
}); });
}, },

View File

@ -3,12 +3,14 @@ import { IPage } from "@/features/page/types/page.types";
import { import {
ICreateShare, ICreateShare,
ISharedItem, ISharedPage, IShare,
ISharedItem,
ISharedPage,
ISharedPageTree, ISharedPageTree,
IShareForPage, IShareForPage,
IShareInfoInput, IShareInfoInput,
IUpdateShare, IUpdateShare,
} from '@/features/share/types/share.types.ts'; } from "@/features/share/types/share.types.ts";
import { IPagination, QueryParams } from "@/lib/types.ts"; import { IPagination, QueryParams } from "@/lib/types.ts";
export async function getShares( export async function getShares(
@ -23,6 +25,11 @@ export async function createShare(data: ICreateShare): Promise<any> {
return req.data; return req.data;
} }
export async function getShareInfo(shareId: string): Promise<IShare> {
const req = await api.post<IShare>("/shares/info", { shareId });
return req.data;
}
export async function updateShare(data: IUpdateShare): Promise<any> { export async function updateShare(data: IUpdateShare): Promise<any> {
const req = await api.post<any>("/shares/update", data); const req = await api.post<any>("/shares/update", data);
return req.data; return req.data;
@ -33,7 +40,7 @@ export async function getShareForPage(pageId: string): Promise<IShareForPage> {
return req.data; return req.data;
} }
export async function getShareInfo( export async function getSharePageInfo(
shareInput: Partial<IShareInfoInput>, shareInput: Partial<IShareInfoInput>,
): Promise<ISharedPage> { ): Promise<ISharedPage> {
const req = await api.post<ISharedPage>("/shares/page-info", shareInput); const req = await api.post<ISharedPage>("/shares/page-info", shareInput);

View File

@ -12,6 +12,7 @@ export interface IShare {
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
deletedAt: string | null; deletedAt: string | null;
sharedPage?: ISharePage;
} }
export interface ISharedItem extends IShare { export interface ISharedItem extends IShare {
@ -44,12 +45,14 @@ export interface ISharedPage extends IShare {
export interface IShareForPage extends IShare { export interface IShareForPage extends IShare {
level: number; level: number;
sharedPage: { sharedPage: ISharePage;
id: string; }
slugId: string;
title: string; interface ISharePage {
icon: string; id: string;
}; slugId: string;
title: string;
icon: string;
} }
export interface ICreateShare { export interface ICreateShare {

View File

@ -0,0 +1,35 @@
import { useNavigate, useParams } from "react-router-dom";
import { useEffect } from "react";
import { buildSharedPageUrl } from "@/features/page/page.utils.ts";
import { Error404 } from "@/components/ui/error-404.tsx";
import { useGetShareByIdQuery } from "@/features/share/queries/share-query.ts";
export default function ShareRedirect() {
const { shareId } = useParams();
const navigate = useNavigate();
const { data: share, isLoading, isError } = useGetShareByIdQuery(shareId);
useEffect(() => {
if (share) {
navigate(
buildSharedPageUrl({
shareId: share.key,
pageSlugId: share?.sharedPage.slugId,
pageTitle: share?.sharedPage.title,
}),
{ replace: true },
);
}
}, [isLoading, share]);
if (isError) {
return <Error404 />;
}
if (isLoading) {
return <></>;
}
return null;
}

View File

@ -1,7 +1,7 @@
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useShareQuery } from "@/features/share/queries/share-query.ts"; import { useSharePageQuery } from "@/features/share/queries/share-query.ts";
import { Container } from "@mantine/core"; import { Container } from "@mantine/core";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import ReadonlyPageEditor from "@/features/editor/readonly-page-editor.tsx"; import ReadonlyPageEditor from "@/features/editor/readonly-page-editor.tsx";
@ -14,7 +14,7 @@ export default function SingleSharedPage() {
const { shareId } = useParams(); const { shareId } = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const { data, isLoading, isError, error } = useShareQuery({ const { data, isLoading, isError, error } = useSharePageQuery({
pageId: extractPageSlugId(pageSlug), pageId: extractPageSlugId(pageSlug),
}); });

View File

@ -67,7 +67,9 @@ export class ShareController {
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@Post('/info') @Post('/info')
async getShare(@Body() dto: ShareIdDto, @AuthUser() user: User) { async getShare(@Body() dto: ShareIdDto, @AuthUser() user: User) {
const share = await this.shareRepo.findById(dto.shareId); const share = await this.shareRepo.findById(dto.shareId, {
includeSharedPage: true,
});
if (!share) { if (!share) {
throw new NotFoundException('Share not found'); throw new NotFoundException('Share not found');

View File

@ -39,6 +39,7 @@ export class ShareRepo {
async findById( async findById(
shareId: string, shareId: string,
opts?: { opts?: {
includeSharedPage?: boolean;
includeCreator?: boolean; includeCreator?: boolean;
withLock?: boolean; withLock?: boolean;
trx?: KyselyTransaction; trx?: KyselyTransaction;
@ -48,6 +49,10 @@ export class ShareRepo {
let query = db.selectFrom('shares').select(this.baseFields); let query = db.selectFrom('shares').select(this.baseFields);
if (opts?.includeSharedPage) {
query = query.select((eb) => this.withSharedPage(eb));
}
if (opts?.includeCreator) { if (opts?.includeCreator) {
query = query.select((eb) => this.withCreator(eb)); query = query.select((eb) => this.withCreator(eb));
} }
@ -98,7 +103,11 @@ export class ShareRepo {
return dbOrTx(this.db, trx) return dbOrTx(this.db, trx)
.updateTable('shares') .updateTable('shares')
.set({ ...updatableShare, updatedAt: new Date() }) .set({ ...updatableShare, updatedAt: new Date() })
.where(isValidUUID(shareId) ? 'id' : sql`LOWER(key)`, '=', shareId.toLowerCase()) .where(
isValidUUID(shareId) ? 'id' : sql`LOWER(key)`,
'=',
shareId.toLowerCase(),
)
.returning(this.baseFields) .returning(this.baseFields)
.executeTakeFirst(); .executeTakeFirst();
} }
@ -215,4 +224,19 @@ export class ShareRepo {
.whereRef('users.id', '=', 'shares.creatorId'), .whereRef('users.id', '=', 'shares.creatorId'),
).as('creator'); ).as('creator');
} }
withSharedPage(eb: ExpressionBuilder<DB, 'shares'>) {
return jsonObjectFrom(
eb
.selectFrom('pages')
.select([
'pages.id',
'pages.slugId',
'pages.title',
'pages.icon',
'pages.parentPageId',
])
.whereRef('pages.id', '=', 'shares.pageId'),
).as('sharedPage');
}
} }