mirror of
https://github.com/docmost/docmost.git
synced 2025-11-18 06:31:10 +10:00
WIP
This commit is contained in:
@ -362,5 +362,22 @@
|
||||
"Move page to a different space.": "Move page to a different space.",
|
||||
"Real-time editor connection lost. Retrying...": "Real-time editor connection lost. Retrying...",
|
||||
"Table of contents": "Table of contents",
|
||||
"Add headings (H1, H2, H3) to generate a table of contents.": "Add headings (H1, H2, H3) to generate a table of contents."
|
||||
"Add headings (H1, H2, H3) to generate a table of contents.": "Add headings (H1, H2, H3) to generate a table of contents.",
|
||||
"Share": "Share",
|
||||
"Public sharing": "Public sharing",
|
||||
"Shared by": "Shared by",
|
||||
"Shared at": "Shared at",
|
||||
"Inherits public sharing from": "Inherits public sharing from",
|
||||
"Publicly shared": "Publicly shared",
|
||||
"Share to web": "Share to web",
|
||||
"Anyone with the link can view this page": "Anyone with the link can view this page",
|
||||
"Make this page publicly accessible": "Make this page publicly accessible",
|
||||
"Include sub-pages": "Include sub-pages",
|
||||
"Make sub-pages public too": "Make sub-pages public too",
|
||||
"Allow search engines to index page": "Allow search engines to index page",
|
||||
"Open page": "Open page",
|
||||
"Page": "Page",
|
||||
"Revoke public link": "Revoke public link",
|
||||
"Are you sure you want to revoke this public link?": "Are you sure you want to revoke this public 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"
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ export default function App() {
|
||||
<Route path={"groups"} element={<Groups />} />
|
||||
<Route path={"groups/:groupId"} element={<GroupInfo />} />
|
||||
<Route path={"spaces"} element={<Spaces />} />
|
||||
<Route path={"shares"} element={<Shares />} />
|
||||
<Route path={"sharing"} element={<Shares />} />
|
||||
<Route path={"security"} element={<Security />} />
|
||||
{!isCloud() && <Route path={"license"} element={<License />} />}
|
||||
{isCloud() && <Route path={"billing"} element={<Billing />} />}
|
||||
|
||||
@ -82,7 +82,7 @@ const groupedData: DataGroup[] = [
|
||||
},
|
||||
{ label: "Groups", icon: IconUsersGroup, path: "/settings/groups" },
|
||||
{ label: "Spaces", icon: IconSpaces, path: "/settings/spaces" },
|
||||
{ label: "Sharing", icon: IconWorld, path: "/settings/shares" },
|
||||
{ label: "Public sharing", icon: IconWorld, path: "/settings/sharing" },
|
||||
|
||||
],
|
||||
},
|
||||
@ -172,7 +172,7 @@ export default function SettingsSidebar() {
|
||||
case "Security & SSO":
|
||||
prefetchHandler = prefetchSsoProviders;
|
||||
break;
|
||||
case "Sharing":
|
||||
case "Public sharing":
|
||||
prefetchHandler = prefetchShares;
|
||||
break;
|
||||
default:
|
||||
|
||||
@ -59,7 +59,7 @@ export default function PageHeaderMenu({ readOnly }: PageHeaderMenuProps) {
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<ShareModal/>
|
||||
<ShareModal readOnly={readOnly}/>
|
||||
|
||||
<Tooltip label={t("Comments")} openDelay={250} withArrow>
|
||||
<ActionIcon
|
||||
|
||||
@ -8,7 +8,6 @@ import {
|
||||
} from "@tabler/icons-react";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useUserRole from "@/hooks/use-user-role.tsx";
|
||||
import { ISharedItem } from "@/features/share/types/share.types.ts";
|
||||
import {
|
||||
buildPageUrl,
|
||||
@ -17,15 +16,16 @@ import {
|
||||
import { useClipboard } from "@mantine/hooks";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useDeleteShareMutation } from "@/features/share/queries/share-query.ts";
|
||||
|
||||
interface Props {
|
||||
share: ISharedItem;
|
||||
}
|
||||
export default function ShareActionMenu({ share }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const { isAdmin } = useUserRole();
|
||||
const navigate = useNavigate();
|
||||
const clipboard = useClipboard();
|
||||
const deleteShareMutation = useDeleteShareMutation();
|
||||
|
||||
const openPage = () => {
|
||||
const pageLink = buildPageUrl(
|
||||
@ -38,7 +38,7 @@ export default function ShareActionMenu({ share }: Props) {
|
||||
|
||||
const copyLink = () => {
|
||||
const shareLink = buildSharedPageUrl({
|
||||
shareId: share.includeSubPages ? share.key : undefined,
|
||||
shareId: share.key,
|
||||
pageTitle: share.page.title,
|
||||
pageSlugId: share.page.slugId,
|
||||
});
|
||||
@ -47,19 +47,19 @@ export default function ShareActionMenu({ share }: Props) {
|
||||
notifications.show({ message: t("Link copied") });
|
||||
};
|
||||
const onRevoke = async () => {
|
||||
//
|
||||
deleteShareMutation.mutateAsync(share.key);
|
||||
};
|
||||
|
||||
const openRevokeModal = () =>
|
||||
modals.openConfirmModal({
|
||||
title: t("Unshare page"),
|
||||
title: t("Revoke public link"),
|
||||
children: (
|
||||
<Text size="sm">
|
||||
{t("Are you sure you want to unshare this page?")}
|
||||
{t("Are you sure you want to revoke this public link?")}
|
||||
</Text>
|
||||
),
|
||||
centered: true,
|
||||
labels: { confirm: t("Unshare"), cancel: t("Don't") },
|
||||
labels: { confirm: t("Revoke"), cancel: t("Don't") },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: onRevoke,
|
||||
});
|
||||
@ -95,9 +95,9 @@ export default function ShareActionMenu({ share }: Props) {
|
||||
c="red"
|
||||
onClick={openRevokeModal}
|
||||
leftSection={<IconTrash size={16} />}
|
||||
disabled={!isAdmin}
|
||||
disabled={share.space?.userRole === "reader"}
|
||||
>
|
||||
{t("Unshare")}
|
||||
{t("Revoke")}
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
|
||||
@ -43,7 +43,7 @@ export default function ShareList() {
|
||||
component={Link}
|
||||
target="_blank"
|
||||
to={buildSharedPageUrl({
|
||||
shareId: share.includeSubPages ? share.key : undefined,
|
||||
shareId: share.key,
|
||||
pageTitle: share.page.title,
|
||||
pageSlugId: share.page.slugId,
|
||||
})}
|
||||
@ -52,7 +52,7 @@ export default function ShareList() {
|
||||
{getPageIcon(share.page.icon)}
|
||||
<div className={classes.shareLinkText}>
|
||||
<Text fz="sm" fw={500} lineClamp={1}>
|
||||
{share.page.title}
|
||||
{share.page.title || t("untitled")}
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
|
||||
@ -1,31 +1,42 @@
|
||||
import {
|
||||
Button,
|
||||
ActionIcon,
|
||||
Anchor,
|
||||
Group,
|
||||
Indicator,
|
||||
Popover,
|
||||
Switch,
|
||||
Text,
|
||||
TextInput,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import { IconWorld } from "@tabler/icons-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
useCreateShareMutation,
|
||||
useDeleteShareMutation,
|
||||
useShareForPageQuery,
|
||||
useUpdateShareMutation,
|
||||
} from "@/features/share/queries/share-query.ts";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { extractPageSlugId } from "@/lib";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import { extractPageSlugId, getPageIcon } from "@/lib";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CopyTextButton from "@/components/common/copy.tsx";
|
||||
import { getAppUrl } from "@/lib/config.ts";
|
||||
import { buildPageUrl } from "@/features/page/page.utils.ts";
|
||||
import classes from "@/features/share/components/share.module.css";
|
||||
|
||||
export default function ShareModal() {
|
||||
interface ShareModalProps {
|
||||
readOnly: boolean;
|
||||
}
|
||||
export default function ShareModal({ readOnly }: ShareModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const { pageSlug } = useParams();
|
||||
const pageId = extractPageSlugId(pageSlug);
|
||||
const { data: share } = useShareForPageQuery(pageId);
|
||||
const { spaceSlug } = useParams();
|
||||
const createShareMutation = useCreateShareMutation();
|
||||
const updateShareMutation = useUpdateShareMutation();
|
||||
const deleteShareMutation = useDeleteShareMutation();
|
||||
// pageIsShared means that the share exists and its level equals zero.
|
||||
const pageIsShared = share && share.level === 0;
|
||||
// if level is greater than zero, then it is a descendant page from a shared page
|
||||
@ -33,16 +44,6 @@ export default function ShareModal() {
|
||||
|
||||
const publicLink = `${getAppUrl()}/share/${share?.key}/${pageSlug}`;
|
||||
|
||||
|
||||
// TODO: think of permissions
|
||||
// controls should be read only for non space editors.
|
||||
|
||||
|
||||
// we could use the same shared content but have it have a share status
|
||||
// when you unshare, we hide the rest menu
|
||||
|
||||
// todo, is public only if this is the shared page
|
||||
// if this is not the shared page and include chdilren == false, then set it to false
|
||||
const [isPagePublic, setIsPagePublic] = useState<boolean>(false);
|
||||
useEffect(() => {
|
||||
if (share) {
|
||||
@ -54,9 +55,20 @@ export default function ShareModal() {
|
||||
|
||||
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = event.currentTarget.checked;
|
||||
createShareMutation.mutateAsync({ pageId: pageId });
|
||||
setIsPagePublic(value);
|
||||
// on create refetch share
|
||||
|
||||
if (value) {
|
||||
createShareMutation.mutateAsync({
|
||||
pageId: pageId,
|
||||
includeSubPages: true,
|
||||
searchIndexing: true,
|
||||
});
|
||||
setIsPagePublic(value);
|
||||
} else {
|
||||
if (share && share.id) {
|
||||
deleteShareMutation.mutateAsync(share.id);
|
||||
setIsPagePublic(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubPagesChange = async (
|
||||
@ -82,32 +94,74 @@ export default function ShareModal() {
|
||||
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>
|
||||
<Tooltip label={t("Share")} openDelay={250} withArrow>
|
||||
<Indicator
|
||||
color="green"
|
||||
offset={7}
|
||||
disabled={!isPagePublic}
|
||||
withBorder
|
||||
>
|
||||
<ActionIcon variant="default" style={{ border: "none" }}>
|
||||
<IconWorld size={20} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
</Indicator>
|
||||
</Tooltip>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown>
|
||||
{isDescendantShared ? (
|
||||
<Text>
|
||||
{t("This page was shared via")} {share.sharedPage.title}
|
||||
</Text>
|
||||
<>
|
||||
<Text size="sm">{t("Inherits public sharing from")}</Text>
|
||||
<Anchor
|
||||
size="sm"
|
||||
underline="never"
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
color: "var(--mantine-color-text)",
|
||||
}}
|
||||
component={Link}
|
||||
to={buildPageUrl(
|
||||
spaceSlug,
|
||||
share.sharedPage.slugId,
|
||||
share.sharedPage.title,
|
||||
)}
|
||||
>
|
||||
<Group gap="4" wrap="nowrap" my="sm">
|
||||
{getPageIcon(share.sharedPage.icon)}
|
||||
<div className={classes.shareLinkText}>
|
||||
<Text fz="sm" fw={500} lineClamp={1}>
|
||||
{share.sharedPage.title || t("untitled")}
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</Anchor>
|
||||
|
||||
<Group my="sm" grow>
|
||||
<TextInput
|
||||
variant="filled"
|
||||
value={publicLink}
|
||||
readOnly
|
||||
rightSection={<CopyTextButton text={publicLink} />}
|
||||
/>
|
||||
</Group>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Group justify="space-between" wrap="nowrap" gap="xl">
|
||||
<div>
|
||||
<Text>Share page</Text>
|
||||
<Text size="sm">
|
||||
{isPagePublic ? t("Publicly shared") : t("Share to web")}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
Make it public to the internet
|
||||
{isPagePublic
|
||||
? t("Anyone with the link can view this page")
|
||||
: t("Make this page publicly accessible")}
|
||||
</Text>
|
||||
</div>
|
||||
<Switch
|
||||
onChange={handleChange}
|
||||
defaultChecked={isPagePublic}
|
||||
size="sm"
|
||||
disabled={readOnly}
|
||||
size="xs"
|
||||
/>
|
||||
</Group>
|
||||
|
||||
@ -124,29 +178,32 @@ export default function ShareModal() {
|
||||
|
||||
<Group justify="space-between" wrap="nowrap" gap="xl">
|
||||
<div>
|
||||
<Text>{t("Include sub pages")}</Text>
|
||||
<Text size="sm">{t("Include sub-pages")}</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
Include children of this page
|
||||
{t("Make sub-pages public too")}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<Switch
|
||||
onChange={handleSubPagesChange}
|
||||
checked={share.includeSubPages}
|
||||
size="xs"
|
||||
disabled={readOnly}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
<Group justify="space-between" wrap="nowrap" gap="xl" mt="sm">
|
||||
<div>
|
||||
<Text>{t("Enable search indexing")}</Text>
|
||||
<Text size="sm">{t("Search engine indexing")}</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
Allow search engine indexing
|
||||
{t("Allow search engines to index page")}
|
||||
</Text>
|
||||
</div>
|
||||
<Switch
|
||||
onChange={handleIndexSearchChange}
|
||||
checked={share.searchIndexing}
|
||||
size="xs"
|
||||
disabled={readOnly}
|
||||
/>
|
||||
</Group>
|
||||
</>
|
||||
|
||||
@ -6,7 +6,6 @@ import {
|
||||
Button,
|
||||
Group,
|
||||
ScrollArea,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import { useGetSharedPageTreeQuery } from "@/features/share/queries/share-query.ts";
|
||||
@ -56,14 +55,16 @@ export default function ShareShell({
|
||||
return (
|
||||
<AppShell
|
||||
header={{ height: 48 }}
|
||||
navbar={{
|
||||
width: 300,
|
||||
breakpoint: "sm",
|
||||
collapsed: {
|
||||
mobile: !mobileOpened,
|
||||
desktop: !desktopOpened,
|
||||
{...(data?.pageTree?.length > 1 && {
|
||||
navbar: {
|
||||
width: 300,
|
||||
breakpoint: "sm",
|
||||
collapsed: {
|
||||
mobile: !mobileOpened,
|
||||
desktop: !desktopOpened,
|
||||
},
|
||||
},
|
||||
}}
|
||||
})}
|
||||
aside={{
|
||||
width: 300,
|
||||
breakpoint: "sm",
|
||||
@ -77,7 +78,7 @@ export default function ShareShell({
|
||||
<AppShell.Header>
|
||||
<Group wrap="nowrap" justify="space-between" py="sm" px="xl">
|
||||
<Group>
|
||||
{data?.pageTree?.length > 0 && (
|
||||
{data?.pageTree?.length > 1 && (
|
||||
<>
|
||||
<Tooltip label={t("Sidebar toggle")}>
|
||||
<SidebarToggle
|
||||
@ -133,7 +134,7 @@ export default function ShareShell({
|
||||
</Group>
|
||||
</AppShell.Header>
|
||||
|
||||
{data?.pageTree?.length > 0 && (
|
||||
{data?.pageTree?.length > 1 && (
|
||||
<AppShell.Navbar p="md">
|
||||
<MemoizedSharedTree sharedPageTree={data} />
|
||||
</AppShell.Navbar>
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useElementSize, useMergedRef } from "@mantine/hooks";
|
||||
import { SpaceTreeNode } from "@/features/page/tree/types.ts";
|
||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import { atom, useAtom } from "jotai/index";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { buildSharedPageUrl } from "@/features/page/page.utils.ts";
|
||||
@ -22,7 +22,6 @@ import { extractPageSlugId } from "@/lib";
|
||||
import { OpenMap } from "react-arborist/dist/main/state/open-slice";
|
||||
import classes from "@/features/page/tree/styles/tree.module.css";
|
||||
import styles from "./share.module.css";
|
||||
import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts";
|
||||
import { mobileSidebarAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts";
|
||||
|
||||
interface SharedTree {
|
||||
@ -54,12 +53,16 @@ export default function SharedTree({ sharedPageTree }: SharedTree) {
|
||||
const parentNodeId = treeData?.[0]?.slugId;
|
||||
|
||||
if (parentNodeId && tree) {
|
||||
const parentNode = tree.get(parentNodeId);
|
||||
|
||||
setTimeout(() => {
|
||||
tree.openSiblings(tree.get(parentNodeId));
|
||||
if (parentNode) {
|
||||
tree.openSiblings(parentNode);
|
||||
}
|
||||
});
|
||||
|
||||
// open direct children of parent node
|
||||
tree.get(parentNodeId).children.forEach((node) => {
|
||||
parentNode?.children.forEach((node) => {
|
||||
tree.openSiblings(node);
|
||||
});
|
||||
}
|
||||
@ -84,7 +87,7 @@ export default function SharedTree({ sharedPageTree }: SharedTree) {
|
||||
<div ref={mergedRef} className={classes.treeContainer}>
|
||||
{rootElement.current && (
|
||||
<Tree
|
||||
initialData={treeData}
|
||||
data={treeData}
|
||||
disableDrag={true}
|
||||
disableDrop={true}
|
||||
disableEdit={true}
|
||||
|
||||
@ -9,12 +9,13 @@ import { notifications } from "@mantine/notifications";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
ICreateShare,
|
||||
ISharedItem, ISharedPage,
|
||||
ISharedItem,
|
||||
ISharedPage,
|
||||
ISharedPageTree,
|
||||
IShareForPage,
|
||||
IShareInfoInput,
|
||||
IUpdateShare,
|
||||
} from '@/features/share/types/share.types.ts';
|
||||
} from "@/features/share/types/share.types.ts";
|
||||
import {
|
||||
createShare,
|
||||
deleteShare,
|
||||
@ -26,6 +27,7 @@ import {
|
||||
} from "@/features/share/services/share-service.ts";
|
||||
import { IPage } from "@/features/page/types/page.types.ts";
|
||||
import { IPagination, QueryParams } from "@/lib/types.ts";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export function useGetSharesQuery(
|
||||
params?: QueryParams,
|
||||
@ -56,7 +58,8 @@ export function useShareForPageQuery(
|
||||
queryKey: ["share-for-page", pageId],
|
||||
queryFn: () => getShareForPage(pageId),
|
||||
enabled: !!pageId,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
staleTime: 0,
|
||||
retry: false,
|
||||
});
|
||||
|
||||
return query;
|
||||
@ -64,8 +67,16 @@ export function useShareForPageQuery(
|
||||
|
||||
export function useCreateShareMutation() {
|
||||
const { t } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<any, Error, ICreateShare>({
|
||||
mutationFn: (data) => createShare(data),
|
||||
onSuccess: (data) => {
|
||||
queryClient.invalidateQueries({
|
||||
predicate: (item) =>
|
||||
["share-for-page", "share-list"].includes(item.queryKey[0] as string),
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
notifications.show({ message: t("Failed to share page"), color: "red" });
|
||||
},
|
||||
@ -77,9 +88,22 @@ export function useUpdateShareMutation() {
|
||||
return useMutation<any, Error, IUpdateShare>({
|
||||
mutationFn: (data) => updateShare(data),
|
||||
onSuccess: (data) => {
|
||||
queryClient.refetchQueries({
|
||||
queryClient.invalidateQueries({
|
||||
predicate: (item) =>
|
||||
["share-for-page"].includes(item.queryKey[0] as string),
|
||||
["share-for-page", "share-list"].includes(item.queryKey[0] as string),
|
||||
});
|
||||
},
|
||||
onError: (error, params) => {
|
||||
if (error?.["status"] === 404) {
|
||||
queryClient.removeQueries({
|
||||
predicate: (item) =>
|
||||
["share-for-page"].includes(item.queryKey[0] as string),
|
||||
});
|
||||
}
|
||||
|
||||
notifications.show({
|
||||
message: error?.["response"]?.data?.message || "Share share not found",
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
});
|
||||
@ -87,14 +111,33 @@ export function useUpdateShareMutation() {
|
||||
|
||||
export function useDeleteShareMutation() {
|
||||
const { t } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (shareId: string) => deleteShare(shareId),
|
||||
onSuccess: () => {
|
||||
onSuccess: (data) => {
|
||||
queryClient.removeQueries({
|
||||
predicate: (item) =>
|
||||
["share-for-page"].includes(item.queryKey[0] as string),
|
||||
});
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
predicate: (item) =>
|
||||
["share-list"].includes(item.queryKey[0] as string),
|
||||
});
|
||||
|
||||
notifications.show({ message: t("Share deleted successfully") });
|
||||
},
|
||||
onError: (error) => {
|
||||
if (error?.["status"] === 404) {
|
||||
queryClient.removeQueries({
|
||||
predicate: (item) =>
|
||||
["share-for-page"].includes(item.queryKey[0] as string),
|
||||
});
|
||||
}
|
||||
|
||||
notifications.show({
|
||||
message: t("Failed to delete share"),
|
||||
message: error?.["response"]?.data?.message || "Failed to delete share",
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
|
||||
@ -25,6 +25,7 @@ export interface ISharedItem extends IShare {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
userRole: string;
|
||||
};
|
||||
creator: {
|
||||
id: string;
|
||||
@ -37,21 +38,17 @@ export interface ISharedPage extends IShare {
|
||||
page: IPage;
|
||||
share: IShare & {
|
||||
level: number;
|
||||
sharedPage: { id: string; slugId: string; title: string };
|
||||
sharedPage: { id: string; slugId: string; title: string; icon: string };
|
||||
};
|
||||
}
|
||||
|
||||
export interface IShareForPage extends IShare {
|
||||
level: number;
|
||||
page: {
|
||||
id: string;
|
||||
title: string;
|
||||
slugId: string;
|
||||
};
|
||||
sharedPage: {
|
||||
id: string;
|
||||
slugId: string;
|
||||
title: string;
|
||||
icon: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,9 @@ 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();
|
||||
@ -11,10 +14,17 @@ export default function Shares() {
|
||||
<>
|
||||
<Helmet>
|
||||
<title>
|
||||
{t("Shares")} - {getAppName()}
|
||||
{t("Public sharing")} - {getAppName()}
|
||||
</title>
|
||||
</Helmet>
|
||||
<SettingsTitle title={t("Shares")} />
|
||||
<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 />
|
||||
</>
|
||||
);
|
||||
|
||||
@ -22,7 +22,7 @@ export default function SingleSharedPage() {
|
||||
if (shareId && data) {
|
||||
if (data.share.key !== shareId) {
|
||||
// affects parent share, what to do?
|
||||
//navigate(`/share/${data.share.key}/${pageSlug}`);
|
||||
navigate(`/share/${data.share.key}/${pageSlug}`);
|
||||
}
|
||||
}
|
||||
}, [shareId, data]);
|
||||
@ -41,7 +41,8 @@ export default function SingleSharedPage() {
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>{`${data?.page?.icon || ""} ${data?.page?.title || t("untitled")}`}</title>
|
||||
<title>{`${data?.page?.title || t("untitled")}`}</title>
|
||||
{!data?.share.searchIndexing && <meta name="robots" content="noindex" />}
|
||||
</Helmet>
|
||||
|
||||
<Container size={900}>
|
||||
|
||||
@ -69,7 +69,7 @@ export class ShareService {
|
||||
key: generateSlugId(),
|
||||
pageId: page.id,
|
||||
includeSubPages: createShareDto.includeSubPages,
|
||||
searchIndexing: true,
|
||||
searchIndexing: createShareDto.searchIndexing,
|
||||
creatorId: authUserId,
|
||||
spaceId: page.spaceId,
|
||||
workspaceId,
|
||||
@ -126,6 +126,7 @@ export class ShareService {
|
||||
'id',
|
||||
'slugId',
|
||||
'pages.title',
|
||||
'pages.icon',
|
||||
'parentPageId',
|
||||
sql`0`.as('level'),
|
||||
])
|
||||
@ -137,6 +138,7 @@ export class ShareService {
|
||||
'p.id',
|
||||
'p.slugId',
|
||||
'p.title',
|
||||
'p.icon',
|
||||
'p.parentPageId',
|
||||
// Increase the level by 1 for each ancestor.
|
||||
sql`ph.level + 1`.as('level'),
|
||||
@ -150,11 +152,13 @@ export class ShareService {
|
||||
'page_hierarchy.id as sharedPageId',
|
||||
'page_hierarchy.slugId as sharedPageSlugId',
|
||||
'page_hierarchy.title as sharedPageTitle',
|
||||
'page_hierarchy.icon as sharedPageIcon',
|
||||
'page_hierarchy.level as level',
|
||||
'shares.id',
|
||||
'shares.key',
|
||||
'shares.pageId',
|
||||
'shares.includeSubPages',
|
||||
'shares.searchIndexing',
|
||||
'shares.creatorId',
|
||||
'shares.spaceId',
|
||||
'shares.workspaceId',
|
||||
@ -166,18 +170,19 @@ export class ShareService {
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!share || share.workspaceId != workspaceId) {
|
||||
throw new NotFoundException('Shared page not found');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (share.level === 1 && !share.includeSubPages) {
|
||||
// we can only show a page if its shared ancestor permits it
|
||||
throw new NotFoundException('Shared page not found');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
id: share.id,
|
||||
key: share.key,
|
||||
includeSubPages: share.includeSubPages,
|
||||
searchIndexing: share.searchIndexing,
|
||||
pageId: share.pageId,
|
||||
creatorId: share.creatorId,
|
||||
spaceId: share.spaceId,
|
||||
@ -188,6 +193,7 @@ export class ShareService {
|
||||
id: share.sharedPageId,
|
||||
slugId: share.sharedPageSlugId,
|
||||
title: share.sharedPageTitle,
|
||||
icon: share.sharedPageIcon,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user