chore: bump blocksuite (#8230)

## 0.17.11

### Patch Changes

- [3c61be5](3c61be5ded): - Refactor drag handle widget
  - Split embed blocks to `@blocksuite/affine-block-embed`
  - Fix latex selected state in edgeless mode
  - Fix unclear naming
  - Fix prototype pollution
  - Fix portal interaction in affine modal
  - Fix paste linked block on edgeless
  - Add scroll anchoring widget
  - Add highlight selection
This commit is contained in:
fundon
2024-09-18 12:11:14 +00:00
parent b73d3b3d55
commit f397815ad1
21 changed files with 424 additions and 300 deletions

View File

@@ -3,8 +3,8 @@
"private": true,
"type": "module",
"devDependencies": {
"@blocksuite/global": "0.17.10",
"@blocksuite/store": "0.17.10",
"@blocksuite/global": "0.17.11",
"@blocksuite/store": "0.17.11",
"vitest": "2.1.0"
},
"exports": {

View File

@@ -14,10 +14,10 @@
"@affine/debug": "workspace:*",
"@affine/env": "workspace:*",
"@affine/templates": "workspace:*",
"@blocksuite/blocks": "0.17.10",
"@blocksuite/global": "0.17.10",
"@blocksuite/presets": "0.17.10",
"@blocksuite/store": "0.17.10",
"@blocksuite/blocks": "0.17.11",
"@blocksuite/global": "0.17.11",
"@blocksuite/presets": "0.17.11",
"@blocksuite/store": "0.17.11",
"@datastructures-js/binary-search-tree": "^5.3.2",
"foxact": "^0.2.33",
"fuse.js": "^7.0.0",
@@ -34,7 +34,7 @@
"devDependencies": {
"@affine-test/fixtures": "workspace:*",
"@affine/templates": "workspace:*",
"@blocksuite/presets": "0.17.10",
"@blocksuite/presets": "0.17.11",
"@testing-library/react": "^16.0.0",
"fake-indexeddb": "^6.0.0",
"react": "^18.2.0",

View File

@@ -28,7 +28,7 @@
"@affine/core": "workspace:*",
"@affine/i18n": "workspace:*",
"@affine/native": "workspace:*",
"@blocksuite/global": "0.17.10",
"@blocksuite/global": "0.17.11",
"@electron-forge/cli": "^7.3.0",
"@electron-forge/core": "^7.3.0",
"@electron-forge/core-utils": "^7.3.0",

View File

@@ -13,7 +13,7 @@
"@affine/component": "workspace:*",
"@affine/core": "workspace:*",
"@affine/i18n": "workspace:*",
"@blocksuite/blocks": "0.17.10",
"@blocksuite/blocks": "0.17.11",
"@blocksuite/icons": "^2.1.67",
"@sentry/react": "^8.0.0",
"react": "^18.2.0",

View File

@@ -63,7 +63,7 @@
"zod": "^3.22.4"
},
"devDependencies": {
"@blocksuite/global": "0.17.10",
"@blocksuite/global": "0.17.11",
"@blocksuite/icons": "2.1.67",
"@chromatic-com/storybook": "^2.0.0",
"@storybook/addon-essentials": "^8.2.9",

View File

@@ -16,13 +16,13 @@
"@affine/i18n": "workspace:*",
"@affine/templates": "workspace:*",
"@affine/track": "workspace:*",
"@blocksuite/block-std": "0.17.10",
"@blocksuite/blocks": "0.17.10",
"@blocksuite/global": "0.17.10",
"@blocksuite/block-std": "0.17.11",
"@blocksuite/blocks": "0.17.11",
"@blocksuite/global": "0.17.11",
"@blocksuite/icons": "2.1.67",
"@blocksuite/inline": "0.17.10",
"@blocksuite/presets": "0.17.10",
"@blocksuite/store": "0.17.10",
"@blocksuite/inline": "0.17.11",
"@blocksuite/presets": "0.17.11",
"@blocksuite/store": "0.17.11",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0",

View File

@@ -34,7 +34,6 @@ import type {
DocMode,
DocModeProvider,
QuickSearchResult,
ReferenceParams,
RootService,
} from '@blocksuite/blocks';
import {
@@ -42,12 +41,10 @@ import {
DocModeExtension,
EdgelessRootBlockComponent,
EmbedLinkedDocBlockComponent,
EmbedOptionProvider,
NotificationExtension,
ParseDocUrlExtension,
PeekViewExtension,
QuickSearchExtension,
QuickSearchProvider,
ReferenceNodeConfigExtension,
} from '@blocksuite/blocks';
import { AIChatBlockSchema } from '@blocksuite/presets';
@@ -62,6 +59,7 @@ import {
import { type TemplateResult } from 'lit';
import { customElement } from 'lit/decorators.js';
import { literal } from 'lit/static-html.js';
import { pick } from 'lodash-es';
export type ReferenceReactRenderer = (
reference: AffineReference
@@ -318,14 +316,13 @@ export function patchQuickSearchService(framework: FrameworkProvider) {
}
if (result.source === 'link') {
const { docId, blockIds, elementIds, mode } = result.payload;
resolve({
docId,
params: {
blockIds,
elementIds,
mode,
},
docId: result.payload.docId,
params: pick(result.payload, [
'mode',
'blockIds',
'elementIds',
]),
});
return;
}
@@ -349,13 +346,8 @@ export function patchQuickSearchService(framework: FrameworkProvider) {
primaryMode: mode,
docProps,
});
track.doc.editor.quickSearch.createDoc({
mode,
});
resolve({
docId: newDoc.id,
});
resolve({ docId: newDoc.id });
return;
}
},
@@ -373,6 +365,7 @@ export function patchQuickSearchService(framework: FrameworkProvider) {
return searchResult;
},
});
const SlashMenuQuickSearchExtension = patchSpecService<RootService>(
'affine:page',
() => {},
@@ -383,51 +376,38 @@ export function patchQuickSearchService(framework: FrameworkProvider) {
'action' in item &&
(item.name === 'Linked Doc' || item.name === 'Link')
) {
const oldAction = item.action;
item.action = async ({ model, rootComponent }) => {
const { host, std } = rootComponent;
const quickSearchService =
component.std.getOptional(QuickSearchProvider);
item.action = async ({ rootComponent }) => {
// TODO(@Mirone): fix the type
// @ts-expect-error fixme
const { success, insertedLinkType } =
// @ts-expect-error fixme
rootComponent.std.command.exec('insertLinkByQuickSearch');
if (!quickSearchService)
return oldAction({ model, rootComponent });
if (!success) return;
const result = await quickSearchService.openQuickSearch();
if (result === null) return;
// TODO(@Mirone): fix the type
insertedLinkType
?.then(
(type: {
flavour?: 'affine:embed-linked-doc' | 'affine:bookmark';
}) => {
const flavour = type?.flavour;
if (!flavour) return;
if ('docId' in result) {
const linkedDoc = std.collection.getDoc(result.docId);
if (!linkedDoc) return;
if (flavour === 'affine:embed-linked-doc') {
track.doc.editor.slashMenu.linkDoc({
control: 'linkDoc',
});
return;
}
const props: {
flavour: string;
pageId: string;
params?: ReferenceParams;
} = {
flavour: 'affine:embed-linked-doc',
pageId: linkedDoc.id,
};
if (result.params) {
props.params = result.params;
}
host.doc.addSiblingBlocks(model, [props]);
track.doc.editor.slashMenu.linkDoc({ control: 'linkDoc' });
} else if (result.externalUrl) {
const embedOptions = std
.get(EmbedOptionProvider)
.getEmbedBlockOptions(result.externalUrl);
if (!embedOptions) return;
host.doc.addSiblingBlocks(model, [
{
flavour: embedOptions.flavour,
url: result.externalUrl,
},
]);
}
if (flavour === 'affine:bookmark') {
track.doc.editor.slashMenu.bookmark();
return;
}
}
)
.catch(console.error);
};
}
});

View File

@@ -16,7 +16,10 @@ import {
WorkspaceService,
} from '@toeverything/infra';
export function createLinkedWidgetConfig(framework: FrameworkProvider) {
// TODO: fix the type
export function createLinkedWidgetConfig(
framework: FrameworkProvider
): Partial<Record<string, unknown>> {
return {
getMenus: (
query: string,

View File

@@ -1,5 +1,6 @@
import { notify } from '@affine/component';
import { getAffineCloudBaseUrl } from '@affine/core/modules/cloud/services/fetch';
import { toURLSearchParams } from '@affine/core/utils';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { type EditorHost } from '@blocksuite/block-std';
@@ -34,20 +35,14 @@ export const generateUrl = ({
if (!baseUrl) return null;
try {
const url = new URL(`${baseUrl}/workspace/${workspaceId}/${pageId}`);
const search = url.searchParams;
if (shareMode) {
search.append('mode', shareMode);
}
if (blockIds && blockIds.length > 0) {
search.append('blockIds', blockIds.join(','));
}
if (elementIds && elementIds.length > 0) {
search.append('elementIds', elementIds.join(','));
}
if (xywh) {
search.append('xywh', xywh);
}
const url = new URL(`/workspace/${workspaceId}/${pageId}`, baseUrl);
const search = toURLSearchParams({
mode: shareMode,
blockIds,
elementIds,
xywh,
});
if (search) url.search = search.toString();
return url.toString();
} catch {
return null;

View File

@@ -1,5 +1,5 @@
import { useAutoFocus } from '@affine/component';
import { getSvgPath } from '@blocksuite/global/utils';
import { getFigmaSquircleSvgPath } from '@blocksuite/global/utils';
import { SearchIcon } from '@blocksuite/icons/rc';
import clsx from 'clsx';
import { debounce } from 'lodash-es';
@@ -49,7 +49,13 @@ export const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(
const [inputValue, setInputValue] = useState(value);
const clipPath = useMemo(
() => getSvgPath({ width, height, cornerRadius, cornerSmoothing }),
() =>
getFigmaSquircleSvgPath({
width,
height,
cornerRadius,
cornerSmoothing,
}),
[cornerRadius, cornerSmoothing, height, width]
);

View File

@@ -1,10 +1,15 @@
import type { DocMode, EdgelessRootService } from '@blocksuite/blocks';
import type {
DocMode,
EdgelessRootService,
ReferenceParams,
} from '@blocksuite/blocks';
import type { InlineEditor } from '@blocksuite/inline';
import type { AffineEditorContainer, DocTitle } from '@blocksuite/presets';
import type { DocService, WorkspaceService } from '@toeverything/infra';
import { Entity, LiveData } from '@toeverything/infra';
import { isEqual } from 'lodash-es';
import { paramsParseOptions, preprocessParams } from '../../navigation/utils';
import type { WorkbenchView } from '../../workbench';
import { EditorScope } from '../scopes/editor';
import type { EditorSelector } from '../types';
@@ -58,22 +63,11 @@ export class Editor extends Entity {
*/
bindWorkbenchView(view: WorkbenchView) {
// eslint-disable-next-line rxjs/finnish
const viewParams$ = view.queryString$<{
mode?: DocMode;
blockIds?: string[];
elementIds?: string[];
refreshKey?: string;
}>({
// Cannot handle single id situation correctly: `blockIds=xxx`
arrayFormat: 'none',
types: {
mode: value =>
value === 'page' || value === 'edgeless' ? value : undefined,
blockIds: value => (value.length ? value.split(',') : []),
elementIds: value => (value.length ? value.split(',') : []),
refreshKey: 'string',
},
});
const viewParams$ = view
.queryString$<
ReferenceParams & { refreshKey?: string }
>(paramsParseOptions)
.map(preprocessParams);
const stablePrimaryMode = this.doc.getPrimaryMode();

View File

@@ -6,7 +6,6 @@ import type {
EdgelessRootService,
PageRootService,
} from '@blocksuite/blocks';
import { ZOOM_MAX } from '@blocksuite/blocks';
import { Bound, deserializeXYWH } from '@blocksuite/global/utils';
function scrollAnchoringInEdgelessMode(
@@ -47,8 +46,7 @@ function scrollAnchoringInEdgelessMode(
const { zoom, centerX, centerY } = service.getFitToScreenData(
[20, 20, 100, 20],
[bounds],
ZOOM_MAX
[bounds]
);
service.viewport.setCenter(centerX, centerY);

View File

@@ -53,13 +53,37 @@ const testCases: [string, ReturnType<typeof resolveLinkToDoc>][] = [
},
],
[
'http//localhost:8000/workspace/48__RTCSwASvWZxyAk3Jw/-Uge-K6SYcAbcNYfQ5U-j?blockIds=xxxx',
'http//localhost:8000/workspace/48__RTCSwASvWZxyAk3Jw/-Uge-K6SYcAbcNYfQ5U-j?mode=page&blockIds=xxxx',
{
workspaceId: '48__RTCSwASvWZxyAk3Jw',
docId: '-Uge-K6SYcAbcNYfQ5U-j',
mode: 'page',
blockIds: ['xxxx'],
},
],
[
'http//localhost:8000/workspace/48__RTCSwASvWZxyAk3Jw/-Uge-K6SYcAbcNYfQ5U-j?mode=&blockIds=',
{
workspaceId: '48__RTCSwASvWZxyAk3Jw',
docId: '-Uge-K6SYcAbcNYfQ5U-j',
},
],
[
'http//localhost:8000/workspace/48__RTCSwASvWZxyAk3Jw/-Uge-K6SYcAbcNYfQ5U-j?mode=edgeless&elementIds=yyyy',
{
workspaceId: '48__RTCSwASvWZxyAk3Jw',
docId: '-Uge-K6SYcAbcNYfQ5U-j',
mode: 'edgeless',
elementIds: ['yyyy'],
},
],
[
'http//localhost:8000/workspace/48__RTCSwASvWZxyAk3Jw/-Uge-K6SYcAbcNYfQ5U-j?mode=edgeles&elementId=yyyy',
{
workspaceId: '48__RTCSwASvWZxyAk3Jw',
docId: '-Uge-K6SYcAbcNYfQ5U-j',
},
],
];
for (const [input, expected] of testCases) {

View File

@@ -1,4 +1,6 @@
import type { DocMode } from '@blocksuite/blocks';
import type { ReferenceParams } from '@blocksuite/blocks';
import { isNil, pick, pickBy } from 'lodash-es';
import type { ParsedQuery, ParseOptions } from 'query-string';
import queryString from 'query-string';
function maybeAffineOrigin(origin: string) {
@@ -61,6 +63,23 @@ export const resolveRouteLinkMeta = (href: string) => {
}
};
export const isLink = (href: string) => {
try {
const hasScheme = href.match(/^https?:\/\//);
if (!hasScheme) {
const dotIdx = href.indexOf('.');
if (dotIdx > 0 && dotIdx < href.length - 1) {
href = `https://${href}`;
}
}
return Boolean(URL.canParse?.(href) ?? new URL(href));
} catch {
return null;
}
};
/**
* @see /packages/frontend/core/src/router.tsx
*/
@@ -76,22 +95,49 @@ export const resolveLinkToDoc = (href: string) => {
const meta = resolveRouteLinkMeta(href);
if (!meta || meta.moduleName !== 'doc') return null;
const params: {
mode?: DocMode;
blockIds?: string[];
elementIds?: string[];
} = queryString.parse(meta.location.search, {
arrayFormat: 'none',
types: {
mode: value => (value === 'edgeless' ? 'edgeless' : 'page') as DocMode,
blockIds: value => value.split(','),
elementIds: value => value.split(','),
},
});
const params = preprocessParams(
queryString.parse(meta.location.search, paramsParseOptions)
);
return {
workspaceId: meta.workspaceId,
docId: meta.docId,
...pick(meta, ['workspaceId', 'docId']),
...params,
};
};
export const preprocessParams = (
params: ParsedQuery<string>
): ReferenceParams & { refreshKey?: string } => {
const result: ReferenceParams & { refreshKey?: string } = pickBy(
params,
value => {
if (isNil(value)) return false;
if (typeof value === 'string' && value.length === 0) return false;
if (Array.isArray(value) && value.length === 0) return false;
return true;
}
);
if (result.blockIds?.length) {
result.blockIds = result.blockIds.filter(v => v.length);
}
if (result.elementIds?.length) {
result.elementIds = result.elementIds.filter(v => v.length);
}
return pick(result, ['mode', 'blockIds', 'elementIds', 'refreshKey']);
};
export const paramsParseOptions: ParseOptions = {
// Cannot handle single id situation correctly: `blockIds=xxx`
arrayFormat: 'none',
types: {
mode: value =>
value === 'page' || value === 'edgeless' ? value : undefined,
blockIds: value =>
value.length ? value.split(',').filter(v => v.length) : [],
elementIds: value =>
value.length ? value.split(',').filter(v => v.length) : [],
refreshKey: 'string',
},
};

View File

@@ -3,6 +3,7 @@ import type { WorkspaceService } from '@toeverything/infra';
import { Entity, LiveData } from '@toeverything/infra';
import { resolveLinkToDoc } from '../../navigation';
import { isLink } from '../../navigation/utils';
import type { QuickSearchSession } from '../providers/quick-search-provider';
import type { QuickSearchItem } from '../types/item';
@@ -21,11 +22,10 @@ export class ExternalLinksQuickSearchSession
query$ = new LiveData('');
items$ = LiveData.computed(get => {
const query = get(this.query$);
const query = get(this.query$).trim();
if (!query) return [];
const isLink = query.startsWith('http://') || query.startsWith('https://');
if (!isLink) return [];
if (!isLink(query)) return [];
const resolvedDoc = resolveLinkToDoc(query);
if (

View File

@@ -1,20 +1,16 @@
import type { DocMode } from '@blocksuite/blocks';
import type { ReferenceParams } from '@blocksuite/blocks';
import { BlockLinkIcon, EdgelessIcon, PageIcon } from '@blocksuite/icons/rc';
import type { DocsService, WorkspaceService } from '@toeverything/infra';
import { Entity, LiveData } from '@toeverything/infra';
import { truncate } from 'lodash-es';
import { omit, truncate } from 'lodash-es';
import { resolveLinkToDoc } from '../../navigation';
import { isLink } from '../../navigation/utils';
import type { QuickSearchSession } from '../providers/quick-search-provider';
import type { DocDisplayMetaService } from '../services/doc-display-meta';
import type { QuickSearchItem } from '../types/item';
type LinkPayload = {
docId: string;
blockIds?: string[];
elementIds?: string[];
mode?: DocMode;
};
type LinkPayload = { docId: string } & ReferenceParams;
export class LinksQuickSearchSession
extends Entity
@@ -31,11 +27,10 @@ export class LinksQuickSearchSession
query$ = new LiveData('');
items$ = LiveData.computed(get => {
const query = get(this.query$);
const query = get(this.query$).trim();
if (!query) return [];
const isLink = query.startsWith('http://') || query.startsWith('https://');
if (!isLink) return [];
if (!isLink(query)) return [];
const resolvedDoc = resolveLinkToDoc(query);
if (
@@ -53,6 +48,13 @@ export class LinksQuickSearchSession
this.docDisplayMetaService.getDocDisplayMeta(doc);
const linkToNode = resolvedDoc.blockIds || resolvedDoc.elementIds;
const score = 100;
const payload = omit(resolvedDoc, ['workspaceId']);
const icons = {
page: PageIcon,
edgeless: EdgelessIcon,
node: BlockLinkIcon,
other: icon,
};
return [
{
@@ -67,23 +69,12 @@ export class LinksQuickSearchSession
score: 5,
},
label: {
title: title,
title,
},
score,
icon: linkToNode
? BlockLinkIcon
: resolvedDoc.mode === 'page'
? PageIcon
: resolvedDoc.mode === 'edgeless'
? EdgelessIcon
: icon,
icon: icons[linkToNode ? 'node' : (resolvedDoc.mode ?? 'other')],
timestamp: updatedDate,
payload: {
docId,
blockIds: resolvedDoc.blockIds,
elementIds: resolvedDoc.elementIds,
mode: resolvedDoc.mode,
},
payload,
} as QuickSearchItem<'link', LinkPayload>,
];
});

View File

@@ -1,4 +1,5 @@
import { appInfo } from '@affine/electron-api';
import { isNil } from 'lodash-es';
interface AppUrlOptions {
desktop?: boolean | string;
@@ -32,12 +33,24 @@ export function buildAppUrl(path: string, opts: AppUrlOptions = {}) {
}
}
export function toURLSearchParams(params?: Record<string, string | string[]>) {
export function toURLSearchParams(
params?: Partial<Record<string, string | string[]>>
) {
if (!params) return;
const items = Object.entries(params)
.filter(([_, v]) => !isNil(v))
.filter(([_, v]) => {
if (typeof v === 'string') {
return v.length > 0;
}
if (Array.isArray(v)) {
return v.length > 0;
}
return false;
}) as [string, string | string[]][];
return new URLSearchParams(
Object.entries(params).map(([k, v]) => [
k,
Array.isArray(v) ? v.join(',') : v,
])
items.map(([k, v]) => [k, Array.isArray(v) ? v.join(',') : v])
);
}

View File

@@ -45,7 +45,8 @@ type DocEvents =
| 'switchPageMode'
| 'openDocOptionsMenu'
| 'openDocInfo'
| 'copyBlockToLink';
| 'copyBlockToLink'
| 'bookmark';
type EditorEvents = 'bold' | 'italic' | 'underline' | 'strikeThrough';
// END SECTION
@@ -262,7 +263,7 @@ const PageEvents = {
},
doc: {
editor: {
slashMenu: ['linkDoc', 'createDoc'],
slashMenu: ['linkDoc', 'createDoc', 'bookmark'],
atMenu: ['linkDoc'],
quickSearch: ['createDoc'],
formatToolbar: ['bold'],