diff --git a/blocksuite/affine/model/src/consts/doc.ts b/blocksuite/affine/model/src/consts/doc.ts index 84b2144217..64542139ec 100644 --- a/blocksuite/affine/model/src/consts/doc.ts +++ b/blocksuite/affine/model/src/consts/doc.ts @@ -1,3 +1,4 @@ +import type { SerializedXYWH } from '@blocksuite/global/gfx'; import { z } from 'zod'; export type DocMode = 'edgeless' | 'page'; @@ -26,6 +27,14 @@ export const AliasInfoSchema = z export type AliasInfo = z.infer; +export const SerializedXYWHSchema = z.custom(val => { + if (typeof val !== 'string') return false; + const match = val.match(/^\[(\d+),(\d+),(\d+),(\d+)\]$/); + if (!match) return false; + // Ensure all numbers are valid + return match.slice(1).every(num => !isNaN(Number(num))); +}, 'Invalid XYWH format. Expected [number,number,number,number]'); + export const ReferenceParamsSchema = z .object({ mode: z.enum(DocModes), @@ -33,6 +42,7 @@ export const ReferenceParamsSchema = z elementIds: z.string().array(), databaseId: z.string().optional(), databaseRowId: z.string().optional(), + xywh: SerializedXYWHSchema.optional(), }) .partial(); diff --git a/packages/frontend/core/src/blocksuite/block-suite-editor/bi-directional-link-panel.tsx b/packages/frontend/core/src/blocksuite/block-suite-editor/bi-directional-link-panel.tsx index eabe7a2bea..e4ace9e2f1 100644 --- a/packages/frontend/core/src/blocksuite/block-suite-editor/bi-directional-link-panel.tsx +++ b/packages/frontend/core/src/blocksuite/block-suite-editor/bi-directional-link-panel.tsx @@ -7,7 +7,7 @@ import { DocLinksService, type Link, } from '@affine/core/modules/doc-link'; -import { toURLSearchParams } from '@affine/core/modules/navigation'; +import { toDocSearchParams } from '@affine/core/modules/navigation/utils'; import { GlobalSessionStateService } from '@affine/core/modules/storage'; import { WorkbenchLink } from '@affine/core/modules/workbench'; import { @@ -144,7 +144,7 @@ const usePreviewExtensions = () => { const pageId = data.pageId; if (!pageId) return ; - const params = toURLSearchParams(data.params); + const params = toDocSearchParams(data.params); if (workspaceService.workspace.openOptions.isSharedMode) { return ( diff --git a/packages/frontend/core/src/blocksuite/block-suite-editor/lit-adaper.tsx b/packages/frontend/core/src/blocksuite/block-suite-editor/lit-adaper.tsx index 59c31ae1bb..39bfd16619 100644 --- a/packages/frontend/core/src/blocksuite/block-suite-editor/lit-adaper.tsx +++ b/packages/frontend/core/src/blocksuite/block-suite-editor/lit-adaper.tsx @@ -18,7 +18,7 @@ import { EditorService } from '@affine/core/modules/editor'; import { EditorSettingService } from '@affine/core/modules/editor-setting'; import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { JournalService } from '@affine/core/modules/journal'; -import { toURLSearchParams } from '@affine/core/modules/navigation'; +import { toDocSearchParams } from '@affine/core/modules/navigation'; import { useInsidePeekView } from '@affine/core/modules/peek-view'; import { PeekViewService } from '@affine/core/modules/peek-view/services/peek-view'; import { MemberSearchService } from '@affine/core/modules/permissions'; @@ -121,7 +121,7 @@ const usePatchSpecs = (mode: DocMode) => { // title alias const title = data.title; - const params = toURLSearchParams(data.params); + const params = toDocSearchParams(data.params); if (workspaceService.workspace.openOptions.isSharedMode) { return ( diff --git a/packages/frontend/core/src/components/hooks/affine/use-share-url.ts b/packages/frontend/core/src/components/hooks/affine/use-share-url.ts index ebb45e5534..9f9c15e9be 100644 --- a/packages/frontend/core/src/components/hooks/affine/use-share-url.ts +++ b/packages/frontend/core/src/components/hooks/affine/use-share-url.ts @@ -1,9 +1,10 @@ import { notify } from '@affine/component'; import { ServerService } from '@affine/core/modules/cloud'; -import { toURLSearchParams } from '@affine/core/modules/navigation'; +import { toDocSearchParams } from '@affine/core/modules/navigation'; import { copyTextToClipboard } from '@affine/core/utils/clipboard'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; +import type { SerializedXYWH } from '@blocksuite/affine/global/gfx'; import { type DocMode } from '@blocksuite/affine/model'; import { getBlockSelectionsCommand, @@ -25,7 +26,7 @@ export type UseSharingUrl = { mode?: DocMode; blockIds?: string[]; elementIds?: string[]; - xywh?: string; // not needed currently + xywh?: SerializedXYWH; // not needed currently }; /** @@ -44,7 +45,7 @@ export const generateUrl = ({ }: UseSharingUrl & { baseUrl: string }) => { try { const url = new URL(`/workspace/${workspaceId}/${pageId}`, baseUrl); - const search = toURLSearchParams({ mode, blockIds, elementIds, xywh }); + const search = toDocSearchParams({ mode, blockIds, elementIds, xywh }); if (search?.size) url.search = search.toString(); return url.toString(); } catch (err) { diff --git a/packages/frontend/core/src/components/hooks/use-navigate-helper.ts b/packages/frontend/core/src/components/hooks/use-navigate-helper.ts index 25e9981b25..cd35c37c46 100644 --- a/packages/frontend/core/src/components/hooks/use-navigate-helper.ts +++ b/packages/frontend/core/src/components/hooks/use-navigate-helper.ts @@ -1,5 +1,5 @@ import type { SettingTab } from '@affine/core/modules/dialogs/constant'; -import { toURLSearchParams } from '@affine/core/modules/navigation'; +import { toDocSearchParams } from '@affine/core/modules/navigation'; import { getOpenUrlInDesktopAppLink } from '@affine/core/modules/open-in-app'; import type { DocMode } from '@blocksuite/affine/model'; import { nanoid } from 'nanoid'; @@ -49,7 +49,7 @@ export function useNavigateHelper() { elementIds?: string[], logic: RouteLogic = RouteLogic.PUSH ) => { - const search = toURLSearchParams({ + const search = toDocSearchParams({ mode, blockIds, elementIds, diff --git a/packages/frontend/core/src/modules/docs-search/services/docs-search.ts b/packages/frontend/core/src/modules/docs-search/services/docs-search.ts index 1cf6e6385d..742c568dd1 100644 --- a/packages/frontend/core/src/modules/docs-search/services/docs-search.ts +++ b/packages/frontend/core/src/modules/docs-search/services/docs-search.ts @@ -1,4 +1,4 @@ -import { toURLSearchParams } from '@affine/core/modules/navigation'; +import { toDocSearchParams } from '@affine/core/modules/navigation'; import type { IndexerSyncState } from '@affine/nbstore'; import type { ReferenceParams } from '@blocksuite/affine/model'; import { fromPromise, LiveData, Service } from '@toeverything/infra'; @@ -193,7 +193,7 @@ export class DocsSearchService extends Service { docId: doc.id, params: isEmpty(params) ? undefined - : toURLSearchParams(params), + : toDocSearchParams(params), }; }) .filter(ref => !!ref); diff --git a/packages/frontend/core/src/modules/navigation/index.ts b/packages/frontend/core/src/modules/navigation/index.ts index 48e0f0a443..3b5f978363 100644 --- a/packages/frontend/core/src/modules/navigation/index.ts +++ b/packages/frontend/core/src/modules/navigation/index.ts @@ -2,6 +2,7 @@ export { Navigator } from './entities/navigator'; export { resolveLinkToDoc, resolveRouteLinkMeta, + toDocSearchParams, toURLSearchParams, } from './utils'; export { NavigationButtons } from './view/navigation-buttons'; diff --git a/packages/frontend/core/src/modules/navigation/utils.ts b/packages/frontend/core/src/modules/navigation/utils.ts index 783a3b8856..2f59d7d7bb 100644 --- a/packages/frontend/core/src/modules/navigation/utils.ts +++ b/packages/frontend/core/src/modules/navigation/utils.ts @@ -189,3 +189,12 @@ export function toURLSearchParams( .map(([k, v]) => [k, Array.isArray(v) ? v.join(',') : v]) ); } + +// a type safe version of toURLSearchParams for doc search params +export function toDocSearchParams( + params?: ReferenceParams & { + refreshKey?: string; + } +) { + return toURLSearchParams(params); +} diff --git a/packages/frontend/core/src/modules/peek-view/view/peek-view-controls.tsx b/packages/frontend/core/src/modules/peek-view/view/peek-view-controls.tsx index 8a6b8dd9e3..ec119908ac 100644 --- a/packages/frontend/core/src/modules/peek-view/view/peek-view-controls.tsx +++ b/packages/frontend/core/src/modules/peek-view/view/peek-view-controls.tsx @@ -26,7 +26,7 @@ import { import { ServerService } from '../../cloud'; import { WorkspaceDialogService } from '../../dialogs'; import { DocsService } from '../../doc/services/docs'; -import { toURLSearchParams } from '../../navigation'; +import { toDocSearchParams } from '../../navigation'; import { WorkbenchService } from '../../workbench'; import type { AttachmentPeekViewInfo, @@ -153,7 +153,7 @@ export const DocPeekViewControls = ({ name: t['com.affine.peek-view-controls.copy-link'](), onClick: async () => { const preferredMode = docsService.list.getPrimaryMode(docRef.docId); - const search = toURLSearchParams({ + const search = toDocSearchParams({ mode: docRef.mode || preferredMode, blockIds: docRef.blockIds, elementIds: docRef.elementIds, diff --git a/packages/frontend/core/src/modules/workbench/entities/workbench.ts b/packages/frontend/core/src/modules/workbench/entities/workbench.ts index d22e42a0f1..60f92b76a4 100644 --- a/packages/frontend/core/src/modules/workbench/entities/workbench.ts +++ b/packages/frontend/core/src/modules/workbench/entities/workbench.ts @@ -1,4 +1,4 @@ -import { toURLSearchParams } from '@affine/core/modules/navigation/utils'; +import { toDocSearchParams } from '@affine/core/modules/navigation/utils'; import { Unreachable } from '@affine/env/constant'; import type { ReferenceParams } from '@blocksuite/affine/model'; import { Entity, LiveData } from '@toeverything/infra'; @@ -156,10 +156,11 @@ export class Workbench extends Entity { openDoc( id: | string - | ({ docId: string } & ( - | ReferenceParams - | Record - )), + | ({ + docId: string; + refreshKey?: string; + fromTab?: string; + } & ReferenceParams), options?: WorkbenchOpenOptions ) { const isString = typeof id === 'string'; @@ -167,7 +168,7 @@ export class Workbench extends Entity { let query = ''; if (!isString) { - const search = toURLSearchParams(omit(id, ['docId'])); + const search = toDocSearchParams(omit(id, ['docId'])); if (search?.size) { query = `?${search.toString()}`; }