mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-18 18:41:52 +08:00
fix(core): shared page mode syncing (#14756)
### Summary This fixes a few inconsistencies in shared page behavior: fixes https://github.com/toeverything/AFFiNE/issues/14751 - shared pages now open in the correct published mode when the URL does not already include ?mode=... - switching between page and edgeless in shared mode now keeps the URL query param in sync - the default Copy Link action now follows the current editor mode - shared viewers can toggle between page and edgeless mode in readonly share pages --- ### What Changed - updated shared page mode resolution to prefer URL mode, with backend publish mode as fallback - added query-param syncing for shared page mode changes - made the default share link copy use: - page link in page mode - edgeless link in edgeless mode - allowed EditorModeSwitch to toggle both ways in shared mode - extracted shared-mode behavior into small hooks to keep share-page.tsx cleaner --- ### Demo https://www.loom.com/share/a287172321fb4fc5b94f7c67a39298a9 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Mode switching between page and edgeless no longer blocked by shared gating; shared pages initialize and respect the resolved editor mode. * Shared page URLs stay in sync with editor mode and copy-link actions include/preserve the selected mode. * **Tests** * Added tests for publish-mode resolution, query-string mode handling, and default share-mode behavior. * **Bug Fixes** * Updated shared-page “not found” UI text to match new messaging. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: DarkSky <25152247+darkskygit@users.noreply.github.com>
This commit is contained in:
@@ -37,6 +37,7 @@ import { PageNotFound } from '../../404';
|
||||
import { ShareFooter } from './share-footer';
|
||||
import { ShareHeader } from './share-header';
|
||||
import * as styles from './share-page.css';
|
||||
import { useSharedModeQuerySync } from './use-shared-mode-query-sync';
|
||||
|
||||
const useUpdateBasename = (workspace: Workspace | null) => {
|
||||
const location = useLocation();
|
||||
@@ -106,7 +107,7 @@ export const SharePage = ({
|
||||
const SharePageInner = ({
|
||||
workspaceId,
|
||||
docId,
|
||||
publishMode = 'page',
|
||||
publishMode,
|
||||
selector,
|
||||
isTemplate,
|
||||
templateName,
|
||||
@@ -128,10 +129,19 @@ const SharePageInner = ({
|
||||
const [noPermission, setNoPermission] = useState(false);
|
||||
const [editorContainer, setActiveBlocksuiteEditor] =
|
||||
useActiveBlocksuiteEditor();
|
||||
const resolvedPublishMode = publishMode ?? null;
|
||||
const currentPublishMode = useSharedModeQuerySync({
|
||||
editor,
|
||||
resolvedPublishMode,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (editor || workspace || page) {
|
||||
return;
|
||||
}
|
||||
|
||||
// create a workspace for share page
|
||||
const { workspace } = workspacesService.open(
|
||||
const { workspace: sharedWorkspace } = workspacesService.open(
|
||||
{
|
||||
metadata: {
|
||||
id: workspaceId,
|
||||
@@ -160,16 +170,16 @@ const SharePageInner = ({
|
||||
}
|
||||
);
|
||||
|
||||
setWorkspace(workspace);
|
||||
setWorkspace(sharedWorkspace);
|
||||
|
||||
workspace.engine.doc
|
||||
.waitForDocLoaded(workspace.id)
|
||||
sharedWorkspace.engine.doc
|
||||
.waitForDocLoaded(sharedWorkspace.id)
|
||||
.then(async () => {
|
||||
const { doc } = workspace.scope.get(DocsService).open(docId);
|
||||
const { doc } = sharedWorkspace.scope.get(DocsService).open(docId);
|
||||
doc.blockSuiteDoc.load();
|
||||
doc.blockSuiteDoc.readonly = true;
|
||||
|
||||
await workspace.engine.doc.waitForDocLoaded(docId);
|
||||
await sharedWorkspace.engine.doc.waitForDocLoaded(docId);
|
||||
|
||||
if (!doc.blockSuiteDoc.root) {
|
||||
throw new Error('Doc is empty');
|
||||
@@ -178,7 +188,7 @@ const SharePageInner = ({
|
||||
setPage(doc);
|
||||
|
||||
const editor = doc.scope.get(EditorsService).createEditor();
|
||||
editor.setMode(publishMode);
|
||||
editor.setMode(resolvedPublishMode ?? doc.getPrimaryMode() ?? 'page');
|
||||
|
||||
if (selector) {
|
||||
editor.setSelector(selector);
|
||||
@@ -192,13 +202,24 @@ const SharePageInner = ({
|
||||
});
|
||||
}, [
|
||||
docId,
|
||||
workspaceId,
|
||||
workspacesService,
|
||||
publishMode,
|
||||
editor,
|
||||
page,
|
||||
resolvedPublishMode,
|
||||
selector,
|
||||
workspaceId,
|
||||
workspace,
|
||||
workspacesService,
|
||||
serverService.server.baseUrl,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
editor.setSelector(selector);
|
||||
}, [editor, selector]);
|
||||
|
||||
const t = useI18n();
|
||||
const pageTitle = useLiveData(page?.title$);
|
||||
const { jumpToPageBlock, openPage } = useNavigateHelper();
|
||||
@@ -244,7 +265,7 @@ const SharePageInner = ({
|
||||
return <PageNotFound noPermission />;
|
||||
}
|
||||
|
||||
if (!workspace || !page || !editor) {
|
||||
if (!workspace || !page || !editor || !currentPublishMode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -252,13 +273,13 @@ const SharePageInner = ({
|
||||
<FrameworkScope scope={workspace.scope}>
|
||||
<FrameworkScope scope={page.scope}>
|
||||
<FrameworkScope scope={editor.scope}>
|
||||
<ViewIcon icon={publishMode === 'page' ? 'doc' : 'edgeless'} />
|
||||
<ViewIcon icon={currentPublishMode === 'page' ? 'doc' : 'edgeless'} />
|
||||
<ViewTitle title={pageTitle ?? t['unnamed']()} />
|
||||
<div className={styles.root}>
|
||||
<div className={styles.mainContainer}>
|
||||
<ShareHeader
|
||||
pageId={page.id}
|
||||
publishMode={publishMode}
|
||||
publishMode={currentPublishMode}
|
||||
isTemplate={isTemplate}
|
||||
templateName={templateName}
|
||||
snapshotUrl={templateSnapshotUrl}
|
||||
@@ -271,7 +292,7 @@ const SharePageInner = ({
|
||||
)}
|
||||
>
|
||||
<PageDetailEditor onLoad={onEditorLoad} readonly />
|
||||
{publishMode === 'page' && !BUILD_CONFIG.isElectron ? (
|
||||
{currentPublishMode === 'page' && !BUILD_CONFIG.isElectron ? (
|
||||
<ShareFooter />
|
||||
) : null}
|
||||
</Scrollable.Viewport>
|
||||
@@ -279,7 +300,7 @@ const SharePageInner = ({
|
||||
</Scrollable.Root>
|
||||
<EditorOutlineViewer
|
||||
editor={editorContainer?.host ?? null}
|
||||
show={publishMode === 'page'}
|
||||
show={currentPublishMode === 'page'}
|
||||
/>
|
||||
{!BUILD_CONFIG.isElectron && <SharePageFooter />}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { PublicDocMode } from '@affine/graphql';
|
||||
import { type DocMode, DocModes } from '@blocksuite/affine/model';
|
||||
|
||||
export const getResolvedPublishMode = (
|
||||
queryMode: DocMode | null,
|
||||
publicMode?: PublicDocMode | null
|
||||
): DocMode => {
|
||||
if (queryMode && DocModes.includes(queryMode)) {
|
||||
return queryMode;
|
||||
}
|
||||
|
||||
return publicMode === PublicDocMode.Edgeless ? 'edgeless' : 'page';
|
||||
};
|
||||
|
||||
export const getSearchWithMode = (search: string, mode: DocMode) => {
|
||||
const searchParams = new URLSearchParams(search);
|
||||
searchParams.set('mode', mode);
|
||||
|
||||
const nextSearch = searchParams.toString();
|
||||
return nextSearch ? `?${nextSearch}` : '';
|
||||
};
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
import type { Editor } from '@affine/core/modules/editor';
|
||||
import type { DocMode } from '@blocksuite/affine/model';
|
||||
import { useLiveData } from '@toeverything/infra';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { getSearchWithMode } from './share-page.utils';
|
||||
|
||||
export const useSharedModeQuerySync = ({
|
||||
editor,
|
||||
resolvedPublishMode,
|
||||
}: {
|
||||
editor: Editor | null;
|
||||
resolvedPublishMode: DocMode | null;
|
||||
}) => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const currentPublishMode = useLiveData(editor?.mode$) ?? resolvedPublishMode;
|
||||
const previousPublishModeRef = useRef<DocMode | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor || !resolvedPublishMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (editor.mode$.value !== resolvedPublishMode) {
|
||||
editor.setMode(resolvedPublishMode);
|
||||
}
|
||||
}, [editor, resolvedPublishMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentPublishMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (previousPublishModeRef.current === null) {
|
||||
previousPublishModeRef.current = currentPublishMode;
|
||||
return;
|
||||
}
|
||||
|
||||
if (previousPublishModeRef.current === currentPublishMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
previousPublishModeRef.current = currentPublishMode;
|
||||
|
||||
const nextSearch = getSearchWithMode(location.search, currentPublishMode);
|
||||
if (nextSearch !== location.search) {
|
||||
navigate(
|
||||
{
|
||||
pathname: location.pathname,
|
||||
search: nextSearch,
|
||||
},
|
||||
{ replace: true }
|
||||
);
|
||||
}
|
||||
}, [currentPublishMode, location.pathname, location.search, navigate]);
|
||||
|
||||
return currentPublishMode;
|
||||
};
|
||||
Reference in New Issue
Block a user