mirror of
https://github.com/docmost/docmost.git
synced 2025-11-17 06:51:09 +10:00
WIP
This commit is contained in:
@ -26,7 +26,7 @@ import { useTranslation } from "react-i18next";
|
||||
import Security from "@/ee/security/pages/security.tsx";
|
||||
import License from "@/ee/licence/pages/license.tsx";
|
||||
import { useRedirectToCloudSelect } from "@/ee/hooks/use-redirect-to-cloud-select.tsx";
|
||||
import SharedPage from '@/pages/share/shared-page.tsx';
|
||||
import SharedPage from "@/pages/share/shared-page.tsx";
|
||||
|
||||
export default function App() {
|
||||
const { t } = useTranslation();
|
||||
@ -53,6 +53,7 @@ export default function App() {
|
||||
)}
|
||||
|
||||
<Route path={"/share/:shareId/:pageSlug"} element={<SharedPage />} />
|
||||
<Route path={"/share/p/:pageSlug"} element={<SharedPage />} />
|
||||
|
||||
<Route path={"/p/:pageSlug"} element={<PageRedirect />} />
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { NodeViewProps, NodeViewWrapper } from "@tiptap/react";
|
||||
import { ActionIcon, Anchor, Text } from "@mantine/core";
|
||||
import { IconFileDescription } from "@tabler/icons-react";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import { Link, useLocation, useParams } from "react-router-dom";
|
||||
import { usePageQuery } from "@/features/page/queries/page-query.ts";
|
||||
import {
|
||||
buildPageUrl,
|
||||
@ -20,6 +20,9 @@ export default function MentionView(props: NodeViewProps) {
|
||||
isError,
|
||||
} = usePageQuery({ pageId: entityType === "page" ? slugId : null });
|
||||
|
||||
const location = useLocation();
|
||||
const isShareRoute = location.pathname.startsWith("/share");
|
||||
|
||||
const shareSlugUrl = buildSharedPageUrl({
|
||||
shareId,
|
||||
pageSlugId: slugId,
|
||||
@ -38,7 +41,9 @@ export default function MentionView(props: NodeViewProps) {
|
||||
<Anchor
|
||||
component={Link}
|
||||
fw={500}
|
||||
to={shareId ? shareSlugUrl : buildPageUrl(spaceSlug, slugId, label)}
|
||||
to={
|
||||
isShareRoute ? shareSlugUrl : buildPageUrl(spaceSlug, slugId, label)
|
||||
}
|
||||
underline="never"
|
||||
className={classes.pageMentionLink}
|
||||
>
|
||||
|
||||
@ -35,6 +35,7 @@ import {
|
||||
import { formattedDate, timeAgo } from "@/lib/time.ts";
|
||||
import MovePageModal from "@/features/page/components/move-page-modal.tsx";
|
||||
import { useTimeAgo } from "@/hooks/use-time-ago.tsx";
|
||||
import ShareModal from '@/features/share/components/share-modal.tsx';
|
||||
|
||||
interface PageHeaderMenuProps {
|
||||
readOnly?: boolean;
|
||||
@ -58,6 +59,8 @@ export default function PageHeaderMenu({ readOnly }: PageHeaderMenuProps) {
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<ShareModal/>
|
||||
|
||||
<Tooltip label={t("Comments")} openDelay={250} withArrow>
|
||||
<ActionIcon
|
||||
variant="default"
|
||||
|
||||
@ -31,5 +31,9 @@ export const buildSharedPageUrl = (opts: {
|
||||
pageTitle?: string;
|
||||
}): string => {
|
||||
const { shareId, pageSlugId, pageTitle } = opts;
|
||||
if (!shareId) {
|
||||
return `/share/p/${buildPageSlug(pageSlugId, pageTitle)}`;
|
||||
}
|
||||
|
||||
return `/share/${shareId}/${buildPageSlug(pageSlugId, pageTitle)}`;
|
||||
};
|
||||
|
||||
88
apps/client/src/features/share/components/share-modal.tsx
Normal file
88
apps/client/src/features/share/components/share-modal.tsx
Normal file
@ -0,0 +1,88 @@
|
||||
import {
|
||||
Button,
|
||||
Group,
|
||||
MantineSize,
|
||||
Popover,
|
||||
Switch,
|
||||
Text,
|
||||
TextInput,
|
||||
} from "@mantine/core";
|
||||
import { IconWorld } from "@tabler/icons-react";
|
||||
import React, { useState } from "react";
|
||||
import { useShareStatusQuery } from "@/features/share/queries/share-query.ts";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { extractPageSlugId } from "@/lib";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CopyTextButton from "@/components/common/copy.tsx";
|
||||
|
||||
export default function ShareModal() {
|
||||
const { t } = useTranslation();
|
||||
const { pageSlug } = useParams();
|
||||
const { data } = useShareStatusQuery(extractPageSlugId(pageSlug));
|
||||
|
||||
const publicLink =
|
||||
window.location.protocol +'//' + window.location.host +
|
||||
"/share/" +
|
||||
data?.["share"]?.["key"] +
|
||||
"/" +
|
||||
pageSlug;
|
||||
|
||||
return (
|
||||
<Popover width={350} position="bottom" withArrow shadow="md">
|
||||
<Popover.Target>
|
||||
<Button
|
||||
variant="default"
|
||||
style={{ border: "none" }}
|
||||
leftSection={<IconWorld size={20} stroke={1.5} />}
|
||||
>
|
||||
Share
|
||||
</Button>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown>
|
||||
<Group justify="space-between" wrap="nowrap" gap="xl">
|
||||
<div>
|
||||
<Text size="md">{t("Make page public")}</Text>
|
||||
</div>
|
||||
<ToggleShare isChecked={true}></ToggleShare>
|
||||
</Group>
|
||||
|
||||
<Group my="sm" grow>
|
||||
<TextInput
|
||||
variant="filled"
|
||||
value={publicLink}
|
||||
pointer
|
||||
readOnly
|
||||
rightSection={<CopyTextButton text={publicLink} />}
|
||||
/>
|
||||
</Group>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
interface PageWidthToggleProps {
|
||||
isChecked: boolean;
|
||||
size?: MantineSize;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export function ToggleShare({ isChecked, size, label }: PageWidthToggleProps) {
|
||||
const { t } = useTranslation();
|
||||
const [checked, setChecked] = useState(isChecked);
|
||||
|
||||
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = event.currentTarget.checked;
|
||||
setChecked(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Switch
|
||||
size={size}
|
||||
label={label}
|
||||
labelPosition="left"
|
||||
defaultChecked={checked}
|
||||
onChange={handleChange}
|
||||
aria-label={t("Toggle share")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -1,8 +1,4 @@
|
||||
import {
|
||||
useMutation,
|
||||
useQuery,
|
||||
UseQueryResult,
|
||||
} from "@tanstack/react-query";
|
||||
import { useMutation, useQuery, UseQueryResult } from "@tanstack/react-query";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { validate as isValidUuid } from "uuid";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@ -14,6 +10,7 @@ import {
|
||||
createShare,
|
||||
deleteShare,
|
||||
getShareInfo,
|
||||
getShareStatus,
|
||||
updateShare,
|
||||
} from "@/features/share/services/share-service.ts";
|
||||
import { IPage } from "@/features/page/types/page.types.ts";
|
||||
@ -24,7 +21,20 @@ export function useShareQuery(
|
||||
const query = useQuery({
|
||||
queryKey: ["shares", shareInput],
|
||||
queryFn: () => getShareInfo(shareInput),
|
||||
enabled: !!shareInput.shareId,
|
||||
enabled: !!shareInput.pageId,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
export function useShareStatusQuery(
|
||||
pageId: string,
|
||||
): UseQueryResult<IPage, Error> {
|
||||
const query = useQuery({
|
||||
queryKey: ["share-status", pageId],
|
||||
queryFn: () => getShareStatus(pageId),
|
||||
enabled: !!pageId,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
|
||||
|
||||
@ -6,11 +6,21 @@ import {
|
||||
IShareInfoInput,
|
||||
} from "@/features/share/types/share.types.ts";
|
||||
|
||||
export async function getShares(data: ICreateShare): Promise<any> {
|
||||
const req = await api.post<any>("/shares", data);
|
||||
return req.data;
|
||||
}
|
||||
|
||||
export async function createShare(data: ICreateShare): Promise<any> {
|
||||
const req = await api.post<any>("/shares/create", data);
|
||||
return req.data;
|
||||
}
|
||||
|
||||
export async function getShareStatus(pageId: string): Promise<any> {
|
||||
const req = await api.post<IPage>("/shares/status", { pageId });
|
||||
return req.data;
|
||||
}
|
||||
|
||||
export async function getShareInfo(
|
||||
shareInput: Partial<IShareInfoInput>,
|
||||
): Promise<IPage> {
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
export interface ICreateShare {
|
||||
slugId: string; // share slugId
|
||||
pageId: string;
|
||||
includeSubPages?: boolean;
|
||||
}
|
||||
|
||||
export interface IShareInfoInput {
|
||||
shareId: string;
|
||||
pageId: string;
|
||||
}
|
||||
}
|
||||
@ -2,14 +2,14 @@ import { useParams } from "react-router-dom";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useShareQuery } from "@/features/share/queries/share-query.ts";
|
||||
import { Container } from "@mantine/core";
|
||||
import { Affix, Button, Container } from "@mantine/core";
|
||||
import React from "react";
|
||||
import ReadonlyPageEditor from "@/features/editor/readonly-page-editor.tsx";
|
||||
import { extractPageSlugId } from "@/lib";
|
||||
import { Error404 } from "@/components/ui/error-404.tsx";
|
||||
|
||||
export default function SharedPage() {
|
||||
export default function SingleSharedPage() {
|
||||
const { t } = useTranslation();
|
||||
const { shareId } = useParams();
|
||||
const { pageSlug } = useParams();
|
||||
|
||||
const {
|
||||
@ -17,7 +17,7 @@ export default function SharedPage() {
|
||||
isLoading,
|
||||
isError,
|
||||
error,
|
||||
} = useShareQuery({ shareId: shareId, pageId: extractPageSlugId(pageSlug) });
|
||||
} = useShareQuery({ pageId: extractPageSlugId(pageSlug) });
|
||||
|
||||
if (isLoading) {
|
||||
return <></>;
|
||||
@ -25,7 +25,7 @@ export default function SharedPage() {
|
||||
|
||||
if (isError || !page) {
|
||||
if ([401, 403, 404].includes(error?.["status"])) {
|
||||
return <div>{t("Page not found")}</div>;
|
||||
return <Error404 />;
|
||||
}
|
||||
return <div>{t("Error fetching page data.")}</div>;
|
||||
}
|
||||
@ -43,6 +43,10 @@ export default function SharedPage() {
|
||||
content={page.content}
|
||||
/>
|
||||
</Container>
|
||||
|
||||
<Affix position={{ bottom: 20, right: 20 }}>
|
||||
<Button variant="default">Powered by Docmost</Button>
|
||||
</Affix>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user