mirror of
https://github.com/docmost/docmost.git
synced 2025-11-19 19:11:09 +10:00
feat: public page sharing (#1012)
* Share - WIP * - public attachment links - WIP * WIP * WIP * Share - WIP * WIP * WIP * include userRole in space object * WIP * Server render shared page meta tags * disable user select * Close Navbar on outside click on mobile * update shared page spaceId * WIP * fix * close sidebar on click * close sidebar * defaults * update copy * Store share key in lowercase * refactor page breadcrumbs * Change copy * add link ref * open link button * add meta og:title * add twitter tags * WIP * make shares/info endpoint public * fix * * add /p/ segment to share urls * minore fixes * change mobile breadcrumb icon
This commit is contained in:
31
apps/client/src/pages/settings/shares/shares.tsx
Normal file
31
apps/client/src/pages/settings/shares/shares.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import SettingsTitle from "@/components/settings/settings-title.tsx";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { getAppName } from "@/lib/config.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ShareList from "@/features/share/components/share-list.tsx";
|
||||
import { Alert, Text } from "@mantine/core";
|
||||
import { IconInfoCircle } from "@tabler/icons-react";
|
||||
import React from "react";
|
||||
|
||||
export default function Shares() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>
|
||||
{t("Public sharing")} - {getAppName()}
|
||||
</title>
|
||||
</Helmet>
|
||||
<SettingsTitle title={t("Public sharing")} />
|
||||
|
||||
<Alert variant="light" color="blue" icon={<IconInfoCircle />}>
|
||||
{t(
|
||||
"Publicly shared pages from spaces you are a member of will appear here",
|
||||
)}
|
||||
</Alert>
|
||||
|
||||
<ShareList />
|
||||
</>
|
||||
);
|
||||
}
|
||||
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;
|
||||
}
|
||||
58
apps/client/src/pages/share/shared-page.tsx
Normal file
58
apps/client/src/pages/share/shared-page.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSharePageQuery } from "@/features/share/queries/share-query.ts";
|
||||
import { Container } from "@mantine/core";
|
||||
import React, { useEffect } 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 SingleSharedPage() {
|
||||
const { t } = useTranslation();
|
||||
const { pageSlug } = useParams();
|
||||
const { shareId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { data, isLoading, isError, error } = useSharePageQuery({
|
||||
pageId: extractPageSlugId(pageSlug),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (shareId && data) {
|
||||
if (data.share.key !== shareId) {
|
||||
navigate(`/share/${data.share.key}/p/${pageSlug}`, { replace: true });
|
||||
}
|
||||
}
|
||||
}, [shareId, data]);
|
||||
|
||||
if (isLoading) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (isError || !data) {
|
||||
if ([401, 403, 404].includes(error?.["status"])) {
|
||||
return <Error404 />;
|
||||
}
|
||||
return <div>{t("Error fetching page data.")}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>{`${data?.page?.title || t("untitled")}`}</title>
|
||||
{!data?.share.searchIndexing && (
|
||||
<meta name="robots" content="noindex" />
|
||||
)}
|
||||
</Helmet>
|
||||
|
||||
<Container size={900} p={0}>
|
||||
<ReadonlyPageEditor
|
||||
key={data.page.id}
|
||||
title={data.page.title}
|
||||
content={data.page.content}
|
||||
/>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user