feat(core): guard service (#9816)

This commit is contained in:
EYHN
2025-02-10 07:26:38 +08:00
committed by GitHub
parent 879157b938
commit 92f4f0c2d9
89 changed files with 1520 additions and 522 deletions
@@ -47,10 +47,7 @@ export const DocInfoDialog = ({
className={styles.titleContainer}
data-testid="info-modal-title"
>
<BlocksuiteHeaderTitle
docId={docId}
className={styles.titleStyle}
/>
<BlocksuiteHeaderTitle className={styles.titleStyle} />
</div>
<InfoTable docId={docId} onClose={() => close()} />
</Scrollable.Viewport>
@@ -16,6 +16,7 @@ import type {
DatabaseValueCell,
} from '@affine/core/modules/doc-info/types';
import { DocsSearchService } from '@affine/core/modules/docs-search';
import { GuardService } from '@affine/core/modules/permissions';
import { useI18n } from '@affine/i18n';
import track from '@affine/track';
import { PlusIcon } from '@blocksuite/icons/rc';
@@ -33,10 +34,15 @@ export const InfoTable = ({
onClose: () => void;
}) => {
const t = useI18n();
const { docsSearchService, docsService } = useServices({
const { docsSearchService, docsService, guardService } = useServices({
DocsSearchService,
DocsService,
GuardService,
});
const canEditPropertyInfo = useLiveData(
guardService.can$('Workspace_Properties_Update')
);
const canEditProperty = useLiveData(guardService.can$('Doc_Update', docId));
const [newPropertyId, setNewPropertyId] = useState<string | null>(null);
const properties = useLiveData(docsService.propertyList.sortedProperties$);
const links = useLiveData(
@@ -133,6 +139,8 @@ export const InfoTable = ({
<DocPropertyRow
key={property.id}
propertyInfo={property}
readonly={!canEditProperty}
propertyInfoReadonly={!canEditPropertyInfo}
defaultOpenEditMenu={newPropertyId === property.id}
onChange={value => onPropertyChange(property, value)}
onPropertyInfoChange={(...args) =>
@@ -140,22 +148,33 @@ export const InfoTable = ({
}
/>
))}
<Menu
items={<CreatePropertyMenuItems onCreated={onPropertyAdded} />}
contentOptions={{
onClick(e) {
e.stopPropagation();
},
}}
>
{!canEditPropertyInfo ? (
<Button
disabled
variant="plain"
prefix={<PlusIcon />}
className={styles.addPropertyButton}
>
{t['com.affine.page-properties.add-property']()}
</Button>
</Menu>
) : (
<Menu
items={<CreatePropertyMenuItems onCreated={onPropertyAdded} />}
contentOptions={{
onClick(e) {
e.stopPropagation();
},
}}
>
<Button
variant="plain"
prefix={<PlusIcon />}
className={styles.addPropertyButton}
>
{t['com.affine.page-properties.add-property']()}
</Button>
</Menu>
)}
</PropertyCollapsibleContent>
</PropertyCollapsibleSection>
<Divider size="thinner" />
@@ -5,10 +5,11 @@ import {
VirtualizedPageList,
} from '@affine/core/components/page-list';
import { GlobalContextService } from '@affine/core/modules/global-context';
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
import { WorkspaceService } from '@affine/core/modules/workspace';
import type { Filter } from '@affine/env/filter';
import { useI18n } from '@affine/i18n';
import { useService } from '@toeverything/infra';
import { useLiveData, useService } from '@toeverything/infra';
import { useEffect, useState } from 'react';
import {
@@ -26,8 +27,11 @@ import { AllPageHeader } from './all-page-header';
export const AllPage = () => {
const currentWorkspace = useService(WorkspaceService).workspace;
const globalContext = useService(GlobalContextService).globalContext;
const permissionService = useService(WorkspacePermissionService);
const pageMetas = useBlockSuiteDocMeta(currentWorkspace.docCollection);
const [hideHeaderCreateNew, setHideHeaderCreateNew] = useState(true);
const isAdmin = useLiveData(permissionService.permission.isAdmin$);
const isOwner = useLiveData(permissionService.permission.isOwner$);
const [filters, setFilters] = useState<Filter[]>([]);
const filteredPageMetas = useFilteredPageMetas(pageMetas, {
@@ -65,6 +69,7 @@ export const AllPage = () => {
<FilterContainer filters={filters} onChangeFilters={setFilters} />
{filteredPageMetas.length > 0 ? (
<VirtualizedPageList
disableMultiDelete={!isAdmin && !isOwner}
setHideHeaderCreateNewPage={setHideHeaderCreateNew}
filters={filters}
/>
@@ -4,6 +4,7 @@ import { VirtualizedPageList } from '@affine/core/components/page-list';
import { CollectionService } from '@affine/core/modules/collection';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { GlobalContextService } from '@affine/core/modules/global-context';
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
import { WorkspaceService } from '@affine/core/modules/workspace';
import type { Collection } from '@affine/env/filter';
import { useI18n } from '@affine/i18n';
@@ -30,6 +31,9 @@ export const CollectionDetail = ({
const { workspaceDialogService } = useServices({
WorkspaceDialogService,
});
const permissionService = useService(WorkspacePermissionService);
const isAdmin = useLiveData(permissionService.permission.isAdmin$);
const isOwner = useLiveData(permissionService.permission.isOwner$);
const [hideHeaderCreateNew, setHideHeaderCreateNew] = useState(true);
const handleEditCollection = useCallback(() => {
@@ -50,6 +54,7 @@ export const CollectionDetail = ({
<VirtualizedPageList
collection={collection}
setHideHeaderCreateNewPage={setHideHeaderCreateNew}
disableMultiDelete={!isAdmin && !isOwner}
/>
</ViewBody>
</>
@@ -158,10 +158,7 @@ export function NormalPageHeader({ page, workspace }: PageHeaderProps) {
<ViewTitle title={title} />
<ViewIcon icon={currentMode ?? 'page'} />
<EditorModeSwitch />
<BlocksuiteHeaderTitle
docId={page.id}
inputHandleRef={titleInputHandleRef}
/>
<BlocksuiteHeaderTitle inputHandleRef={titleInputHandleRef} />
<TemplateMark />
<div className={styles.iconButtonContainer}>
{hideCollect ? null : (
@@ -6,12 +6,12 @@ import { PageAIOnboarding } from '@affine/core/components/affine/ai-onboarding';
import { EditorOutlineViewer } from '@affine/core/components/blocksuite/outline-viewer';
import { DocPropertySidebar } from '@affine/core/components/doc-properties/sidebar';
import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper';
import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite-page-meta';
import { DocService } from '@affine/core/modules/doc';
import { EditorService } from '@affine/core/modules/editor';
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
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';
@@ -73,6 +73,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
workspaceService,
globalContextService,
featureFlagService,
guardService,
} = useServices({
WorkbenchService,
ViewService,
@@ -81,6 +82,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
WorkspaceService,
GlobalContextService,
FeatureFlagService,
GuardService,
});
const workbench = workbenchService.workbench;
const editor = editorService.editor;
@@ -98,7 +100,6 @@ const DetailPageImpl = memo(function DetailPageImpl() {
const isSideBarOpen = useLiveData(workbench.sidebarOpen$);
const { appSettings } = useAppSettingHelper();
const chatPanelRef = useRef<ChatPanel | null>(null);
const { setDocReadonly } = useDocMetaHelper();
const peekView = useService(PeekViewService).peekView;
@@ -149,12 +150,6 @@ const DetailPageImpl = memo(function DetailPageImpl() {
return;
}, [doc, globalContext, isActiveView, mode]);
useEffect(() => {
if ('isMobile' in environment && environment.isMobile) {
setDocReadonly(doc.id, true);
}
}, [doc.id, setDocReadonly]);
useEffect(() => {
if (isActiveView) {
globalContext.isTrashDoc.set(!!isInTrash);
@@ -273,6 +268,10 @@ const DetailPageImpl = memo(function DetailPageImpl() {
const [dragging, setDragging] = useState(false);
const canEdit = useLiveData(guardService.can$('Doc_Update', doc.id));
const readonly = !canEdit || isInTrash;
return (
<FrameworkScope scope={editor.scope}>
<ViewHeader>
@@ -302,7 +301,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
styles.editorContainer
)}
>
<PageDetailEditor onLoad={onLoad} />
<PageDetailEditor onLoad={onLoad} readonly={readonly} />
</Scrollable.Viewport>
<Scrollable.Scrollbar
className={clsx({
@@ -8,6 +8,7 @@ import {
Scrollable,
useConfirmModal,
} from '@affine/component';
import { DocPermissionGuard } from '@affine/core/components/guard/doc-guard';
import { useJournalRouteHelper } from '@affine/core/components/hooks/use-journal';
import { MoveToTrash } from '@affine/core/components/page-list';
import {
@@ -362,22 +363,38 @@ const ConflictList = ({
}}
items={
<>
<MenuItem
prefixIcon={<CalendarXmarkIcon />}
onClick={e => {
e.stopPropagation();
handleRemoveJournalMark(docRecord.id);
}}
data-testid="journal-conflict-remove-mark"
<DocPermissionGuard
docId={docRecord.id}
permission="Doc_Update"
>
{t[
'com.affine.page-properties.property.journal-remove'
]()}
</MenuItem>
{canEdit => (
<MenuItem
prefixIcon={<CalendarXmarkIcon />}
onClick={e => {
e.stopPropagation();
handleRemoveJournalMark(docRecord.id);
}}
data-testid="journal-conflict-remove-mark"
disabled={!canEdit}
>
{t[
'com.affine.page-properties.property.journal-remove'
]()}
</MenuItem>
)}
</DocPermissionGuard>
<MenuSeparator />
<MoveToTrash
onSelect={() => handleOpenTrashModal(docRecord)}
/>
<DocPermissionGuard
docId={docRecord.id}
permission="Doc_Trash"
>
{canTrash => (
<MoveToTrash
onSelect={() => handleOpenTrashModal(docRecord)}
disabled={!canTrash}
/>
)}
</DocPermissionGuard>
</>
}
>
@@ -6,7 +6,6 @@ import type { DocMode } from '@blocksuite/affine/blocks';
import * as styles from './share-header.css';
export function ShareHeader({
pageId,
publishMode,
isTemplate,
templateName,
@@ -21,7 +20,7 @@ export function ShareHeader({
return (
<div className={styles.header}>
<EditorModeSwitch />
<BlocksuiteHeaderTitle docId={pageId} />
<BlocksuiteHeaderTitle />
<div className={styles.spacer} />
<ShareHeaderRightItem
publishMode={publishMode}
@@ -260,7 +260,7 @@ const SharePageInner = ({
styles.editorContainer
)}
>
<PageDetailEditor onLoad={onEditorLoad} />
<PageDetailEditor onLoad={onEditorLoad} readonly />
{publishMode === 'page' && !BUILD_CONFIG.isElectron ? (
<ShareFooter />
) : null}
@@ -4,6 +4,7 @@ import {
VirtualizedPageList,
} from '@affine/core/components/page-list';
import { GlobalContextService } from '@affine/core/modules/global-context';
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
import { TagService } from '@affine/core/modules/tag';
import {
useIsActiveView,
@@ -26,6 +27,9 @@ export const TagDetail = ({ tagId }: { tagId?: string }) => {
const globalContext = useService(GlobalContextService).globalContext;
const currentWorkspace = useService(WorkspaceService).workspace;
const pageMetas = useBlockSuiteDocMeta(currentWorkspace.docCollection);
const permissionService = useService(WorkspacePermissionService);
const isAdmin = useLiveData(permissionService.permission.isAdmin$);
const isOwner = useLiveData(permissionService.permission.isOwner$);
const tagList = useService(TagService).tagList;
const currentTag = useLiveData(tagList.tagByTagId$(tagId));
@@ -72,6 +76,7 @@ export const TagDetail = ({ tagId }: { tagId?: string }) => {
<VirtualizedPageList
tag={currentTag}
listItem={filteredPageMetas}
disableMultiDelete={!isAdmin && !isOwner}
/>
) : (
<EmptyPageList
@@ -5,11 +5,12 @@ import {
} from '@affine/core/components/page-list';
import { Header } from '@affine/core/components/pure/header';
import { GlobalContextService } from '@affine/core/modules/global-context';
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
import { WorkspaceService } from '@affine/core/modules/workspace';
import { useI18n } from '@affine/i18n';
import { assertExists } from '@blocksuite/affine/global/utils';
import { DeleteIcon } from '@blocksuite/icons/rc';
import { useService } from '@toeverything/infra';
import { useLiveData, useService } from '@toeverything/infra';
import { useEffect } from 'react';
import {
@@ -39,6 +40,9 @@ const TrashHeader = () => {
export const TrashPage = () => {
const globalContextService = useService(GlobalContextService);
const currentWorkspace = useService(WorkspaceService).workspace;
const permissionService = useService(WorkspacePermissionService);
const isAdmin = useLiveData(permissionService.permission.isAdmin$);
const isOwner = useLiveData(permissionService.permission.isOwner$);
const docCollection = currentWorkspace.docCollection;
assertExists(docCollection);
@@ -71,7 +75,10 @@ export const TrashPage = () => {
<ViewBody>
<div className={styles.body}>
{filteredPageMetas.length > 0 ? (
<VirtualizedTrashList />
<VirtualizedTrashList
disableMultiDelete={!isAdmin && !isOwner}
disableMultiRestore={!isAdmin && !isOwner}
/>
) : (
<EmptyPageList type="trash" />
)}