mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 18:26:05 +08:00
feat(core): add use guard hook (#11180)
Previously, we used `useLiveData(guardService.can$())` to get the guard result, but `guardService.can$()` will request the server to revalidate the permission when calling it, will cause additional network requests when re-render. This pr make a new hook `useGuard` to fix this problem. And the side effect in `can$` is moved to `revalidateCan()` to make that the subscribe method is pure
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Button, Divider, useLitPortalFactory } from '@affine/component';
|
||||
import { useGuard } from '@affine/core/components/guard';
|
||||
import { useEnableAI } from '@affine/core/components/hooks/affine/use-enable-ai';
|
||||
import { DocService } from '@affine/core/modules/doc';
|
||||
import {
|
||||
@@ -7,7 +8,6 @@ import {
|
||||
type Link,
|
||||
} from '@affine/core/modules/doc-link';
|
||||
import { toURLSearchParams } from '@affine/core/modules/navigation';
|
||||
import { GuardService } from '@affine/core/modules/permissions';
|
||||
import { GlobalSessionStateService } from '@affine/core/modules/storage';
|
||||
import { WorkbenchLink } from '@affine/core/modules/workbench';
|
||||
import {
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
LiveData,
|
||||
useFramework,
|
||||
useLiveData,
|
||||
useService,
|
||||
useServices,
|
||||
} from '@toeverything/infra';
|
||||
import {
|
||||
@@ -265,8 +264,7 @@ export const LinkPreview = ({
|
||||
linkGroup: BacklinkGroups;
|
||||
textRendererOptions: TextRendererOptions;
|
||||
}) => {
|
||||
const guardService = useService(GuardService);
|
||||
const canAccess = useLiveData(guardService.can$('Doc_Read', linkGroup.docId));
|
||||
const canAccess = useGuard('Doc_Read', linkGroup.docId);
|
||||
const t = useI18n();
|
||||
|
||||
if (!canAccess) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
MenuSub,
|
||||
} from '@affine/component/ui/menu';
|
||||
import { PageHistoryModal } from '@affine/core/components/affine/page-history-modal';
|
||||
import { useGuard } from '@affine/core/components/guard';
|
||||
import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper';
|
||||
import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud';
|
||||
import { useExportPage } from '@affine/core/components/hooks/affine/use-export-page';
|
||||
@@ -333,8 +334,8 @@ const PageHeaderMenuItem = ({
|
||||
openInAppService?.showOpenInAppPage();
|
||||
}, [openInAppService]);
|
||||
|
||||
const canEdit = useLiveData(guardService.can$('Doc_Update', pageId));
|
||||
const canMoveToTrash = useLiveData(guardService.can$('Doc_Trash', pageId));
|
||||
const canEdit = useGuard('Doc_Update', pageId);
|
||||
const canMoveToTrash = useGuard('Doc_Trash', pageId);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { InlineEditProps } from '@affine/component';
|
||||
import { InlineEdit } from '@affine/component';
|
||||
import { useGuard } from '@affine/core/components/guard';
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { DocService, DocsService } from '@affine/core/modules/doc';
|
||||
import { GuardService } from '@affine/core/modules/permissions';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import { track } from '@affine/track';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
@@ -25,7 +25,6 @@ export const BlocksuiteHeaderTitle = (props: BlockSuiteHeaderTitleProps) => {
|
||||
const workspaceService = useService(WorkspaceService);
|
||||
const isSharedMode = workspaceService.workspace.openOptions.isSharedMode;
|
||||
const docsService = useService(DocsService);
|
||||
const guardService = useService(GuardService);
|
||||
const docService = useService(DocService);
|
||||
const docTitle = useLiveData(docService.doc.record.title$);
|
||||
|
||||
@@ -37,9 +36,7 @@ export const BlocksuiteHeaderTitle = (props: BlockSuiteHeaderTitleProps) => {
|
||||
[docService.doc.id, docsService]
|
||||
);
|
||||
|
||||
const canEdit = useLiveData(
|
||||
guardService.can$('Doc_Update', docService.doc.id)
|
||||
);
|
||||
const canEdit = useGuard('Doc_Update', docService.doc.id);
|
||||
|
||||
return (
|
||||
<InlineEdit
|
||||
|
||||
@@ -5,10 +5,7 @@ import { Modal, useConfirmModal } from '@affine/component/ui/modal';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import {
|
||||
GuardService,
|
||||
WorkspacePermissionService,
|
||||
} from '@affine/core/modules/permissions';
|
||||
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||
import { WorkspaceQuotaService } from '@affine/core/modules/quota';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import { i18nTime, Trans, useI18n } from '@affine/i18n';
|
||||
@@ -35,6 +32,7 @@ import { encodeStateAsUpdate } from 'yjs';
|
||||
import { BlockSuiteEditor } from '../../../blocksuite/block-suite-editor';
|
||||
import { PureEditorModeSwitch } from '../../../blocksuite/block-suite-mode-switch';
|
||||
import { pageHistoryModalAtom } from '../../atoms/page-history';
|
||||
import { useGuard } from '../../guard';
|
||||
import { AffineErrorBoundary } from '../affine-error-boundary';
|
||||
import {
|
||||
historyListGroupByDay,
|
||||
@@ -408,8 +406,6 @@ const PageHistoryManager = ({
|
||||
const workspaceId = docCollection.id;
|
||||
const [activeVersion, setActiveVersion] = useState<string>();
|
||||
|
||||
const guardService = useService(GuardService);
|
||||
|
||||
const pageDocId = useMemo(() => {
|
||||
return docCollection.getDoc(pageId)?.spaceDoc.guid ?? pageId;
|
||||
}, [pageId, docCollection]);
|
||||
@@ -441,7 +437,7 @@ const PageHistoryManager = ({
|
||||
const i18n = useI18n();
|
||||
|
||||
const title = useLiveData(docDisplayMetaService.title$(pageId));
|
||||
const canEdit = useLiveData(guardService.can$('Doc_Update', pageDocId));
|
||||
const canEdit = useGuard('Doc_Update', pageDocId);
|
||||
|
||||
const onConfirmRestore = useCallback(() => {
|
||||
openConfirmModal({
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
} from '@affine/component';
|
||||
import type { DocCustomPropertyInfo } from '@affine/core/modules/db';
|
||||
import { DocsService } from '@affine/core/modules/doc';
|
||||
import { GuardService } from '@affine/core/modules/permissions';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
@@ -17,6 +16,7 @@ import { useLiveData, useService } from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import { type HTMLProps, useCallback, useState } from 'react';
|
||||
|
||||
import { useGuard } from '../../guard';
|
||||
import { DocPropertyIcon } from '../icons/doc-property-icon';
|
||||
import { EditDocPropertyMenuItems } from '../menu/edit-doc-property';
|
||||
import {
|
||||
@@ -38,13 +38,10 @@ const PropertyItem = ({
|
||||
) => void;
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const guardService = useService(GuardService);
|
||||
const workspaceService = useService(WorkspaceService);
|
||||
const docsService = useService(DocsService);
|
||||
const [moreMenuOpen, setMoreMenuOpen] = useState(defaultOpenEditMenu);
|
||||
const canEditPropertyInfo = useLiveData(
|
||||
guardService.can$('Workspace_Properties_Update')
|
||||
);
|
||||
const canEditPropertyInfo = useGuard('Workspace_Properties_Update');
|
||||
|
||||
const typeInfo = isSupportedDocPropertyType(propertyInfo.type)
|
||||
? DocPropertyTypes[propertyInfo.type]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Divider, IconButton, Tooltip } from '@affine/component';
|
||||
import type { DocCustomPropertyInfo } from '@affine/core/modules/db';
|
||||
import { DocsService } from '@affine/core/modules/doc';
|
||||
import { GuardService } from '@affine/core/modules/permissions';
|
||||
import { generateUniqueNameInSequence } from '@affine/core/utils/unique-name';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import track from '@affine/track';
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { useGuard } from '../../guard';
|
||||
import { DocPropertyManager } from '../manager';
|
||||
import {
|
||||
DocPropertyTypes,
|
||||
@@ -29,12 +29,9 @@ export const DocPropertySidebar = () => {
|
||||
const [newPropertyId, setNewPropertyId] = useState<string>();
|
||||
|
||||
const docsService = useService(DocsService);
|
||||
const guardService = useService(GuardService);
|
||||
const propertyList = docsService.propertyList;
|
||||
const properties = useLiveData(propertyList.properties$);
|
||||
const canEditPropertyInfo = useLiveData(
|
||||
guardService.can$('Workspace_Properties_Update')
|
||||
);
|
||||
const canEditPropertyInfo = useGuard('Workspace_Properties_Update');
|
||||
const onAddProperty = useCallback(
|
||||
(option: { type: string; name: string }) => {
|
||||
if (!isSupportedDocPropertyType(option.type)) {
|
||||
|
||||
@@ -16,7 +16,6 @@ import type {
|
||||
DatabaseValueCell,
|
||||
} from '@affine/core/modules/doc-info/types';
|
||||
import { DocIntegrationPropertiesTable } from '@affine/core/modules/integration';
|
||||
import { GuardService } from '@affine/core/modules/permissions';
|
||||
import { ViewService, WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
@@ -32,6 +31,7 @@ import clsx from 'clsx';
|
||||
import type React from 'react';
|
||||
import { forwardRef, useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { useGuard } from '../guard';
|
||||
import { DocPropertyIcon } from './icons/doc-property-icon';
|
||||
import { CreatePropertyMenuItems } from './menu/create-doc-property';
|
||||
import { EditDocPropertyMenuItems } from './menu/edit-doc-property';
|
||||
@@ -290,18 +290,13 @@ const DocWorkspacePropertiesTableBody = forwardRef<
|
||||
const workbenchService = useService(WorkbenchService);
|
||||
const viewService = useServiceOptional(ViewService);
|
||||
const docService = useService(DocService);
|
||||
const guardService = useService(GuardService);
|
||||
const properties = useLiveData(docsService.propertyList.sortedProperties$);
|
||||
const [addMoreCollapsed, setAddMoreCollapsed] = useState(true);
|
||||
|
||||
const [newPropertyId, setNewPropertyId] = useState<string | null>(null);
|
||||
|
||||
const canEditProperty = useLiveData(
|
||||
guardService.can$('Doc_Update', docService.doc.id)
|
||||
);
|
||||
const canEditPropertyInfo = useLiveData(
|
||||
guardService.can$('Workspace_Properties_Update')
|
||||
);
|
||||
const canEditProperty = useGuard('Doc_Update', docService.doc.id);
|
||||
const canEditPropertyInfo = useGuard('Workspace_Properties_Update');
|
||||
|
||||
const handlePropertyAdded = useCallback(
|
||||
(property: DocCustomPropertyInfo) => {
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import {
|
||||
type DocPermissionActions,
|
||||
GuardService,
|
||||
} from '@affine/core/modules/permissions';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import type React from 'react';
|
||||
|
||||
export const DocPermissionGuard = ({
|
||||
docId,
|
||||
children,
|
||||
permission,
|
||||
}: {
|
||||
docId: string;
|
||||
permission: DocPermissionActions;
|
||||
children: (can: boolean | undefined) => React.ReactNode;
|
||||
}) => {
|
||||
const guardService = useService(GuardService);
|
||||
const can = useLiveData(guardService.can$(permission, docId));
|
||||
|
||||
if (typeof children === 'function') {
|
||||
return children(can);
|
||||
}
|
||||
throw new Error('children must be a function');
|
||||
};
|
||||
25
packages/frontend/core/src/components/guard/guard.tsx
Normal file
25
packages/frontend/core/src/components/guard/guard.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
type DocPermissionActions,
|
||||
type WorkspacePermissionActions,
|
||||
} from '@affine/core/modules/permissions';
|
||||
import type React from 'react';
|
||||
|
||||
import { useGuard } from './use-guard';
|
||||
|
||||
export const Guard = <
|
||||
T extends WorkspacePermissionActions | DocPermissionActions,
|
||||
>(
|
||||
props: {
|
||||
permission: T;
|
||||
children: (can: boolean | undefined) => React.ReactNode;
|
||||
} & (T extends DocPermissionActions ? { docId: string } : {})
|
||||
) => {
|
||||
const { permission, children, ...rest } = props;
|
||||
const docId = 'docId' in rest ? [rest.docId] : [];
|
||||
const can = useGuard(permission, ...(docId as any));
|
||||
|
||||
if (typeof children === 'function') {
|
||||
return children(can);
|
||||
}
|
||||
throw new Error('children must be a function');
|
||||
};
|
||||
2
packages/frontend/core/src/components/guard/index.ts
Normal file
2
packages/frontend/core/src/components/guard/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './guard';
|
||||
export * from './use-guard';
|
||||
29
packages/frontend/core/src/components/guard/use-guard.tsx
Normal file
29
packages/frontend/core/src/components/guard/use-guard.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import {
|
||||
type DocPermissionActions,
|
||||
GuardService,
|
||||
type WorkspacePermissionActions,
|
||||
} from '@affine/core/modules/permissions';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
|
||||
export const useGuard = <
|
||||
T extends WorkspacePermissionActions | DocPermissionActions,
|
||||
>(
|
||||
action: T,
|
||||
...args: T extends DocPermissionActions ? [string] : []
|
||||
) => {
|
||||
const guardService = useService(GuardService);
|
||||
useEffect(() => {
|
||||
guardService.revalidateCan(action, ...args);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [action, guardService, ...args]);
|
||||
|
||||
const livedata$ = useMemo(
|
||||
() => guardService.can$(action, ...args),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[action, guardService, ...args]
|
||||
);
|
||||
|
||||
const can = useLiveData(livedata$);
|
||||
return can;
|
||||
};
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
CompatibleFavoriteItemsAdapter,
|
||||
FavoriteService,
|
||||
} from '@affine/core/modules/favorite';
|
||||
import { GuardService } from '@affine/core/modules/permissions';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import type { Collection, DeleteCollectionInfo } from '@affine/env/filter';
|
||||
@@ -41,6 +40,7 @@ import { useCallback, useState } from 'react';
|
||||
|
||||
import { usePageHelper } from '../../blocksuite/block-suite-page-list/utils';
|
||||
import type { CollectionService } from '../../modules/collection';
|
||||
import { useGuard } from '../guard';
|
||||
import { IsFavoriteIcon } from '../pure/icons';
|
||||
import { FavoriteTag } from './components/favorite-tag';
|
||||
import * as styles from './list.css';
|
||||
@@ -68,15 +68,13 @@ const PageOperationCellMenuItem = ({
|
||||
workspaceService,
|
||||
compatibleFavoriteItemsAdapter: favAdapter,
|
||||
workbenchService,
|
||||
guardService,
|
||||
} = useServices({
|
||||
WorkspaceService,
|
||||
CompatibleFavoriteItemsAdapter,
|
||||
WorkbenchService,
|
||||
GuardService,
|
||||
});
|
||||
|
||||
const canMoveToTrash = useLiveData(guardService.can$('Doc_Trash', page.id));
|
||||
const canMoveToTrash = useGuard('Doc_Trash', page.id);
|
||||
const currentWorkspace = workspaceService.workspace;
|
||||
const favourite = useLiveData(favAdapter.isFavorite$(page.id, 'doc'));
|
||||
const workbench = workbenchService.workbench;
|
||||
|
||||
@@ -6,13 +6,13 @@ import type { AffineEditorContainer } from '@affine/core/blocksuite/block-suite-
|
||||
import { EditorOutlineViewer } from '@affine/core/blocksuite/outline-viewer';
|
||||
import { PageAIOnboarding } from '@affine/core/components/affine/ai-onboarding';
|
||||
import { DocPropertySidebar } from '@affine/core/components/doc-properties/sidebar';
|
||||
import { useGuard } from '@affine/core/components/guard';
|
||||
import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper';
|
||||
import { useEnableAI } from '@affine/core/components/hooks/affine/use-enable-ai';
|
||||
import { DocService } from '@affine/core/modules/doc';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||
import { PeekViewService } from '@affine/core/modules/peek-view';
|
||||
import { GuardService } from '@affine/core/modules/permissions';
|
||||
import { RecentDocsService } from '@affine/core/modules/quicksearch';
|
||||
import { ViewService } from '@affine/core/modules/workbench';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
@@ -69,7 +69,6 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
||||
docService,
|
||||
workspaceService,
|
||||
globalContextService,
|
||||
guardService,
|
||||
} = useServices({
|
||||
WorkbenchService,
|
||||
ViewService,
|
||||
@@ -77,7 +76,6 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
||||
DocService,
|
||||
WorkspaceService,
|
||||
GlobalContextService,
|
||||
GuardService,
|
||||
});
|
||||
const workbench = workbenchService.workbench;
|
||||
const editor = editorService.editor;
|
||||
@@ -262,7 +260,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
||||
|
||||
const [dragging, setDragging] = useState(false);
|
||||
|
||||
const canEdit = useLiveData(guardService.can$('Doc_Update', doc.id));
|
||||
const canEdit = useGuard('Doc_Update', doc.id);
|
||||
|
||||
const readonly = !canEdit || isInTrash;
|
||||
|
||||
@@ -368,7 +366,6 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
||||
export const Component = () => {
|
||||
const params = useParams();
|
||||
const recentPages = useService(RecentDocsService);
|
||||
const guardService = useService(GuardService);
|
||||
|
||||
useEffect(() => {
|
||||
if (params.pageId) {
|
||||
@@ -380,9 +377,7 @@ export const Component = () => {
|
||||
}, [params, recentPages]);
|
||||
|
||||
const pageId = params.pageId;
|
||||
const canAccess = useLiveData(
|
||||
pageId ? guardService.can$('Doc_Read', pageId) : undefined
|
||||
);
|
||||
const canAccess = useGuard('Doc_Read', pageId ?? '');
|
||||
|
||||
return pageId ? (
|
||||
<DetailPageWrapper
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
Scrollable,
|
||||
useConfirmModal,
|
||||
} from '@affine/component';
|
||||
import { DocPermissionGuard } from '@affine/core/components/guard/doc-guard';
|
||||
import { Guard } from '@affine/core/components/guard';
|
||||
import { useJournalRouteHelper } from '@affine/core/components/hooks/use-journal';
|
||||
import { MoveToTrash } from '@affine/core/components/page-list';
|
||||
import {
|
||||
@@ -367,10 +367,7 @@ const ConflictList = ({
|
||||
}}
|
||||
items={
|
||||
<>
|
||||
<DocPermissionGuard
|
||||
docId={docRecord.id}
|
||||
permission="Doc_Update"
|
||||
>
|
||||
<Guard docId={docRecord.id} permission="Doc_Update">
|
||||
{canEdit => (
|
||||
<MenuItem
|
||||
prefixIcon={<CalendarXmarkIcon />}
|
||||
@@ -386,19 +383,16 @@ const ConflictList = ({
|
||||
]()}
|
||||
</MenuItem>
|
||||
)}
|
||||
</DocPermissionGuard>
|
||||
</Guard>
|
||||
<MenuSeparator />
|
||||
<DocPermissionGuard
|
||||
docId={docRecord.id}
|
||||
permission="Doc_Trash"
|
||||
>
|
||||
<Guard docId={docRecord.id} permission="Doc_Trash">
|
||||
{canTrash => (
|
||||
<MoveToTrash
|
||||
onSelect={() => handleOpenTrashModal(docRecord)}
|
||||
disabled={!canTrash}
|
||||
/>
|
||||
)}
|
||||
</DocPermissionGuard>
|
||||
</Guard>
|
||||
</>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -11,13 +11,13 @@ import {
|
||||
DocPropertyRow,
|
||||
} from '@affine/core/components/doc-properties';
|
||||
import { CreatePropertyMenuItems } from '@affine/core/components/doc-properties/menu/create-doc-property';
|
||||
import { useGuard } from '@affine/core/components/guard';
|
||||
import { LinksRow } from '@affine/core/desktop/dialogs/doc-info/links-row';
|
||||
import { TimeRow } from '@affine/core/desktop/dialogs/doc-info/time-row';
|
||||
import type { DocCustomPropertyInfo } from '@affine/core/modules/db';
|
||||
import { DocsService } from '@affine/core/modules/doc';
|
||||
import { DocDatabaseBacklinkInfo } from '@affine/core/modules/doc-info';
|
||||
import { DocsSearchService } from '@affine/core/modules/docs-search';
|
||||
import { GuardService } from '@affine/core/modules/permissions';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||
import { LiveData, useLiveData, useServices } from '@toeverything/infra';
|
||||
@@ -31,17 +31,14 @@ export const DocInfoSheet = ({
|
||||
docId: string;
|
||||
defaultOpenProperty?: DefaultOpenProperty;
|
||||
}) => {
|
||||
const { docsSearchService, docsService, guardService } = useServices({
|
||||
const { docsSearchService, docsService } = useServices({
|
||||
DocsSearchService,
|
||||
DocsService,
|
||||
GuardService,
|
||||
});
|
||||
const t = useI18n();
|
||||
|
||||
const canEditPropertyInfo = useLiveData(
|
||||
guardService.can$('Workspace_Properties_Update')
|
||||
);
|
||||
const canEditProperty = useLiveData(guardService.can$('Doc_Update', docId));
|
||||
const canEditPropertyInfo = useGuard('Workspace_Properties_Update');
|
||||
const canEditProperty = useGuard('Doc_Update', docId);
|
||||
const links = useLiveData(
|
||||
useMemo(
|
||||
() => LiveData.from(docsSearchService.watchRefsFrom(docId), null),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Loading } from '@affine/component';
|
||||
import { DocPermissionGuard } from '@affine/core/components/guard/doc-guard';
|
||||
import { Guard } from '@affine/core/components/guard';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { DocsService } from '@affine/core/modules/doc';
|
||||
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
|
||||
@@ -134,7 +134,7 @@ export const ExplorerDocNode = ({
|
||||
operations={finalOperations}
|
||||
data-testid={`explorer-doc-${docId}`}
|
||||
>
|
||||
<DocPermissionGuard docId={docId} permission="Doc_Read">
|
||||
<Guard docId={docId} permission="Doc_Read">
|
||||
{canRead =>
|
||||
canRead
|
||||
? children?.map((child, index) => (
|
||||
@@ -146,8 +146,8 @@ export const ExplorerDocNode = ({
|
||||
))
|
||||
: null
|
||||
}
|
||||
</DocPermissionGuard>
|
||||
<DocPermissionGuard docId={docId} permission="Doc_Update">
|
||||
</Guard>
|
||||
<Guard docId={docId} permission="Doc_Update">
|
||||
{canEdit =>
|
||||
canEdit ? (
|
||||
<AddItemPlaceholder
|
||||
@@ -156,7 +156,7 @@ export const ExplorerDocNode = ({
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
</DocPermissionGuard>
|
||||
</Guard>
|
||||
</ExplorerTreeNode>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
useConfirmModal,
|
||||
} from '@affine/component';
|
||||
import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils';
|
||||
import { DocPermissionGuard } from '@affine/core/components/guard/doc-guard';
|
||||
import { Guard } from '@affine/core/components/guard';
|
||||
import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper';
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
|
||||
@@ -187,7 +187,7 @@ export const useExplorerDocNodeOperationsMenu = (
|
||||
{
|
||||
index: 10,
|
||||
view: (
|
||||
<DocPermissionGuard docId={docId} permission="Doc_Update">
|
||||
<Guard docId={docId} permission="Doc_Update">
|
||||
{canEdit => (
|
||||
<DocRenameSubMenu
|
||||
onConfirm={handleRename}
|
||||
@@ -195,7 +195,7 @@ export const useExplorerDocNodeOperationsMenu = (
|
||||
disabled={!canEdit}
|
||||
/>
|
||||
)}
|
||||
</DocPermissionGuard>
|
||||
</Guard>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -224,7 +224,7 @@ export const useExplorerDocNodeOperationsMenu = (
|
||||
{
|
||||
index: 97,
|
||||
view: (
|
||||
<DocPermissionGuard docId={docId} permission="Doc_Update">
|
||||
<Guard docId={docId} permission="Doc_Update">
|
||||
{canEdit => (
|
||||
<MenuItem
|
||||
prefixIcon={<LinkedPageIcon />}
|
||||
@@ -234,7 +234,7 @@ export const useExplorerDocNodeOperationsMenu = (
|
||||
{t['com.affine.page-operation.add-linked-page']()}
|
||||
</MenuItem>
|
||||
)}
|
||||
</DocPermissionGuard>
|
||||
</Guard>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -273,7 +273,7 @@ export const useExplorerDocNodeOperationsMenu = (
|
||||
{
|
||||
index: 10000,
|
||||
view: (
|
||||
<DocPermissionGuard docId={docId} permission="Doc_Trash">
|
||||
<Guard docId={docId} permission="Doc_Trash">
|
||||
{canMoveToTrash => (
|
||||
<MenuItem
|
||||
type={'danger'}
|
||||
@@ -284,7 +284,7 @@ export const useExplorerDocNodeOperationsMenu = (
|
||||
{t['com.affine.moveToTrash.title']()}
|
||||
</MenuItem>
|
||||
)}
|
||||
</DocPermissionGuard>
|
||||
</Guard>
|
||||
),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
MobileMenuSub,
|
||||
useConfirmModal,
|
||||
} from '@affine/component';
|
||||
import { DocPermissionGuard } from '@affine/core/components/guard/doc-guard';
|
||||
import { Guard } from '@affine/core/components/guard';
|
||||
import { MoveToTrash } from '@affine/core/components/page-list';
|
||||
import {
|
||||
type DocRecord,
|
||||
@@ -59,7 +59,7 @@ export const ResolveConflictOperations = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<DocPermissionGuard docId={docRecord.id} permission="Doc_Update">
|
||||
<Guard docId={docRecord.id} permission="Doc_Update">
|
||||
{canEdit => (
|
||||
<MobileMenuItem
|
||||
prefixIcon={<CalendarXmarkIcon />}
|
||||
@@ -72,15 +72,15 @@ export const ResolveConflictOperations = ({
|
||||
{t['com.affine.page-properties.property.journal-remove']()}
|
||||
</MobileMenuItem>
|
||||
)}
|
||||
</DocPermissionGuard>
|
||||
<DocPermissionGuard docId={docRecord.id} permission="Doc_Trash">
|
||||
</Guard>
|
||||
<Guard docId={docRecord.id} permission="Doc_Trash">
|
||||
{canTrash => (
|
||||
<MoveToTrash
|
||||
onSelect={() => handleOpenTrashModal(docRecord)}
|
||||
disabled={!canTrash}
|
||||
/>
|
||||
)}
|
||||
</DocPermissionGuard>
|
||||
</Guard>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useThemeColorV2 } from '@affine/component';
|
||||
import { PageDetailSkeleton } from '@affine/component/page-detail-skeleton';
|
||||
import type { AffineEditorContainer } from '@affine/core/blocksuite/block-suite-editor';
|
||||
import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary';
|
||||
import { useGuard } from '@affine/core/components/guard';
|
||||
import { useActiveBlocksuiteEditor } from '@affine/core/components/hooks/use-block-suite-editor';
|
||||
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
|
||||
import { PageDetailEditor } from '@affine/core/components/page-detail-editor';
|
||||
@@ -16,7 +17,6 @@ import { EditorService } from '@affine/core/modules/editor';
|
||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||
import { JournalService } from '@affine/core/modules/journal';
|
||||
import { GuardService } from '@affine/core/modules/permissions';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import { ViewService } from '@affine/core/modules/workbench/services/view';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
@@ -55,7 +55,6 @@ const DetailPageImpl = () => {
|
||||
globalContextService,
|
||||
featureFlagService,
|
||||
aIButtonService,
|
||||
guardService,
|
||||
} = useServices({
|
||||
WorkbenchService,
|
||||
ViewService,
|
||||
@@ -65,7 +64,6 @@ const DetailPageImpl = () => {
|
||||
GlobalContextService,
|
||||
FeatureFlagService,
|
||||
AIButtonService,
|
||||
GuardService,
|
||||
});
|
||||
const editor = editorService.editor;
|
||||
const workspace = workspaceService.workspace;
|
||||
@@ -192,7 +190,7 @@ const DetailPageImpl = () => {
|
||||
[docCollection.id, editor, jumpToPageBlock, openPage, server]
|
||||
);
|
||||
|
||||
const canEdit = useLiveData(guardService.can$('Doc_Update', doc.id));
|
||||
const canEdit = useGuard('Doc_Update', doc.id);
|
||||
|
||||
const readonly =
|
||||
!canEdit ||
|
||||
@@ -254,8 +252,7 @@ const MobileDetailPage = ({
|
||||
const [showTitle, setShowTitle] = useState(checkShowTitle);
|
||||
const title = useLiveData(docDisplayMetaService.title$(pageId));
|
||||
|
||||
const guardService = useService(GuardService);
|
||||
const canAccess = useLiveData(guardService.can$('Doc_Read', pageId));
|
||||
const canAccess = useGuard('Doc_Read', pageId);
|
||||
|
||||
const allJournalDates = useLiveData(journalService.allJournalDates$);
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@ import {
|
||||
MobileMenuItem,
|
||||
} from '@affine/component/ui/menu';
|
||||
import { useFavorite } from '@affine/core/blocksuite/block-suite-header/favorite';
|
||||
import { useGuard } from '@affine/core/components/guard';
|
||||
import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
|
||||
import { DocInfoSheet } from '@affine/core/mobile/components';
|
||||
import { MobileTocMenu } from '@affine/core/mobile/components/toc-menu';
|
||||
import { DocService } from '@affine/core/modules/doc';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import { GuardService } from '@affine/core/modules/permissions';
|
||||
import { ViewService } from '@affine/core/modules/workbench/services/view';
|
||||
import { preventDefault } from '@affine/core/utils';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
@@ -35,8 +35,7 @@ export const PageHeaderMenuButton = () => {
|
||||
const t = useI18n();
|
||||
|
||||
const docId = useService(DocService).doc.id;
|
||||
const guardService = useService(GuardService);
|
||||
const canEdit = useLiveData(guardService.can$('Doc_Update', docId));
|
||||
const canEdit = useGuard('Doc_Update', docId);
|
||||
|
||||
const editorService = useService(EditorService);
|
||||
const editorContainer = useLiveData(editorService.editor.editorContainer$);
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
toast,
|
||||
Tooltip,
|
||||
} from '@affine/component';
|
||||
import { DocPermissionGuard } from '@affine/core/components/guard/doc-guard';
|
||||
import { Guard } from '@affine/core/components/guard';
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { DocsService } from '@affine/core/modules/doc';
|
||||
@@ -274,7 +274,7 @@ export const ExplorerDocNode = ({
|
||||
dropEffect={handleDropEffectOnDoc}
|
||||
data-testid={`explorer-doc-${docId}`}
|
||||
>
|
||||
<DocPermissionGuard docId={docId} permission="Doc_Read">
|
||||
<Guard docId={docId} permission="Doc_Read">
|
||||
{canRead =>
|
||||
canRead
|
||||
? children?.map((child, index) => (
|
||||
@@ -291,7 +291,7 @@ export const ExplorerDocNode = ({
|
||||
))
|
||||
: null
|
||||
}
|
||||
</DocPermissionGuard>
|
||||
</Guard>
|
||||
</ExplorerTreeNode>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
useConfirmModal,
|
||||
} from '@affine/component';
|
||||
import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils';
|
||||
import { DocPermissionGuard } from '@affine/core/components/guard/doc-guard';
|
||||
import { Guard } from '@affine/core/components/guard';
|
||||
import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper';
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
|
||||
@@ -175,7 +175,7 @@ export const useExplorerDocNodeOperations = (
|
||||
{
|
||||
index: 99,
|
||||
view: (
|
||||
<DocPermissionGuard docId={docId} permission="Doc_Update">
|
||||
<Guard docId={docId} permission="Doc_Update">
|
||||
{canEdit => (
|
||||
<MenuItem
|
||||
prefixIcon={<LinkedPageIcon />}
|
||||
@@ -185,7 +185,7 @@ export const useExplorerDocNodeOperations = (
|
||||
{t['com.affine.page-operation.add-linked-page']()}
|
||||
</MenuItem>
|
||||
)}
|
||||
</DocPermissionGuard>
|
||||
</Guard>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -239,7 +239,7 @@ export const useExplorerDocNodeOperations = (
|
||||
{
|
||||
index: 10000,
|
||||
view: (
|
||||
<DocPermissionGuard docId={docId} permission="Doc_Trash">
|
||||
<Guard docId={docId} permission="Doc_Trash">
|
||||
{canMoveToTrash => (
|
||||
<MenuItem
|
||||
type={'danger'}
|
||||
@@ -250,7 +250,7 @@ export const useExplorerDocNodeOperations = (
|
||||
{t['com.affine.moveToTrash.title']()}
|
||||
</MenuItem>
|
||||
)}
|
||||
</DocPermissionGuard>
|
||||
</Guard>
|
||||
),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
useDropTarget,
|
||||
} from '@affine/component';
|
||||
import { RenameModal } from '@affine/component/rename-modal';
|
||||
import { DocPermissionGuard } from '@affine/core/components/guard/doc-guard';
|
||||
import { Guard } from '@affine/core/components/guard';
|
||||
import { AppSidebarService } from '@affine/core/modules/app-sidebar';
|
||||
import type { DocPermissionActions } from '@affine/core/modules/permissions';
|
||||
import { WorkbenchLink } from '@affine/core/modules/workbench';
|
||||
@@ -285,7 +285,7 @@ export const ExplorerTreeNode = ({
|
||||
? {
|
||||
index: 0,
|
||||
view: renameableGuard ? (
|
||||
<DocPermissionGuard
|
||||
<Guard
|
||||
permission={renameableGuard.action}
|
||||
docId={renameableGuard.docId}
|
||||
>
|
||||
@@ -300,7 +300,7 @@ export const ExplorerTreeNode = ({
|
||||
{t['com.affine.menu.rename']()}
|
||||
</MenuItem>
|
||||
)}
|
||||
</DocPermissionGuard>
|
||||
</Guard>
|
||||
) : (
|
||||
<MenuItem
|
||||
key={'explorer-tree-rename'}
|
||||
|
||||
@@ -4,9 +4,9 @@ import { AIProvider } from '@affine/core/blocksuite/ai';
|
||||
import type { AffineEditorContainer } from '@affine/core/blocksuite/block-suite-editor';
|
||||
import { EditorOutlineViewer } from '@affine/core/blocksuite/outline-viewer';
|
||||
import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary';
|
||||
import { useGuard } from '@affine/core/components/guard';
|
||||
import { PageNotFound } from '@affine/core/desktop/pages/404';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import { GuardService } from '@affine/core/modules/permissions';
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/affine/block-std/gfx';
|
||||
import { DisposableGroup } from '@blocksuite/affine/global/disposable';
|
||||
@@ -85,7 +85,6 @@ function DocPeekPreviewEditor({
|
||||
const defaultOpenProperty = useLiveData(editor.defaultOpenProperty$);
|
||||
const workbench = useService(WorkbenchService).workbench;
|
||||
const peekView = useService(PeekViewService).peekView;
|
||||
const guardService = useService(GuardService);
|
||||
const editorElement = useLiveData(editor.editorContainer$);
|
||||
|
||||
const isInTrash = useLiveData(doc.record.trash$);
|
||||
@@ -151,7 +150,7 @@ function DocPeekPreviewEditor({
|
||||
peekView.close();
|
||||
}, [doc, peekView, workbench]);
|
||||
|
||||
const canEdit = useLiveData(guardService.can$('Doc_Update', doc.id));
|
||||
const canEdit = useGuard('Doc_Update', doc.id);
|
||||
|
||||
const readonly = !canEdit || isInTrash;
|
||||
|
||||
@@ -220,8 +219,7 @@ export function DocPeekPreview({
|
||||
!animating
|
||||
);
|
||||
|
||||
const guardService = useService(GuardService);
|
||||
const canAccess = useLiveData(guardService.can$('Doc_Read', docId));
|
||||
const canAccess = useGuard('Doc_Read', docId);
|
||||
|
||||
// if sync engine has been synced and the page is null, show 404 page.
|
||||
if (!doc || !editor || !canAccess) {
|
||||
|
||||
@@ -66,17 +66,6 @@ export class GuardService extends Service {
|
||||
const docId = args[0];
|
||||
return LiveData.from(
|
||||
new Observable(subscriber => {
|
||||
// revalidate permission
|
||||
if (docId) {
|
||||
this.revalidateDocPermission(docId);
|
||||
} else {
|
||||
this.revalidateWorkspacePermission();
|
||||
}
|
||||
// revalidate workspace permission if it's not initialized
|
||||
if (this.isAdmin$.value === null) {
|
||||
this.workspacePermissionService.permission.revalidate();
|
||||
}
|
||||
|
||||
let prev: boolean | undefined = undefined;
|
||||
|
||||
const subscription = combineLatest([
|
||||
@@ -128,6 +117,29 @@ export class GuardService extends Service {
|
||||
return permissions[action as keyof typeof permissions] ?? false;
|
||||
}
|
||||
|
||||
revalidateCan<T extends WorkspacePermissionActions | DocPermissionActions>(
|
||||
_action: T,
|
||||
...args: T extends DocPermissionActions ? [string] : []
|
||||
) {
|
||||
// revalidate workspace permission if it's not initialized
|
||||
if (this.isAdmin$.value === null) {
|
||||
this.workspacePermissionService.permission.revalidate();
|
||||
}
|
||||
|
||||
if (this.isAdmin$.value === true) {
|
||||
// if the user is admin, the permission is always true
|
||||
return;
|
||||
}
|
||||
|
||||
const docId = args[0];
|
||||
// revalidate permission
|
||||
if (docId) {
|
||||
this.revalidateDocPermission(docId);
|
||||
} else {
|
||||
this.revalidateWorkspacePermission();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly revalidateWorkspacePermission = effect(
|
||||
exhaustMapWithTrailing(() =>
|
||||
fromPromise(() => this.guardStore.getWorkspacePermissions()).pipe(
|
||||
|
||||
@@ -8,12 +8,12 @@ import {
|
||||
Tooltip,
|
||||
useConfirmModal,
|
||||
} from '@affine/component';
|
||||
import { useGuard } from '@affine/core/components/guard';
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { DocService } from '@affine/core/modules/doc';
|
||||
import {
|
||||
DocGrantedUsersService,
|
||||
type GrantedUser,
|
||||
GuardService,
|
||||
WorkspacePermissionService,
|
||||
} from '@affine/core/modules/permissions';
|
||||
import { UserFriendlyError } from '@affine/error';
|
||||
@@ -131,7 +131,6 @@ const Options = ({
|
||||
const t = useI18n();
|
||||
const docGrantedUsersService = useService(DocGrantedUsersService);
|
||||
const docService = useService(DocService);
|
||||
const guardService = useService(GuardService);
|
||||
const workspacePermissionService = useService(WorkspacePermissionService);
|
||||
const isWorkspaceOwner = useLiveData(
|
||||
workspacePermissionService.permission.isOwner$
|
||||
@@ -140,11 +139,8 @@ const Options = ({
|
||||
const { openConfirmModal } = useConfirmModal();
|
||||
|
||||
const canTransferOwner =
|
||||
useLiveData(guardService.can$('Doc_TransferOwner', docService.doc.id)) &&
|
||||
!!isWorkspaceOwner;
|
||||
const canManageUsers = useLiveData(
|
||||
guardService.can$('Doc_Users_Manage', docService.doc.id)
|
||||
);
|
||||
useGuard('Doc_TransferOwner', docService.doc.id) && !!isWorkspaceOwner;
|
||||
const canManageUsers = useGuard('Doc_Users_Manage', docService.doc.id);
|
||||
|
||||
const updateUserRole = useCallback(
|
||||
async (userId: string, role: DocRole) => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Skeleton } from '@affine/component';
|
||||
import { useGuard } from '@affine/core/components/guard';
|
||||
import { DocService } from '@affine/core/modules/doc';
|
||||
import {
|
||||
DocGrantedUsersService,
|
||||
type GrantedUser,
|
||||
GuardService,
|
||||
} from '@affine/core/modules/permissions';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { ArrowLeftBigIcon } from '@blocksuite/icons/rc';
|
||||
@@ -33,11 +33,8 @@ export const MemberManagement = ({
|
||||
docGrantedUsersService.grantedUserCount$
|
||||
);
|
||||
const docService = useService(DocService);
|
||||
const guardService = useService(GuardService);
|
||||
|
||||
const canManageUsers = useLiveData(
|
||||
guardService.can$('Doc_Users_Manage', docService.doc.id)
|
||||
);
|
||||
const canManageUsers = useGuard('Doc_Users_Manage', docService.doc.id);
|
||||
|
||||
const t = useI18n();
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Divider, Skeleton } from '@affine/component';
|
||||
import { Button } from '@affine/component/ui/button';
|
||||
import { useGuard } from '@affine/core/components/guard';
|
||||
import { ServerService } from '@affine/core/modules/cloud';
|
||||
import { DocService } from '@affine/core/modules/doc';
|
||||
import { GuardService } from '@affine/core/modules/permissions';
|
||||
import { ShareInfoService } from '@affine/core/modules/share-doc';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
@@ -64,15 +64,10 @@ export const AFFiNESharePage = (
|
||||
const shareInfoService = useService(ShareInfoService);
|
||||
const serverService = useService(ServerService);
|
||||
const docService = useService(DocService);
|
||||
const guardService = useService(GuardService);
|
||||
|
||||
const canManageUsers = useLiveData(
|
||||
guardService.can$('Doc_Users_Manage', docService.doc.id)
|
||||
);
|
||||
const canManageUsers = useGuard('Doc_Users_Manage', docService.doc.id);
|
||||
|
||||
const canPublish = useLiveData(
|
||||
guardService.can$('Doc_Publish', docService.doc.id)
|
||||
);
|
||||
const canPublish = useGuard('Doc_Publish', docService.doc.id);
|
||||
|
||||
useEffect(() => {
|
||||
shareInfoService.shareInfo.revalidate();
|
||||
|
||||
Reference in New Issue
Block a user