fix(core): remove openInfoModalAtom to avoid multiple modal opened in split-view (#8329)

close AF-1403
This commit is contained in:
CatsJuice
2024-09-23 03:51:48 +00:00
parent 35e232c61c
commit f9e0c1e57b
14 changed files with 142 additions and 117 deletions

View File

@@ -4,9 +4,15 @@ import {
Modal, Modal,
Scrollable, Scrollable,
} from '@affine/component'; } from '@affine/component';
import { DocInfoService } from '@affine/core/modules/doc-info';
import { DocsSearchService } from '@affine/core/modules/docs-search'; import { DocsSearchService } from '@affine/core/modules/docs-search';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { LiveData, useLiveData, useServices } from '@toeverything/infra'; import {
LiveData,
useLiveData,
useService,
useServices,
} from '@toeverything/infra';
import { Suspense, useCallback, useContext, useMemo, useRef } from 'react'; import { Suspense, useCallback, useContext, useMemo, useRef } from 'react';
import { BlocksuiteHeaderTitle } from '../../../blocksuite/block-suite-header/title'; import { BlocksuiteHeaderTitle } from '../../../blocksuite/block-suite-header/title';
@@ -22,20 +28,23 @@ import { LinksRow } from './links-row';
import { TagsRow } from './tags-row'; import { TagsRow } from './tags-row';
import { TimeRow } from './time-row'; import { TimeRow } from './time-row';
export const InfoModal = ({ export const InfoModal = () => {
open, const modal = useService(DocInfoService).modal;
onOpenChange, const docId = useLiveData(modal.docId$);
docId,
}: { if (!docId) return null;
open: boolean;
onOpenChange: (open: boolean) => void; return <InfoModalOpened docId={docId} />;
docId: string; };
}) => {
const InfoModalOpened = ({ docId }: { docId: string }) => {
const modal = useService(DocInfoService).modal;
const titleInputHandleRef = useRef<InlineEditHandle>(null); const titleInputHandleRef = useRef<InlineEditHandle>(null);
const manager = usePagePropertiesManager(docId); const manager = usePagePropertiesManager(docId ?? '');
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
onOpenChange(false); modal.close();
}, [onOpenChange]); }, [modal]);
if (!manager.page || manager.readonly) { if (!manager.page || manager.readonly) {
return null; return null;
@@ -46,8 +55,8 @@ export const InfoModal = ({
contentOptions={{ contentOptions={{
className: styles.container, className: styles.container,
}} }}
open={open} open
onOpenChange={onOpenChange} onOpenChange={v => modal.onOpenChange(v)}
withoutCloseButton withoutCloseButton
> >
<Scrollable.Root> <Scrollable.Root>

View File

@@ -12,7 +12,6 @@ export const openQuotaModalAtom = atom(false);
export const openStarAFFiNEModalAtom = atom(false); export const openStarAFFiNEModalAtom = atom(false);
export const openIssueFeedbackModalAtom = atom(false); export const openIssueFeedbackModalAtom = atom(false);
export const openHistoryTipsModalAtom = atom(false); export const openHistoryTipsModalAtom = atom(false);
export const openInfoModalAtom = atom(false);
export const rightSidebarWidthAtom = atom(320); export const rightSidebarWidthAtom = atom(320);

View File

@@ -1,19 +1,19 @@
import { IconButton } from '@affine/component'; import { IconButton } from '@affine/component';
import { openInfoModalAtom } from '@affine/core/components/atoms'; import { DocInfoService } from '@affine/core/modules/doc-info';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { track } from '@affine/track'; import { track } from '@affine/track';
import { InformationIcon } from '@blocksuite/icons/rc'; import { InformationIcon } from '@blocksuite/icons/rc';
import { useSetAtom } from 'jotai'; import { useService } from '@toeverything/infra';
import { useCallback } from 'react'; import { useCallback } from 'react';
export const InfoButton = () => { export const InfoButton = ({ docId }: { docId: string }) => {
const setOpenInfoModal = useSetAtom(openInfoModalAtom); const modal = useService(DocInfoService).modal;
const t = useI18n(); const t = useI18n();
const onOpenInfoModal = useCallback(() => { const onOpenInfoModal = useCallback(() => {
track.$.header.actions.openDocInfo(); track.$.header.actions.openDocInfo();
setOpenInfoModal(true); modal.open(docId);
}, [setOpenInfoModal]); }, [docId, modal]);
return ( return (
<IconButton <IconButton

View File

@@ -7,10 +7,7 @@ import {
} from '@affine/component/ui/menu'; } from '@affine/component/ui/menu';
import { PageHistoryModal } from '@affine/core/components/affine/page-history-modal'; import { PageHistoryModal } from '@affine/core/components/affine/page-history-modal';
import { ShareMenuContent } from '@affine/core/components/affine/share-page-modal/share-menu'; import { ShareMenuContent } from '@affine/core/components/affine/share-page-modal/share-menu';
import { import { openHistoryTipsModalAtom } from '@affine/core/components/atoms';
openHistoryTipsModalAtom,
openInfoModalAtom,
} from '@affine/core/components/atoms';
import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper'; 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 { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud';
import { useExportPage } from '@affine/core/components/hooks/affine/use-export-page'; import { useExportPage } from '@affine/core/components/hooks/affine/use-export-page';
@@ -20,6 +17,7 @@ import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite-
import { Export, MoveToTrash } from '@affine/core/components/page-list'; import { Export, MoveToTrash } from '@affine/core/components/page-list';
import { IsFavoriteIcon } from '@affine/core/components/pure/icons'; import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
import { useDetailPageHeaderResponsive } from '@affine/core/desktop/pages/workspace/detail-page/use-header-responsive'; import { useDetailPageHeaderResponsive } from '@affine/core/desktop/pages/workspace/detail-page/use-header-responsive';
import { DocInfoService } from '@affine/core/modules/doc-info';
import { EditorService } from '@affine/core/modules/editor'; import { EditorService } from '@affine/core/modules/editor';
import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkbenchService } from '@affine/core/modules/workbench';
import { ViewService } from '@affine/core/modules/workbench/services/view'; import { ViewService } from '@affine/core/modules/workbench/services/view';
@@ -117,11 +115,11 @@ export const PageHeaderMenuButton = ({
return setOpenHistoryTipsModal(true); return setOpenHistoryTipsModal(true);
}, [setOpenHistoryTipsModal, workspace.flavour]); }, [setOpenHistoryTipsModal, workspace.flavour]);
const setOpenInfoModal = useSetAtom(openInfoModalAtom); const docInfoModal = useService(DocInfoService).modal;
const openInfoModal = useCallback(() => { const openInfoModal = useCallback(() => {
track.$.header.pageInfo.open(); track.$.header.pageInfo.open();
setOpenInfoModal(true); docInfoModal.open(pageId);
}, [setOpenInfoModal]); }, [docInfoModal, pageId]);
const handleOpenInNewTab = useCallback(() => { const handleOpenInNewTab = useCallback(() => {
workbench.openDoc(pageId, { workbench.openDoc(pageId, {

View File

@@ -3,7 +3,7 @@ import {
PreconditionStrategy, PreconditionStrategy,
registerAffineCommand, registerAffineCommand,
} from '@affine/core/commands'; } from '@affine/core/commands';
import { openInfoModalAtom } from '@affine/core/components/atoms'; import { DocInfoService } from '@affine/core/modules/doc-info';
import type { Editor } from '@affine/core/modules/editor'; import type { Editor } from '@affine/core/modules/editor';
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties';
import { WorkspaceFlavour } from '@affine/env/workspace'; import { WorkspaceFlavour } from '@affine/env/workspace';
@@ -36,7 +36,7 @@ export function useRegisterBlocksuiteEditorCommands(editor: Editor) {
const trash = useLiveData(doc.trash$); const trash = useLiveData(doc.trash$);
const setPageHistoryModalState = useSetAtom(pageHistoryModalAtom); const setPageHistoryModalState = useSetAtom(pageHistoryModalAtom);
const setInfoModalState = useSetAtom(openInfoModalAtom); const docInfoModal = useService(DocInfoService).modal;
const openHistoryModal = useCallback(() => { const openHistoryModal = useCallback(() => {
setPageHistoryModalState(() => ({ setPageHistoryModalState(() => ({
@@ -46,8 +46,8 @@ export function useRegisterBlocksuiteEditorCommands(editor: Editor) {
}, [docId, setPageHistoryModalState]); }, [docId, setPageHistoryModalState]);
const openInfoModal = useCallback(() => { const openInfoModal = useCallback(() => {
setInfoModalState(true); docInfoModal.open(docId);
}, [setInfoModalState]); }, [docId, docInfoModal]);
const { duplicate } = useBlockSuiteMetaHelper(); const { duplicate } = useBlockSuiteMetaHelper();
const exportHandler = useExportPage(); const exportHandler = useExportPage();

View File

@@ -8,6 +8,7 @@ import {
import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper'; import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper';
import { useTrashModalHelper } from '@affine/core/components/hooks/affine/use-trash-modal-helper'; import { useTrashModalHelper } from '@affine/core/components/hooks/affine/use-trash-modal-helper';
import { useCatchEventCallback } from '@affine/core/components/hooks/use-catch-event-hook'; import { useCatchEventCallback } from '@affine/core/components/hooks/use-catch-event-hook';
import { DocInfoService } from '@affine/core/modules/doc-info';
import { FavoriteService } from '@affine/core/modules/favorite'; import { FavoriteService } from '@affine/core/modules/favorite';
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties';
import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkbenchService } from '@affine/core/modules/workbench';
@@ -32,6 +33,7 @@ import {
import { import {
FeatureFlagService, FeatureFlagService,
useLiveData, useLiveData,
useService,
useServices, useServices,
WorkspaceService, WorkspaceService,
} from '@toeverything/infra'; } from '@toeverything/infra';
@@ -39,7 +41,6 @@ import type { MouseEvent } from 'react';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import type { CollectionService } from '../../modules/collection'; import type { CollectionService } from '../../modules/collection';
import { InfoModal } from '../affine/page-properties';
import { usePageHelper } from '../blocksuite/block-suite-page-list/utils'; import { usePageHelper } from '../blocksuite/block-suite-page-list/utils';
import { IsFavoriteIcon } from '../pure/icons'; import { IsFavoriteIcon } from '../pure/icons';
import { FavoriteTag } from './components/favorite-tag'; import { FavoriteTag } from './components/favorite-tag';
@@ -86,11 +87,11 @@ export const PageOperationCell = ({
const { duplicate } = useBlockSuiteMetaHelper(); const { duplicate } = useBlockSuiteMetaHelper();
const blocksuiteDoc = currentWorkspace.docCollection.getDoc(page.id); const blocksuiteDoc = currentWorkspace.docCollection.getDoc(page.id);
const [openInfoModal, setOpenInfoModal] = useState(false); const docInfoModal = useService(DocInfoService).modal;
const onOpenInfoModal = useCallback(() => { const onOpenInfoModal = useCallback(() => {
track.$.docInfoPanel.$.open(); track.$.docInfoPanel.$.open();
setOpenInfoModal(true); docInfoModal.open(blocksuiteDoc?.id);
}, []); }, [blocksuiteDoc?.id, docInfoModal]);
const onDisablePublicSharing = useCallback(() => { const onDisablePublicSharing = useCallback(() => {
// TODO(@EYHN): implement disable public sharing // TODO(@EYHN): implement disable public sharing
@@ -214,13 +215,6 @@ export const PageOperationCell = ({
</IconButton> </IconButton>
</Menu> </Menu>
</ColWrapper> </ColWrapper>
{blocksuiteDoc ? (
<InfoModal
open={openInfoModal}
onOpenChange={setOpenInfoModal}
docId={blocksuiteDoc.id}
/>
) : null}
</> </>
); );
}; };

View File

@@ -21,6 +21,7 @@ import { AuthModal } from '../affine/auth';
import { AiLoginRequiredModal } from '../affine/auth/ai-login-required'; import { AiLoginRequiredModal } from '../affine/auth/ai-login-required';
import { HistoryTipsModal } from '../affine/history-tips-modal'; import { HistoryTipsModal } from '../affine/history-tips-modal';
import { IssueFeedbackModal } from '../affine/issue-feedback-modal'; import { IssueFeedbackModal } from '../affine/issue-feedback-modal';
import { InfoModal } from '../affine/page-properties/info-modal/info-modal';
import { import {
CloudQuotaModal, CloudQuotaModal,
LocalQuotaModal, LocalQuotaModal,
@@ -126,6 +127,7 @@ export function CurrentWorkspaceModals() {
onOpenChange={onTrashConfirmOpenChange} onOpenChange={onTrashConfirmOpenChange}
titles={deletePageTitles} titles={deletePageTitles}
/> />
<InfoModal />
</> </>
); );
} }

View File

@@ -3,8 +3,6 @@ import {
type InlineEditHandle, type InlineEditHandle,
observeResize, observeResize,
} from '@affine/component'; } from '@affine/component';
import { InfoModal } from '@affine/core/components/affine/page-properties';
import { openInfoModalAtom } from '@affine/core/components/atoms';
import { FavoriteButton } from '@affine/core/components/blocksuite/block-suite-header/favorite'; import { FavoriteButton } from '@affine/core/components/blocksuite/block-suite-header/favorite';
import { InfoButton } from '@affine/core/components/blocksuite/block-suite-header/info'; import { InfoButton } from '@affine/core/components/blocksuite/block-suite-header/info';
import { JournalWeekDatePicker } from '@affine/core/components/blocksuite/block-suite-header/journal/date-picker'; import { JournalWeekDatePicker } from '@affine/core/components/blocksuite/block-suite-header/journal/date-picker';
@@ -19,7 +17,7 @@ import { EditorService } from '@affine/core/modules/editor';
import { ViewIcon, ViewTitle } from '@affine/core/modules/workbench'; import { ViewIcon, ViewTitle } from '@affine/core/modules/workbench';
import type { Doc } from '@blocksuite/affine/store'; import type { Doc } from '@blocksuite/affine/store';
import { useLiveData, useService, type Workspace } from '@toeverything/infra'; import { useLiveData, useService, type Workspace } from '@toeverything/infra';
import { useAtom, useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import { forwardRef, useCallback, useEffect, useRef, useState } from 'react'; import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
import { SharePageButton } from '../../../../components/affine/share-page-modal'; import { SharePageButton } from '../../../../components/affine/share-page-modal';
@@ -139,7 +137,7 @@ export function NormalPageHeader({ page, workspace }: PageHeaderProps) {
{hideCollect ? null : ( {hideCollect ? null : (
<> <>
<FavoriteButton pageId={page?.id} /> <FavoriteButton pageId={page?.id} />
<InfoButton /> <InfoButton docId={page.id} />
</> </>
)} )}
<PageHeaderMenuButton <PageHeaderMenuButton
@@ -168,25 +166,15 @@ export function DetailPageHeader(props: PageHeaderProps) {
const { page, workspace } = props; const { page, workspace } = props;
const { isJournal } = useJournalInfoHelper(page.id); const { isJournal } = useJournalInfoHelper(page.id);
const isInTrash = page.meta?.trash; const isInTrash = page.meta?.trash;
const [openInfoModal, setOpenInfoModal] = useAtom(openInfoModalAtom);
useRegisterCopyLinkCommands({ useRegisterCopyLinkCommands({
workspaceMeta: workspace.meta, workspaceMeta: workspace.meta,
docId: page.id, docId: page.id,
}); });
return ( return isJournal && !isInTrash ? (
<> <JournalPageHeader {...props} />
{isJournal && !isInTrash ? ( ) : (
<JournalPageHeader {...props} /> <NormalPageHeader {...props} />
) : (
<NormalPageHeader {...props} />
)}
<InfoModal
open={openInfoModal}
onOpenChange={setOpenInfoModal}
docId={page.id}
/>
</>
); );
} }

View File

@@ -2,6 +2,7 @@ import { NotificationCenter } from '@affine/component';
import { AiLoginRequiredModal } from '@affine/core/components/affine/auth/ai-login-required'; import { AiLoginRequiredModal } from '@affine/core/components/affine/auth/ai-login-required';
import { HistoryTipsModal } from '@affine/core/components/affine/history-tips-modal'; import { HistoryTipsModal } from '@affine/core/components/affine/history-tips-modal';
import { IssueFeedbackModal } from '@affine/core/components/affine/issue-feedback-modal'; import { IssueFeedbackModal } from '@affine/core/components/affine/issue-feedback-modal';
import { InfoModal } from '@affine/core/components/affine/page-properties/info-modal/info-modal';
import { import {
CloudQuotaModal, CloudQuotaModal,
LocalQuotaModal, LocalQuotaModal,
@@ -57,6 +58,7 @@ export function MobileCurrentWorkspaceModals() {
onOpenChange={onTrashConfirmOpenChange} onOpenChange={onTrashConfirmOpenChange}
titles={deletePageTitles} titles={deletePageTitles}
/> />
<InfoModal />
</> </>
); );
} }

View File

@@ -0,0 +1,22 @@
import { Entity, LiveData } from '@toeverything/infra';
export class DocInfoModal extends Entity {
public readonly docId$ = new LiveData<string | null>(null);
public readonly open$ = LiveData.computed(get => !!get(this.docId$));
public open(docId?: string) {
if (docId) {
this.docId$.next(docId);
} else {
this.docId$.next(null);
}
}
public close() {
this.docId$.next(null);
}
public onOpenChange(open: boolean) {
if (!open) this.docId$.next(null);
}
}

View File

@@ -0,0 +1,10 @@
import { type Framework, WorkspaceScope } from '@toeverything/infra';
import { DocInfoModal } from './entities/modal';
import { DocInfoService } from './services/doc-info';
export { DocInfoService };
export function configureDocInfoModule(framework: Framework) {
framework.scope(WorkspaceScope).service(DocInfoService).entity(DocInfoModal);
}

View File

@@ -0,0 +1,7 @@
import { Service } from '@toeverything/infra';
import { DocInfoModal } from '../entities/modal';
export class DocInfoService extends Service {
public readonly modal = this.framework.createEntity(DocInfoModal);
}

View File

@@ -5,9 +5,9 @@ import {
toast, toast,
Tooltip, Tooltip,
} from '@affine/component'; } from '@affine/component';
import { InfoModal } from '@affine/core/components/affine/page-properties';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
import { DocInfoService } from '@affine/core/modules/doc-info';
import { DocsSearchService } from '@affine/core/modules/docs-search'; import { DocsSearchService } from '@affine/core/modules/docs-search';
import type { AffineDNDData } from '@affine/core/types/dnd'; import type { AffineDNDData } from '@affine/core/types/dnd';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
@@ -17,6 +17,7 @@ import {
GlobalContextService, GlobalContextService,
LiveData, LiveData,
useLiveData, useLiveData,
useService,
useServices, useServices,
} from '@toeverything/infra'; } from '@toeverything/infra';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
@@ -175,15 +176,15 @@ export const ExplorerDocNode = ({
[canDrop] [canDrop]
); );
const [enableInfoModal, setEnableInfoModal] = useState(false); const docInfoModal = useService(DocInfoService).modal;
const operations = useExplorerDocNodeOperations( const operations = useExplorerDocNodeOperations(
docId, docId,
useMemo( useMemo(
() => ({ () => ({
openInfoModal: () => setEnableInfoModal(true), openInfoModal: () => docInfoModal.open(docId),
openNodeCollapsed: () => setCollapsed(false), openNodeCollapsed: () => setCollapsed(false),
}), }),
[] [docId, docInfoModal]
) )
); );
@@ -199,57 +200,48 @@ export const ExplorerDocNode = ({
} }
return ( return (
<> <ExplorerTreeNode
<ExplorerTreeNode icon={Icon}
icon={Icon} name={typeof docTitle === 'string' ? docTitle : t[docTitle.key]()}
name={typeof docTitle === 'string' ? docTitle : t[docTitle.key]()} dndData={dndData}
dndData={dndData} onDrop={handleDropOnDoc}
onDrop={handleDropOnDoc} renameable
renameable collapsed={collapsed}
collapsed={collapsed} setCollapsed={setCollapsed}
setCollapsed={setCollapsed} canDrop={handleCanDrop}
canDrop={handleCanDrop} to={`/${docId}`}
to={`/${docId}`} active={active}
active={active} postfix={
postfix={ referencesLoading &&
referencesLoading && !collapsed && (
!collapsed && ( <Tooltip
<Tooltip content={t['com.affine.rootAppSidebar.docs.references-loading']()}
content={t['com.affine.rootAppSidebar.docs.references-loading']()} >
> <div className={styles.loadingIcon}>
<div className={styles.loadingIcon}> <Loading />
<Loading /> </div>
</div> </Tooltip>
</Tooltip> )
) }
} reorderable={reorderable}
reorderable={reorderable} onRename={handleRename}
onRename={handleRename} childrenPlaceholder={<Empty onDrop={handleDropOnPlaceholder} />}
childrenPlaceholder={<Empty onDrop={handleDropOnPlaceholder} />} operations={finalOperations}
operations={finalOperations} dropEffect={handleDropEffectOnDoc}
dropEffect={handleDropEffectOnDoc} data-testid={`explorer-doc-${docId}`}
data-testid={`explorer-doc-${docId}`} >
> {children?.map(child => (
{children?.map(child => ( <ExplorerDocNode
<ExplorerDocNode key={child.docId}
key={child.docId} docId={child.docId}
docId={child.docId} reorderable={false}
reorderable={false} location={{
location={{ at: 'explorer:doc:linked-docs',
at: 'explorer:doc:linked-docs', docId,
docId, }}
}} isLinked
isLinked
/>
))}
</ExplorerTreeNode>
{enableInfoModal && (
<InfoModal
open={enableInfoModal}
onOpenChange={setEnableInfoModal}
docId={docId}
/> />
)} ))}
</> </ExplorerTreeNode>
); );
}; };

View File

@@ -5,6 +5,7 @@ import { configureCloudModule } from './cloud';
import { configureCollectionModule } from './collection'; import { configureCollectionModule } from './collection';
import { configureCreateWorkspaceModule } from './create-workspace'; import { configureCreateWorkspaceModule } from './create-workspace';
import { configureDocDisplayMetaModule } from './doc-display-meta'; import { configureDocDisplayMetaModule } from './doc-display-meta';
import { configureDocInfoModule } from './doc-info';
import { configureDocLinksModule } from './doc-link'; import { configureDocLinksModule } from './doc-link';
import { configureDocsSearchModule } from './docs-search'; import { configureDocsSearchModule } from './docs-search';
import { configureEditorModule } from './editor'; import { configureEditorModule } from './editor';
@@ -55,4 +56,5 @@ export function configureCommonModules(framework: Framework) {
configureImportTemplateModule(framework); configureImportTemplateModule(framework);
configureCreateWorkspaceModule(framework); configureCreateWorkspaceModule(framework);
configureUserspaceModule(framework); configureUserspaceModule(framework);
configureDocInfoModule(framework);
} }