mirror of
https://github.com/docmost/docmost.git
synced 2025-11-17 23:41:12 +10:00
WIP
This commit is contained in:
@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 />}>
|
||||||
|
|||||||
@ -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",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
35
apps/client/src/pages/share/share-redirect.tsx
Normal file
35
apps/client/src/pages/share/share-redirect.tsx
Normal 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;
|
||||||
|
}
|
||||||
@ -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),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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');
|
||||||
|
|||||||
@ -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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user