feat(core): add no access to share menu (#10927)
This commit is contained in:
@@ -110,6 +110,13 @@ globalStyle(`${linkPreview} *`, {
|
|||||||
cursor: 'default',
|
cursor: 'default',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const notFound = style({
|
||||||
|
color: cssVarV2('text/secondary'),
|
||||||
|
fontSize: '12px',
|
||||||
|
lineHeight: '16px',
|
||||||
|
textAlign: 'center',
|
||||||
|
});
|
||||||
|
|
||||||
export const linkPreviewRenderer = style({
|
export const linkPreviewRenderer = style({
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
type Link,
|
type Link,
|
||||||
} from '@affine/core/modules/doc-link';
|
} from '@affine/core/modules/doc-link';
|
||||||
import { toURLSearchParams } from '@affine/core/modules/navigation';
|
import { toURLSearchParams } from '@affine/core/modules/navigation';
|
||||||
|
import { GuardService } from '@affine/core/modules/permissions';
|
||||||
import { GlobalSessionStateService } from '@affine/core/modules/storage';
|
import { GlobalSessionStateService } from '@affine/core/modules/storage';
|
||||||
import { WorkbenchLink } from '@affine/core/modules/workbench';
|
import { WorkbenchLink } from '@affine/core/modules/workbench';
|
||||||
import {
|
import {
|
||||||
@@ -15,13 +16,17 @@ import {
|
|||||||
} from '@affine/core/modules/workspace';
|
} from '@affine/core/modules/workspace';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import track from '@affine/track';
|
import track from '@affine/track';
|
||||||
import type { TransformerMiddleware } from '@blocksuite/affine/store';
|
import type {
|
||||||
|
ExtensionType,
|
||||||
|
TransformerMiddleware,
|
||||||
|
} from '@blocksuite/affine/store';
|
||||||
import { ToggleDownIcon } from '@blocksuite/icons/rc';
|
import { ToggleDownIcon } from '@blocksuite/icons/rc';
|
||||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
import * as Collapsible from '@radix-ui/react-collapsible';
|
||||||
import {
|
import {
|
||||||
LiveData,
|
LiveData,
|
||||||
useFramework,
|
useFramework,
|
||||||
useLiveData,
|
useLiveData,
|
||||||
|
useService,
|
||||||
useServices,
|
useServices,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
import {
|
import {
|
||||||
@@ -46,6 +51,18 @@ import * as styles from './bi-directional-link-panel.css';
|
|||||||
|
|
||||||
const PREFIX = 'bi-directional-link-panel-collapse:';
|
const PREFIX = 'bi-directional-link-panel-collapse:';
|
||||||
|
|
||||||
|
type BacklinkGroups = {
|
||||||
|
docId: string;
|
||||||
|
title: string;
|
||||||
|
links: Backlink[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type TextRendererOptions = {
|
||||||
|
customHeading: boolean;
|
||||||
|
extensions: ExtensionType[];
|
||||||
|
additionalMiddlewares: TransformerMiddleware[];
|
||||||
|
};
|
||||||
|
|
||||||
const useBiDirectionalLinkPanelCollapseState = (
|
const useBiDirectionalLinkPanelCollapseState = (
|
||||||
docId: string,
|
docId: string,
|
||||||
linkDocId?: string
|
linkDocId?: string
|
||||||
@@ -155,7 +172,7 @@ const usePreviewExtensions = () => {
|
|||||||
return [extensions, portals] as const;
|
return [extensions, portals] as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useBacklinkGroups = () => {
|
const useBacklinkGroups: () => BacklinkGroups[] = () => {
|
||||||
const { docLinksService } = useServices({
|
const { docLinksService } = useServices({
|
||||||
DocLinksService,
|
DocLinksService,
|
||||||
});
|
});
|
||||||
@@ -226,72 +243,10 @@ export const BacklinkGroups = () => {
|
|||||||
docId={docService.doc.id}
|
docId={docService.doc.id}
|
||||||
linkDocId={linkGroup.docId}
|
linkDocId={linkGroup.docId}
|
||||||
>
|
>
|
||||||
<div className={styles.linkPreviewContainer}>
|
<LinkPreview
|
||||||
{linkGroup.links.map(link => {
|
textRendererOptions={textRendererOptions}
|
||||||
if (!link.markdownPreview) {
|
linkGroup={linkGroup}
|
||||||
return null;
|
/>
|
||||||
}
|
|
||||||
const searchParams = new URLSearchParams();
|
|
||||||
const displayMode = link.displayMode || 'page';
|
|
||||||
searchParams.set('mode', displayMode);
|
|
||||||
|
|
||||||
let blockId = link.blockId;
|
|
||||||
if (
|
|
||||||
link.parentFlavour === 'affine:database' &&
|
|
||||||
link.parentBlockId
|
|
||||||
) {
|
|
||||||
// if parentBlockFlavour is 'affine:database',
|
|
||||||
// we will fallback to the database block instead
|
|
||||||
blockId = link.parentBlockId;
|
|
||||||
} else if (displayMode === 'edgeless' && link.noteBlockId) {
|
|
||||||
// if note has displayMode === 'edgeless' && has noteBlockId,
|
|
||||||
// set noteBlockId as blockId
|
|
||||||
blockId = link.noteBlockId;
|
|
||||||
}
|
|
||||||
|
|
||||||
searchParams.set('blockIds', blockId);
|
|
||||||
|
|
||||||
const to = {
|
|
||||||
pathname: '/' + linkGroup.docId,
|
|
||||||
search: '?' + searchParams.toString(),
|
|
||||||
hash: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
// if this backlink has no noteBlock && displayMode is edgeless, we will render
|
|
||||||
// the link as a page link
|
|
||||||
const edgelessLink =
|
|
||||||
displayMode === 'edgeless' && !link.noteBlockId;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<WorkbenchLink
|
|
||||||
to={to}
|
|
||||||
key={link.blockId}
|
|
||||||
className={styles.linkPreview}
|
|
||||||
onClick={() => {
|
|
||||||
track.doc.biDirectionalLinksPanel.backlinkPreview.navigate();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{edgelessLink ? (
|
|
||||||
<>
|
|
||||||
[Edgeless]
|
|
||||||
<AffinePageReference
|
|
||||||
key={link.blockId}
|
|
||||||
pageId={linkGroup.docId}
|
|
||||||
params={searchParams}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<LitTextRenderer
|
|
||||||
className={styles.linkPreviewRenderer}
|
|
||||||
answer={link.markdownPreview}
|
|
||||||
schema={getAFFiNEWorkspaceSchema()}
|
|
||||||
options={textRendererOptions}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</WorkbenchLink>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</CollapsibleSection>
|
</CollapsibleSection>
|
||||||
))}
|
))}
|
||||||
<>
|
<>
|
||||||
@@ -303,6 +258,90 @@ export const BacklinkGroups = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const LinkPreview = ({
|
||||||
|
linkGroup,
|
||||||
|
textRendererOptions,
|
||||||
|
}: {
|
||||||
|
linkGroup: BacklinkGroups;
|
||||||
|
textRendererOptions: TextRendererOptions;
|
||||||
|
}) => {
|
||||||
|
const guardService = useService(GuardService);
|
||||||
|
const canAccess = useLiveData(guardService.can$('Doc_Read', linkGroup.docId));
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
|
if (!canAccess) {
|
||||||
|
return (
|
||||||
|
<span className={styles.notFound}>
|
||||||
|
{t['com.affine.share-menu.option.permission.no-access']()}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={styles.linkPreviewContainer}>
|
||||||
|
{linkGroup.links.map(link => {
|
||||||
|
if (!link.markdownPreview) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const searchParams = new URLSearchParams();
|
||||||
|
const displayMode = link.displayMode || 'page';
|
||||||
|
searchParams.set('mode', displayMode);
|
||||||
|
|
||||||
|
let blockId = link.blockId;
|
||||||
|
if (link.parentFlavour === 'affine:database' && link.parentBlockId) {
|
||||||
|
// if parentBlockFlavour is 'affine:database',
|
||||||
|
// we will fallback to the database block instead
|
||||||
|
blockId = link.parentBlockId;
|
||||||
|
} else if (displayMode === 'edgeless' && link.noteBlockId) {
|
||||||
|
// if note has displayMode === 'edgeless' && has noteBlockId,
|
||||||
|
// set noteBlockId as blockId
|
||||||
|
blockId = link.noteBlockId;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchParams.set('blockIds', blockId);
|
||||||
|
|
||||||
|
const to = {
|
||||||
|
pathname: '/' + linkGroup.docId,
|
||||||
|
search: '?' + searchParams.toString(),
|
||||||
|
hash: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
// if this backlink has no noteBlock && displayMode is edgeless, we will render
|
||||||
|
// the link as a page link
|
||||||
|
const edgelessLink = displayMode === 'edgeless' && !link.noteBlockId;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<WorkbenchLink
|
||||||
|
to={to}
|
||||||
|
key={link.blockId}
|
||||||
|
className={styles.linkPreview}
|
||||||
|
onClick={() => {
|
||||||
|
track.doc.biDirectionalLinksPanel.backlinkPreview.navigate();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{edgelessLink ? (
|
||||||
|
<>
|
||||||
|
[Edgeless]
|
||||||
|
<AffinePageReference
|
||||||
|
key={link.blockId}
|
||||||
|
pageId={linkGroup.docId}
|
||||||
|
params={searchParams}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<LitTextRenderer
|
||||||
|
className={styles.linkPreviewRenderer}
|
||||||
|
answer={link.markdownPreview}
|
||||||
|
schema={getAFFiNEWorkspaceSchema()}
|
||||||
|
options={textRendererOptions}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</WorkbenchLink>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const BiDirectionalLinkPanel = () => {
|
export const BiDirectionalLinkPanel = () => {
|
||||||
const { docLinksService, docService } = useServices({
|
const { docLinksService, docService } = useServices({
|
||||||
DocLinksService,
|
DocLinksService,
|
||||||
|
|||||||
@@ -68,10 +68,12 @@ export const DetailPageWrapper = ({
|
|||||||
children,
|
children,
|
||||||
skeleton,
|
skeleton,
|
||||||
notFound,
|
notFound,
|
||||||
|
canAccess,
|
||||||
}: PropsWithChildren<{
|
}: PropsWithChildren<{
|
||||||
pageId: string;
|
pageId: string;
|
||||||
skeleton: ReactNode;
|
skeleton: ReactNode;
|
||||||
notFound: ReactNode;
|
notFound: ReactNode;
|
||||||
|
canAccess?: boolean;
|
||||||
}>) => {
|
}>) => {
|
||||||
const { doc, editor, docListReady } = useLoadDoc(pageId);
|
const { doc, editor, docListReady } = useLoadDoc(pageId);
|
||||||
// if sync engine has been synced and the page is null, show 404 page.
|
// if sync engine has been synced and the page is null, show 404 page.
|
||||||
@@ -79,8 +81,10 @@ export const DetailPageWrapper = ({
|
|||||||
return notFound;
|
return notFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!doc || !editor) {
|
if (canAccess === undefined || !doc || !editor) {
|
||||||
return skeleton;
|
return skeleton;
|
||||||
|
} else if (!canAccess) {
|
||||||
|
return notFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -368,6 +368,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
|||||||
export const Component = () => {
|
export const Component = () => {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const recentPages = useService(RecentDocsService);
|
const recentPages = useService(RecentDocsService);
|
||||||
|
const guardService = useService(GuardService);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (params.pageId) {
|
if (params.pageId) {
|
||||||
@@ -379,10 +380,14 @@ export const Component = () => {
|
|||||||
}, [params, recentPages]);
|
}, [params, recentPages]);
|
||||||
|
|
||||||
const pageId = params.pageId;
|
const pageId = params.pageId;
|
||||||
|
const canAccess = useLiveData(
|
||||||
|
pageId ? guardService.can$('Doc_Read', pageId) : undefined
|
||||||
|
);
|
||||||
|
|
||||||
return pageId ? (
|
return pageId ? (
|
||||||
<DetailPageWrapper
|
<DetailPageWrapper
|
||||||
pageId={pageId}
|
pageId={pageId}
|
||||||
|
canAccess={canAccess}
|
||||||
skeleton={<PageDetailSkeleton />}
|
skeleton={<PageDetailSkeleton />}
|
||||||
notFound={<PageNotFound noPermission />}
|
notFound={<PageNotFound noPermission />}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export const ExplorerDocNode = ({
|
|||||||
reference: isLinked,
|
reference: isLinked,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const docTitle = useLiveData(docDisplayMetaService.title$(docId));
|
const docTitle = useLiveData(docDisplayMetaService.title$(docId));
|
||||||
const isInTrash = useLiveData(docRecord?.trash$);
|
const isInTrash = useLiveData(docRecord?.trash$);
|
||||||
const enableEmojiIcon = useLiveData(
|
const enableEmojiIcon = useLiveData(
|
||||||
@@ -133,10 +134,19 @@ export const ExplorerDocNode = ({
|
|||||||
operations={finalOperations}
|
operations={finalOperations}
|
||||||
data-testid={`explorer-doc-${docId}`}
|
data-testid={`explorer-doc-${docId}`}
|
||||||
>
|
>
|
||||||
{children?.map(child => (
|
<DocPermissionGuard docId={docId} permission="Doc_Read">
|
||||||
<ExplorerDocNode key={child.docId} docId={child.docId} isLinked />
|
{canRead =>
|
||||||
))}
|
canRead
|
||||||
|
? children?.map((child, index) => (
|
||||||
|
<ExplorerDocNode
|
||||||
|
key={`${child.docId}-${index}`}
|
||||||
|
docId={child.docId}
|
||||||
|
isLinked
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</DocPermissionGuard>
|
||||||
<DocPermissionGuard docId={docId} permission="Doc_Update">
|
<DocPermissionGuard docId={docId} permission="Doc_Update">
|
||||||
{canEdit =>
|
{canEdit =>
|
||||||
canEdit ? (
|
canEdit ? (
|
||||||
|
|||||||
@@ -254,6 +254,9 @@ const MobileDetailPage = ({
|
|||||||
const [showTitle, setShowTitle] = useState(checkShowTitle);
|
const [showTitle, setShowTitle] = useState(checkShowTitle);
|
||||||
const title = useLiveData(docDisplayMetaService.title$(pageId));
|
const title = useLiveData(docDisplayMetaService.title$(pageId));
|
||||||
|
|
||||||
|
const guardService = useService(GuardService);
|
||||||
|
const canAccess = useLiveData(guardService.can$('Doc_Read', pageId));
|
||||||
|
|
||||||
const allJournalDates = useLiveData(journalService.allJournalDates$);
|
const allJournalDates = useLiveData(journalService.allJournalDates$);
|
||||||
|
|
||||||
const location = useLiveData(workbench.location$);
|
const location = useLiveData(workbench.location$);
|
||||||
@@ -281,6 +284,7 @@ const MobileDetailPage = ({
|
|||||||
skeleton={date ? skeleton : skeletonWithBack}
|
skeleton={date ? skeleton : skeletonWithBack}
|
||||||
notFound={date ? notFound : notFoundWithBack}
|
notFound={date ? notFound : notFoundWithBack}
|
||||||
pageId={pageId}
|
pageId={pageId}
|
||||||
|
canAccess={canAccess}
|
||||||
>
|
>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
back={!fromTab}
|
back={!fromTab}
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import { EmptyNodeChildren } from '../../layouts/empty-node-children';
|
|||||||
|
|
||||||
export const Empty = ({
|
export const Empty = ({
|
||||||
onDrop,
|
onDrop,
|
||||||
|
noAccessible = false,
|
||||||
}: {
|
}: {
|
||||||
onDrop: (data: DropTargetDropEvent<AffineDNDData>) => void;
|
onDrop: (data: DropTargetDropEvent<AffineDNDData>) => void;
|
||||||
|
noAccessible?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const { dropTargetRef } = useDropTarget<AffineDNDData>(
|
const { dropTargetRef } = useDropTarget<AffineDNDData>(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -19,7 +21,9 @@ export const Empty = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<EmptyNodeChildren ref={dropTargetRef}>
|
<EmptyNodeChildren ref={dropTargetRef}>
|
||||||
{t['com.affine.rootAppSidebar.docs.no-subdoc']()}
|
{noAccessible
|
||||||
|
? t['com.affine.share-menu.option.permission.no-access']()
|
||||||
|
: t['com.affine.rootAppSidebar.docs.no-subdoc']()}
|
||||||
</EmptyNodeChildren>
|
</EmptyNodeChildren>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
toast,
|
toast,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
|
import { DocPermissionGuard } from '@affine/core/components/guard/doc-guard';
|
||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||||
import { DocsService } from '@affine/core/modules/doc';
|
import { DocsService } from '@affine/core/modules/doc';
|
||||||
@@ -43,6 +44,7 @@ export const ExplorerDocNode = ({
|
|||||||
}: {
|
}: {
|
||||||
docId: string;
|
docId: string;
|
||||||
isLinked?: boolean;
|
isLinked?: boolean;
|
||||||
|
forwardKey?: string;
|
||||||
} & GenericExplorerNode) => {
|
} & GenericExplorerNode) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const {
|
const {
|
||||||
@@ -261,24 +263,35 @@ export const ExplorerDocNode = ({
|
|||||||
}}
|
}}
|
||||||
onRename={handleRename}
|
onRename={handleRename}
|
||||||
childrenPlaceholder={
|
childrenPlaceholder={
|
||||||
searching ? null : <Empty onDrop={handleDropOnPlaceholder} />
|
searching ? null : (
|
||||||
|
<Empty
|
||||||
|
onDrop={handleDropOnPlaceholder}
|
||||||
|
noAccessible={!!children && children.length > 0}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
operations={finalOperations}
|
operations={finalOperations}
|
||||||
dropEffect={handleDropEffectOnDoc}
|
dropEffect={handleDropEffectOnDoc}
|
||||||
data-testid={`explorer-doc-${docId}`}
|
data-testid={`explorer-doc-${docId}`}
|
||||||
>
|
>
|
||||||
{children?.map(child => (
|
<DocPermissionGuard docId={docId} permission="Doc_Read">
|
||||||
<ExplorerDocNode
|
{canRead =>
|
||||||
key={child.docId}
|
canRead
|
||||||
docId={child.docId}
|
? children?.map((child, index) => (
|
||||||
reorderable={false}
|
<ExplorerDocNode
|
||||||
location={{
|
key={`${child.docId}-${index}`}
|
||||||
at: 'explorer:doc:linked-docs',
|
docId={child.docId}
|
||||||
docId,
|
reorderable={false}
|
||||||
}}
|
location={{
|
||||||
isLinked
|
at: 'explorer:doc:linked-docs',
|
||||||
/>
|
docId,
|
||||||
))}
|
}}
|
||||||
|
isLinked
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</DocPermissionGuard>
|
||||||
</ExplorerTreeNode>
|
</ExplorerTreeNode>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export interface BaseExplorerTreeNodeProps {
|
|||||||
operations?: NodeOperation[];
|
operations?: NodeOperation[];
|
||||||
childrenOperations?: NodeOperation[];
|
childrenOperations?: NodeOperation[];
|
||||||
childrenPlaceholder?: React.ReactNode;
|
childrenPlaceholder?: React.ReactNode;
|
||||||
|
|
||||||
linkComponent?: React.ComponentType<
|
linkComponent?: React.ComponentType<
|
||||||
React.PropsWithChildren<{ to: To; className?: string }> &
|
React.PropsWithChildren<{ to: To; className?: string }> &
|
||||||
RefAttributes<any> & { draggable?: boolean }
|
RefAttributes<any> & { draggable?: boolean }
|
||||||
@@ -492,7 +493,7 @@ export const ExplorerTreeNode = ({
|
|||||||
<Collapsible.Content style={{ display: dragging ? 'none' : undefined }}>
|
<Collapsible.Content style={{ display: dragging ? 'none' : undefined }}>
|
||||||
{/* For lastInGroup check, the placeholder must be placed above all children in the dom */}
|
{/* For lastInGroup check, the placeholder must be placed above all children in the dom */}
|
||||||
<div className={styles.collapseContentPlaceholder}>
|
<div className={styles.collapseContentPlaceholder}>
|
||||||
{childCount === 0 && !collapsed && childrenPlaceholder}
|
{childCount === 0 && !collapsed ? childrenPlaceholder : null}
|
||||||
</div>
|
</div>
|
||||||
<ExplorerTreeContext.Provider value={contextValue}>
|
<ExplorerTreeContext.Provider value={contextValue}>
|
||||||
{collapsed ? null : children}
|
{collapsed ? null : children}
|
||||||
|
|||||||
@@ -220,9 +220,12 @@ export function DocPeekPreview({
|
|||||||
!animating
|
!animating
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const guardService = useService(GuardService);
|
||||||
|
const canAccess = useLiveData(guardService.can$('Doc_Read', docId));
|
||||||
|
|
||||||
// if sync engine has been synced and the page is null, show 404 page.
|
// if sync engine has been synced and the page is null, show 404 page.
|
||||||
if (!doc || !editor) {
|
if (!doc || !editor || !canAccess) {
|
||||||
return loading ? (
|
return loading || canAccess === undefined ? (
|
||||||
<PageDetailSkeleton key="current-page-is-null" />
|
<PageDetailSkeleton key="current-page-is-null" />
|
||||||
) : (
|
) : (
|
||||||
<PageNotFound noPermission />
|
<PageNotFound noPermission />
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ const getRoleName = (t: ReturnType<typeof useI18n>, role?: DocRole) => {
|
|||||||
return t['com.affine.share-menu.option.permission.can-edit']();
|
return t['com.affine.share-menu.option.permission.can-edit']();
|
||||||
case DocRole.Reader:
|
case DocRole.Reader:
|
||||||
return t['com.affine.share-menu.option.permission.can-read']();
|
return t['com.affine.share-menu.option.permission.can-read']();
|
||||||
|
case DocRole.None:
|
||||||
|
return t['com.affine.share-menu.option.permission.no-access']();
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -53,7 +55,9 @@ export const MembersPermission = ({
|
|||||||
[docDefaultRole, t]
|
[docDefaultRole, t]
|
||||||
);
|
);
|
||||||
const showTips =
|
const showTips =
|
||||||
docDefaultRole === DocRole.Reader || docDefaultRole === DocRole.Editor;
|
docDefaultRole === DocRole.Reader ||
|
||||||
|
docDefaultRole === DocRole.Editor ||
|
||||||
|
docDefaultRole === DocRole.None;
|
||||||
const changePermission = useAsyncCallback(
|
const changePermission = useAsyncCallback(
|
||||||
async (docRole: DocRole) => {
|
async (docRole: DocRole) => {
|
||||||
try {
|
try {
|
||||||
@@ -93,6 +97,14 @@ export const MembersPermission = ({
|
|||||||
changePermission(DocRole.Reader);
|
changePermission(DocRole.Reader);
|
||||||
}, [changePermission, hittingPaywall, openPaywallModal]);
|
}, [changePermission, hittingPaywall, openPaywallModal]);
|
||||||
|
|
||||||
|
const selectNone = useCallback(() => {
|
||||||
|
if (hittingPaywall) {
|
||||||
|
openPaywallModal?.();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
changePermission(DocRole.None);
|
||||||
|
}, [changePermission, hittingPaywall, openPaywallModal]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.rowContainerStyle}>
|
<div className={styles.rowContainerStyle}>
|
||||||
<div className={styles.labelStyle}>
|
<div className={styles.labelStyle}>
|
||||||
@@ -141,6 +153,17 @@ export const MembersPermission = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onSelect={selectNone}
|
||||||
|
selected={docDefaultRole === DocRole.None}
|
||||||
|
>
|
||||||
|
<div className={styles.publicItemRowStyle}>
|
||||||
|
<div className={styles.tagContainerStyle}>
|
||||||
|
{t['com.affine.share-menu.option.permission.no-access']()}
|
||||||
|
{hittingPaywall ? <PlanTag /> : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MenuItem>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -6004,6 +6004,10 @@ export function useAFFiNEI18N(): {
|
|||||||
* `Can read`
|
* `Can read`
|
||||||
*/
|
*/
|
||||||
["com.affine.share-menu.option.permission.can-read"](): string;
|
["com.affine.share-menu.option.permission.can-read"](): string;
|
||||||
|
/**
|
||||||
|
* `No access`
|
||||||
|
*/
|
||||||
|
["com.affine.share-menu.option.permission.no-access"](): string;
|
||||||
/**
|
/**
|
||||||
* `Members in workspace`
|
* `Members in workspace`
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1498,6 +1498,7 @@
|
|||||||
"com.affine.share-menu.option.permission.can-manage": "Can manage",
|
"com.affine.share-menu.option.permission.can-manage": "Can manage",
|
||||||
"com.affine.share-menu.option.permission.can-edit": "Can edit",
|
"com.affine.share-menu.option.permission.can-edit": "Can edit",
|
||||||
"com.affine.share-menu.option.permission.can-read": "Can read",
|
"com.affine.share-menu.option.permission.can-read": "Can read",
|
||||||
|
"com.affine.share-menu.option.permission.no-access": "No access",
|
||||||
"com.affine.share-menu.option.permission.label": "Members in workspace",
|
"com.affine.share-menu.option.permission.label": "Members in workspace",
|
||||||
"com.affine.share-menu.option.permission.tips": "Workspace admins and owner automatically have Can manage permissions.",
|
"com.affine.share-menu.option.permission.tips": "Workspace admins and owner automatically have Can manage permissions.",
|
||||||
"com.affine.share-menu.publish-to-web": "Publish to web",
|
"com.affine.share-menu.publish-to-web": "Publish to web",
|
||||||
|
|||||||
Reference in New Issue
Block a user