fix(core): add toDocSearchParams for better typeschecking doc search params (#11383)

This commit is contained in:
pengx17
2025-04-02 04:46:22 +00:00
parent 1b5df6b75e
commit 08eb248cb4
10 changed files with 41 additions and 19 deletions

View File

@@ -1,3 +1,4 @@
import type { SerializedXYWH } from '@blocksuite/global/gfx';
import { z } from 'zod'; import { z } from 'zod';
export type DocMode = 'edgeless' | 'page'; export type DocMode = 'edgeless' | 'page';
@@ -26,6 +27,14 @@ export const AliasInfoSchema = z
export type AliasInfo = z.infer<typeof AliasInfoSchema>; export type AliasInfo = z.infer<typeof AliasInfoSchema>;
export const SerializedXYWHSchema = z.custom<SerializedXYWH>(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 export const ReferenceParamsSchema = z
.object({ .object({
mode: z.enum(DocModes), mode: z.enum(DocModes),
@@ -33,6 +42,7 @@ export const ReferenceParamsSchema = z
elementIds: z.string().array(), elementIds: z.string().array(),
databaseId: z.string().optional(), databaseId: z.string().optional(),
databaseRowId: z.string().optional(), databaseRowId: z.string().optional(),
xywh: SerializedXYWHSchema.optional(),
}) })
.partial(); .partial();

View File

@@ -7,7 +7,7 @@ import {
DocLinksService, DocLinksService,
type Link, type Link,
} from '@affine/core/modules/doc-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 { GlobalSessionStateService } from '@affine/core/modules/storage';
import { WorkbenchLink } from '@affine/core/modules/workbench'; import { WorkbenchLink } from '@affine/core/modules/workbench';
import { import {
@@ -144,7 +144,7 @@ const usePreviewExtensions = () => {
const pageId = data.pageId; const pageId = data.pageId;
if (!pageId) return <span />; if (!pageId) return <span />;
const params = toURLSearchParams(data.params); const params = toDocSearchParams(data.params);
if (workspaceService.workspace.openOptions.isSharedMode) { if (workspaceService.workspace.openOptions.isSharedMode) {
return ( return (

View File

@@ -18,7 +18,7 @@ import { EditorService } from '@affine/core/modules/editor';
import { EditorSettingService } from '@affine/core/modules/editor-setting'; import { EditorSettingService } from '@affine/core/modules/editor-setting';
import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { FeatureFlagService } from '@affine/core/modules/feature-flag';
import { JournalService } from '@affine/core/modules/journal'; 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 { useInsidePeekView } from '@affine/core/modules/peek-view';
import { PeekViewService } from '@affine/core/modules/peek-view/services/peek-view'; import { PeekViewService } from '@affine/core/modules/peek-view/services/peek-view';
import { MemberSearchService } from '@affine/core/modules/permissions'; import { MemberSearchService } from '@affine/core/modules/permissions';
@@ -121,7 +121,7 @@ const usePatchSpecs = (mode: DocMode) => {
// title alias // title alias
const title = data.title; const title = data.title;
const params = toURLSearchParams(data.params); const params = toDocSearchParams(data.params);
if (workspaceService.workspace.openOptions.isSharedMode) { if (workspaceService.workspace.openOptions.isSharedMode) {
return ( return (

View File

@@ -1,9 +1,10 @@
import { notify } from '@affine/component'; import { notify } from '@affine/component';
import { ServerService } from '@affine/core/modules/cloud'; 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 { copyTextToClipboard } from '@affine/core/utils/clipboard';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { track } from '@affine/track'; import { track } from '@affine/track';
import type { SerializedXYWH } from '@blocksuite/affine/global/gfx';
import { type DocMode } from '@blocksuite/affine/model'; import { type DocMode } from '@blocksuite/affine/model';
import { import {
getBlockSelectionsCommand, getBlockSelectionsCommand,
@@ -25,7 +26,7 @@ export type UseSharingUrl = {
mode?: DocMode; mode?: DocMode;
blockIds?: string[]; blockIds?: string[];
elementIds?: string[]; elementIds?: string[];
xywh?: string; // not needed currently xywh?: SerializedXYWH; // not needed currently
}; };
/** /**
@@ -44,7 +45,7 @@ export const generateUrl = ({
}: UseSharingUrl & { baseUrl: string }) => { }: UseSharingUrl & { baseUrl: string }) => {
try { try {
const url = new URL(`/workspace/${workspaceId}/${pageId}`, baseUrl); 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(); if (search?.size) url.search = search.toString();
return url.toString(); return url.toString();
} catch (err) { } catch (err) {

View File

@@ -1,5 +1,5 @@
import type { SettingTab } from '@affine/core/modules/dialogs/constant'; 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 { getOpenUrlInDesktopAppLink } from '@affine/core/modules/open-in-app';
import type { DocMode } from '@blocksuite/affine/model'; import type { DocMode } from '@blocksuite/affine/model';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
@@ -49,7 +49,7 @@ export function useNavigateHelper() {
elementIds?: string[], elementIds?: string[],
logic: RouteLogic = RouteLogic.PUSH logic: RouteLogic = RouteLogic.PUSH
) => { ) => {
const search = toURLSearchParams({ const search = toDocSearchParams({
mode, mode,
blockIds, blockIds,
elementIds, elementIds,

View File

@@ -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 { IndexerSyncState } from '@affine/nbstore';
import type { ReferenceParams } from '@blocksuite/affine/model'; import type { ReferenceParams } from '@blocksuite/affine/model';
import { fromPromise, LiveData, Service } from '@toeverything/infra'; import { fromPromise, LiveData, Service } from '@toeverything/infra';
@@ -193,7 +193,7 @@ export class DocsSearchService extends Service {
docId: doc.id, docId: doc.id,
params: isEmpty(params) params: isEmpty(params)
? undefined ? undefined
: toURLSearchParams(params), : toDocSearchParams(params),
}; };
}) })
.filter(ref => !!ref); .filter(ref => !!ref);

View File

@@ -2,6 +2,7 @@ export { Navigator } from './entities/navigator';
export { export {
resolveLinkToDoc, resolveLinkToDoc,
resolveRouteLinkMeta, resolveRouteLinkMeta,
toDocSearchParams,
toURLSearchParams, toURLSearchParams,
} from './utils'; } from './utils';
export { NavigationButtons } from './view/navigation-buttons'; export { NavigationButtons } from './view/navigation-buttons';

View File

@@ -189,3 +189,12 @@ export function toURLSearchParams(
.map(([k, v]) => [k, Array.isArray(v) ? v.join(',') : v]) .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);
}

View File

@@ -26,7 +26,7 @@ import {
import { ServerService } from '../../cloud'; import { ServerService } from '../../cloud';
import { WorkspaceDialogService } from '../../dialogs'; import { WorkspaceDialogService } from '../../dialogs';
import { DocsService } from '../../doc/services/docs'; import { DocsService } from '../../doc/services/docs';
import { toURLSearchParams } from '../../navigation'; import { toDocSearchParams } from '../../navigation';
import { WorkbenchService } from '../../workbench'; import { WorkbenchService } from '../../workbench';
import type { import type {
AttachmentPeekViewInfo, AttachmentPeekViewInfo,
@@ -153,7 +153,7 @@ export const DocPeekViewControls = ({
name: t['com.affine.peek-view-controls.copy-link'](), name: t['com.affine.peek-view-controls.copy-link'](),
onClick: async () => { onClick: async () => {
const preferredMode = docsService.list.getPrimaryMode(docRef.docId); const preferredMode = docsService.list.getPrimaryMode(docRef.docId);
const search = toURLSearchParams({ const search = toDocSearchParams({
mode: docRef.mode || preferredMode, mode: docRef.mode || preferredMode,
blockIds: docRef.blockIds, blockIds: docRef.blockIds,
elementIds: docRef.elementIds, elementIds: docRef.elementIds,

View File

@@ -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 { Unreachable } from '@affine/env/constant';
import type { ReferenceParams } from '@blocksuite/affine/model'; import type { ReferenceParams } from '@blocksuite/affine/model';
import { Entity, LiveData } from '@toeverything/infra'; import { Entity, LiveData } from '@toeverything/infra';
@@ -156,10 +156,11 @@ export class Workbench extends Entity {
openDoc( openDoc(
id: id:
| string | string
| ({ docId: string } & ( | ({
| ReferenceParams docId: string;
| Record<string, string | undefined> refreshKey?: string;
)), fromTab?: string;
} & ReferenceParams),
options?: WorkbenchOpenOptions options?: WorkbenchOpenOptions
) { ) {
const isString = typeof id === 'string'; const isString = typeof id === 'string';
@@ -167,7 +168,7 @@ export class Workbench extends Entity {
let query = ''; let query = '';
if (!isString) { if (!isString) {
const search = toURLSearchParams(omit(id, ['docId'])); const search = toDocSearchParams(omit(id, ['docId']));
if (search?.size) { if (search?.size) {
query = `?${search.toString()}`; query = `?${search.toString()}`;
} }