feat(core): bump blocksuite (#6965)

## Features
- https://github.com/toeverything/BlockSuite/pull/7052 @donteatfriedrice

## Bugfix
- https://github.com/toeverything/BlockSuite/pull/7072 @Flrande
- https://github.com/toeverything/BlockSuite/pull/7073 @Flrande
- https://github.com/toeverything/BlockSuite/pull/7062 @akumatus
- https://github.com/toeverything/BlockSuite/pull/7066 @L-Sun
- https://github.com/toeverything/BlockSuite/pull/7061 @Flrande
- https://github.com/toeverything/BlockSuite/pull/7058 @L-Sun
- https://github.com/toeverything/BlockSuite/pull/7060 @doouding
- https://github.com/toeverything/BlockSuite/pull/7051 @L-Sun
- https://github.com/toeverything/BlockSuite/pull/7054 @L-Sun
- https://github.com/toeverything/BlockSuite/pull/7023 @golok727
- https://github.com/toeverything/BlockSuite/pull/7022 @golok727
- https://github.com/toeverything/BlockSuite/pull/7047 @fundon
- https://github.com/toeverything/BlockSuite/pull/7043 @akumatus
- https://github.com/toeverything/BlockSuite/pull/7041 @donteatfriedrice
- https://github.com/toeverything/BlockSuite/pull/7038 @fourdim
- https://github.com/toeverything/BlockSuite/pull/7040 @regischen

## Refactor
- https://github.com/toeverything/BlockSuite/pull/7068 @doouding
- https://github.com/toeverything/BlockSuite/pull/7069 @zzj3720
- https://github.com/toeverything/BlockSuite/pull/7065 @Flrande
- https://github.com/toeverything/BlockSuite/pull/7048 @fundon
- https://github.com/toeverything/BlockSuite/pull/7045 @Flrande
- https://github.com/toeverything/BlockSuite/pull/7046 @donteatfriedrice
- https://github.com/toeverything/BlockSuite/pull/7039 @Flrande
- https://github.com/toeverything/BlockSuite/pull/7036 @Saul-Mirone
- https://github.com/toeverything/BlockSuite/pull/7032 @Saul-Mirone

## Misc
- https://github.com/toeverything/BlockSuite/pull/7063 @Saul-Mirone
- https://github.com/toeverything/BlockSuite/pull/7050 @fourdim
- https://github.com/toeverything/BlockSuite/pull/7044 @doouding
- https://github.com/toeverything/BlockSuite/pull/7042 @Flrande
- https://github.com/toeverything/BlockSuite/pull/6992 @doouding
This commit is contained in:
Flrande
2024-05-17 03:44:11 +00:00
parent bd5023d4ab
commit 35a6cf655b
21 changed files with 403 additions and 433 deletions

View File

@@ -17,7 +17,7 @@ import {
} from './request';
import { setupTracker } from './tracker';
export function setupAIProvider() {
function setupAIProvider() {
// a single workspace should have only a single chat session
// user-id:workspace-id:doc-id -> chat session id
const chatSessions = new Map<string, Promise<string>>();
@@ -371,3 +371,5 @@ Could you make a new website based on these notes and send back just the html fi
setupTracker();
}
setupAIProvider();

View File

@@ -1,8 +0,0 @@
import { getAISpecs } from '@blocksuite/presets';
import { setupAIProvider } from './provider';
export function getParsedAISpecs() {
setupAIProvider();
return getAISpecs();
}

View File

@@ -21,7 +21,7 @@ import {
} from 'react';
import { BlocksuiteDocEditor, BlocksuiteEdgelessEditor } from './lit-adaper';
import type { InlineRenderers } from './specs';
import type { ReferenceReactRenderer } from './specs/custom/patch-reference-renderer';
import * as styles from './styles.css';
// copy forwardSlot from blocksuite, but it seems we need to dispose the pipe
@@ -45,7 +45,7 @@ interface BlocksuiteEditorContainerProps {
className?: string;
style?: React.CSSProperties;
defaultSelectedBlockId?: string;
customRenderers?: InlineRenderers;
referenceRenderer?: ReferenceReactRenderer;
}
// mimic the interface of the webcomponent and expose slots & host
@@ -99,7 +99,7 @@ export const BlocksuiteEditorContainer = forwardRef<
AffineEditorContainer,
BlocksuiteEditorContainerProps
>(function AffineEditorContainer(
{ page, mode, className, style, defaultSelectedBlockId, customRenderers },
{ page, mode, className, style, defaultSelectedBlockId, referenceRenderer },
ref
) {
const [scrolled, setScrolled] = useState(false);
@@ -246,7 +246,7 @@ export const BlocksuiteEditorContainer = forwardRef<
return;
}
const newSelection = selectManager.create('block', {
path: blockElement.path,
blockId: blockElement.blockId,
});
selectManager.set([newSelection]);
setScrolled(true);
@@ -282,13 +282,13 @@ export const BlocksuiteEditorContainer = forwardRef<
<BlocksuiteDocEditor
page={page}
ref={docRef}
customRenderers={customRenderers}
referenceRenderer={referenceRenderer}
/>
) : (
<BlocksuiteEdgelessEditor
page={page}
ref={edgelessRef}
customRenderers={customRenderers}
referenceRenderer={referenceRenderer}
/>
)}
</div>

View File

@@ -1,7 +1,4 @@
import { EditorLoading } from '@affine/component/page-detail-skeleton';
import { useDocMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
import { useJournalHelper } from '@affine/core/hooks/use-journal';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { assertExists } from '@blocksuite/global/utils';
import type { AffineEditorContainer } from '@blocksuite/presets';
import type { Doc } from '@blocksuite/store';
@@ -17,11 +14,10 @@ import {
useRef,
} from 'react';
import type { PageReferenceRendererOptions } from '../../affine/reference-link';
import { AffinePageReference } from '../../affine/reference-link';
import { BlocksuiteEditorContainer } from './blocksuite-editor-container';
import { NoPageRootError } from './no-page-error';
import type { InlineRenderers } from './specs';
import type { ReferenceReactRenderer } from './specs/custom/patch-reference-renderer';
export type ErrorBoundaryProps = {
onReset?: () => void;
@@ -59,20 +55,6 @@ function usePageRoot(page: Doc) {
return page.root;
}
const customRenderersFactory: (
opts: Omit<PageReferenceRendererOptions, 'pageId'>
) => InlineRenderers = opts => ({
pageReference(reference) {
const pageId = reference.delta.attributes?.reference?.pageId;
if (!pageId) {
return <span />;
}
return (
<AffinePageReference docCollection={opts.docCollection} pageId={pageId} />
);
},
});
const BlockSuiteEditorImpl = forwardRef<AffineEditorContainer, EditorProps>(
function BlockSuiteEditorImpl(
{ mode, page, className, defaultSelectedBlockId, onLoadEditor, style },
@@ -106,18 +88,18 @@ const BlockSuiteEditorImpl = forwardRef<AffineEditorContainer, EditorProps>(
};
}, []);
const pageMetaHelper = useDocMetaHelper(page.collection);
const journalHelper = useJournalHelper(page.collection);
const t = useAFFiNEI18N();
const customRenderers = useMemo(() => {
return customRenderersFactory({
pageMetaHelper,
journalHelper,
t,
docCollection: page.collection,
});
}, [journalHelper, page.collection, pageMetaHelper, t]);
const referenceRenderer: ReferenceReactRenderer = useMemo(() => {
return function customReference(reference) {
const pageId = reference.delta.attributes?.reference?.pageId;
if (!pageId) return <span />;
return (
<AffinePageReference
docCollection={page.collection}
pageId={pageId}
/>
);
};
}, [page.collection]);
return (
<BlocksuiteEditorContainer
@@ -126,7 +108,7 @@ const BlockSuiteEditorImpl = forwardRef<AffineEditorContainer, EditorProps>(
ref={onRefChange}
className={className}
style={style}
customRenderers={customRenderers}
referenceRenderer={referenceRenderer}
defaultSelectedBlockId={defaultSelectedBlockId}
/>
);

View File

@@ -1 +1,3 @@
export * from './blocksuite-editor';
import './ai/setup-provider';

View File

@@ -23,8 +23,12 @@ import React, {
import { PagePropertiesTable } from '../../affine/page-properties';
import { BlocksuiteEditorJournalDocTitle } from './journal-doc-title';
import type { InlineRenderers } from './specs';
import { docModeSpecs, edgelessModeSpecs, patchSpecs } from './specs';
import {
patchReferenceRenderer,
type ReferenceReactRenderer,
} from './specs/custom/patch-reference-renderer';
import { EdgelessModeSpecs } from './specs/edgeless';
import { PageModeSpecs } from './specs/page';
import * as styles from './styles.css';
const adapted = {
@@ -50,16 +54,16 @@ const adapted = {
}),
};
interface BlocksuiteDocEditorProps {
interface BlocksuiteEditorProps {
page: Doc;
customRenderers?: InlineRenderers;
referenceRenderer?: ReferenceReactRenderer;
// todo: add option to replace docTitle with custom component (e.g., for journal page)
}
export const BlocksuiteDocEditor = forwardRef<
PageEditor,
BlocksuiteDocEditorProps
>(function BlocksuiteDocEditor({ page, customRenderers }, ref) {
BlocksuiteEditorProps
>(function BlocksuiteDocEditor({ page, referenceRenderer }, ref) {
const titleRef = useRef<DocTitle>(null);
const docRef = useRef<PageEditor | null>(null);
const [docPage, setDocPage] =
@@ -80,11 +84,12 @@ export const BlocksuiteDocEditor = forwardRef<
[ref]
);
const [litToTemplate, portals] = useLitPortalFactory();
const [reactToLit, portals] = useLitPortalFactory();
const specs = useMemo(() => {
return patchSpecs(docModeSpecs, litToTemplate, customRenderers);
}, [customRenderers, litToTemplate]);
if (!referenceRenderer) return PageModeSpecs;
return patchReferenceRenderer(PageModeSpecs, reactToLit, referenceRenderer);
}, [reactToLit, referenceRenderer]);
useEffect(() => {
// auto focus the title
@@ -139,12 +144,17 @@ export const BlocksuiteDocEditor = forwardRef<
export const BlocksuiteEdgelessEditor = forwardRef<
EdgelessEditor,
BlocksuiteDocEditorProps
>(function BlocksuiteEdgelessEditor({ page, customRenderers }, ref) {
const [litToTemplate, portals] = useLitPortalFactory();
BlocksuiteEditorProps
>(function BlocksuiteEdgelessEditor({ page, referenceRenderer }, ref) {
const [reactToLit, portals] = useLitPortalFactory();
const specs = useMemo(() => {
return patchSpecs(edgelessModeSpecs, litToTemplate, customRenderers);
}, [customRenderers, litToTemplate]);
if (!referenceRenderer) return EdgelessModeSpecs;
return patchReferenceRenderer(
EdgelessModeSpecs,
reactToLit,
referenceRenderer
);
}, [reactToLit, referenceRenderer]);
return (
<>
<adapted.EdgelessEditor ref={ref} doc={page} specs={specs} />

View File

@@ -1,228 +0,0 @@
import type { ElementOrFactory } from '@affine/component';
import { mixpanel } from '@affine/core/utils';
import type { BlockSpec } from '@blocksuite/block-std';
import type { ParagraphService, RootService } from '@blocksuite/blocks';
import {
AffineLinkedDocWidget,
AffineSlashMenuWidget,
AttachmentService,
CanvasTextFonts,
EdgelessRootService,
PageRootService,
} from '@blocksuite/blocks';
import type { BlockModel } from '@blocksuite/store';
import bytes from 'bytes';
import type { TemplateResult } from 'lit';
import { getParsedAISpecs } from './ai/spec';
const {
pageModeSpecs: PageEditorBlockSpecs,
edgelessModeSpecs: EdgelessEditorBlockSpecs,
} = getParsedAISpecs();
class CustomAttachmentService extends AttachmentService {
override mounted(): void {
// blocksuite default max file size is 10MB, we override it to 2GB
// but the real place to limit blob size is CloudQuotaModal / LocalQuotaModal
this.maxFileSize = bytes.parse('2GB');
super.mounted();
}
}
function customLoadFonts(service: RootService): void {
if (runtimeConfig.isSelfHosted) {
const fonts = CanvasTextFonts.map(font => ({
...font,
// self-hosted fonts are served from /assets
url: '/assets/' + new URL(font.url).pathname.split('/').pop(),
}));
service.fontLoader.load(fonts);
} else {
service.fontLoader.load(CanvasTextFonts);
}
}
class CustomDocPageService extends PageRootService {
override loadFonts(): void {
customLoadFonts(this);
}
}
class CustomEdgelessPageService extends EdgelessRootService {
override loadFonts(): void {
customLoadFonts(this);
}
override addElement<T = Record<string, unknown>>(type: string, props: T) {
const res = super.addElement(type, props);
mixpanel.track('WhiteboardObjectCreated', {
page: 'whiteboard editor',
module: 'whiteboard',
segment: 'canvas',
// control:
type: 'whiteboard object',
category: type,
});
return res;
}
override addBlock(
flavour: string,
props: Record<string, unknown>,
parent?: string | BlockModel,
parentIndex?: number
) {
const res = super.addBlock(flavour, props, parent, parentIndex);
mixpanel.track('WhiteboardObjectCreated', {
page: 'whiteboard editor',
module: 'whiteboard',
segment: 'canvas',
// control:
type: 'whiteboard object',
category: flavour.split(':')[1], // affine:paragraph -> paragraph
});
return res;
}
}
type AffineReference = HTMLElementTagNameMap['affine-reference'];
type PageReferenceRenderer = (reference: AffineReference) => React.ReactElement;
export interface InlineRenderers {
pageReference?: PageReferenceRenderer;
}
function patchSpecsWithReferenceRenderer(
specs: BlockSpec<string>[],
pageReferenceRenderer: PageReferenceRenderer,
toLitTemplate: (element: ElementOrFactory) => TemplateResult
) {
const renderer = (reference: AffineReference) => {
const node = pageReferenceRenderer(reference);
return toLitTemplate(node);
};
return specs.map(spec => {
if (
['affine:paragraph', 'affine:list', 'affine:database'].includes(
spec.schema.model.flavour
)
) {
// todo: remove these type assertions
spec.service = class extends (spec.service as typeof ParagraphService) {
override mounted() {
super.mounted();
this.referenceNodeConfig.setCustomContent(renderer);
}
};
}
return spec;
});
}
function patchSlashMenuWidget() {
const menuGroup = AffineSlashMenuWidget.DEFAULT_OPTIONS.menus.find(group => {
return group.name === 'Docs';
});
if (Array.isArray(menuGroup?.items)) {
const newDocItem = menuGroup.items.find(item => {
return item.name === 'New Doc';
});
if (newDocItem) {
const oldAction = newDocItem.action;
newDocItem.action = async (...props) => {
await oldAction(...props);
mixpanel.track('DocCreated', {
segment: 'doc',
module: 'command menu',
control: 'new doc command',
type: 'doc',
category: 'doc',
});
};
}
}
}
function patchLinkedDocPopover() {
const oldGetMenus = AffineLinkedDocWidget.DEFAULT_OPTIONS.getMenus;
AffineLinkedDocWidget.DEFAULT_OPTIONS.getMenus = ctx => {
const menus = oldGetMenus(ctx);
const newDocGroup = menus.find(group => group.name === 'New Doc');
const newDocItem = newDocGroup?.items.find(item => item.key === 'create');
// todo: patch import doc/workspace action
// const importItem = newDocGroup?.items.find(item => item.key === 'import');
if (newDocItem) {
const oldAction = newDocItem.action;
newDocItem.action = async () => {
await oldAction();
mixpanel.track('DocCreated', {
segment: 'doc',
module: 'linked doc popover',
control: 'new doc command',
type: 'doc',
category: 'doc',
});
};
}
return menus;
};
}
patchSlashMenuWidget();
patchLinkedDocPopover();
/**
* Patch the block specs with custom renderers.
*/
export function patchSpecs(
specs: BlockSpec<string>[],
toLitTemplate: (element: ElementOrFactory) => TemplateResult,
inlineRenderers?: InlineRenderers
) {
let newSpecs = specs;
if (inlineRenderers?.pageReference) {
newSpecs = patchSpecsWithReferenceRenderer(
newSpecs,
inlineRenderers.pageReference,
toLitTemplate
);
}
return newSpecs;
}
export const docModeSpecs = PageEditorBlockSpecs.map(spec => {
if (spec.schema.model.flavour === 'affine:attachment') {
return {
...spec,
service: CustomAttachmentService,
};
}
if (spec.schema.model.flavour === 'affine:page') {
return {
...spec,
service: CustomDocPageService,
};
}
return spec;
});
export const edgelessModeSpecs = EdgelessEditorBlockSpecs.map(spec => {
if (spec.schema.model.flavour === 'affine:attachment') {
return {
...spec,
service: CustomAttachmentService,
};
}
if (spec.schema.model.flavour === 'affine:page') {
return {
...spec,
service: CustomEdgelessPageService,
};
}
return spec;
});

View File

@@ -0,0 +1,42 @@
import type { BlockSpec } from '@blocksuite/block-std';
import {
BookmarkBlockSpec,
CodeBlockSpec,
DatabaseBlockSpec,
DataViewBlockSpec,
DividerBlockSpec,
EmbedFigmaBlockSpec,
EmbedGithubBlockSpec,
EmbedHtmlBlockSpec,
EmbedLinkedDocBlockSpec,
EmbedLoomBlockSpec,
EmbedSyncedDocBlockSpec,
EmbedYoutubeBlockSpec,
ImageBlockSpec,
ListBlockSpec,
NoteBlockSpec,
} from '@blocksuite/blocks';
import { AIParagraphBlockSpec } from '@blocksuite/presets';
import { CustomAttachmentBlockSpec } from './custom/attachment-block';
export const CommonBlockSpecs: BlockSpec[] = [
ListBlockSpec,
NoteBlockSpec,
DatabaseBlockSpec,
DataViewBlockSpec,
DividerBlockSpec,
CodeBlockSpec,
ImageBlockSpec,
BookmarkBlockSpec,
EmbedFigmaBlockSpec,
EmbedGithubBlockSpec,
EmbedYoutubeBlockSpec,
EmbedLoomBlockSpec,
EmbedHtmlBlockSpec,
EmbedSyncedDocBlockSpec,
EmbedLinkedDocBlockSpec,
// special
CustomAttachmentBlockSpec,
AIParagraphBlockSpec,
];

View File

@@ -0,0 +1,20 @@
import type { BlockSpec } from '@blocksuite/block-std';
import {
AttachmentBlockService,
AttachmentBlockSpec,
} from '@blocksuite/blocks';
import bytes from 'bytes';
class CustomAttachmentBlockService extends AttachmentBlockService {
override mounted(): void {
// blocksuite default max file size is 10MB, we override it to 2GB
// but the real place to limit blob size is CloudQuotaModal / LocalQuotaModal
this.maxFileSize = bytes.parse('2GB');
super.mounted();
}
}
export const CustomAttachmentBlockSpec: BlockSpec = {
...AttachmentBlockSpec,
service: CustomAttachmentBlockService,
};

View File

@@ -0,0 +1,45 @@
import type { ElementOrFactory } from '@affine/component';
import type { BlockSpec } from '@blocksuite/block-std';
import type {
AffineReference,
ParagraphBlockService,
} from '@blocksuite/blocks';
import type { TemplateResult } from 'lit';
export type ReferenceReactRenderer = (
reference: AffineReference
) => React.ReactElement;
/**
* Patch the block specs with custom renderers.
*/
export function patchReferenceRenderer(
specs: BlockSpec[],
reactToLit: (element: ElementOrFactory) => TemplateResult,
reactRenderer: ReferenceReactRenderer
) {
const litRenderer = (reference: AffineReference) => {
const node = reactRenderer(reference);
return reactToLit(node);
};
return specs.map(spec => {
if (
['affine:paragraph', 'affine:list', 'affine:database'].includes(
spec.schema.model.flavour
)
) {
// todo: remove these type assertions
spec.service = class extends (
(spec.service as typeof ParagraphBlockService)
) {
override mounted() {
super.mounted();
this.referenceNodeConfig.setCustomContent(litRenderer);
}
};
}
return spec;
});
}

View File

@@ -0,0 +1,79 @@
import { mixpanel } from '@affine/core/utils';
import type { BlockSpec } from '@blocksuite/block-std';
import type { RootService } from '@blocksuite/blocks';
import {
AffineCanvasTextFonts,
EdgelessRootService,
PageRootService,
} from '@blocksuite/blocks';
import {
AIEdgelessRootBlockSpec,
AIPageRootBlockSpec,
} from '@blocksuite/presets';
import type { BlockModel } from '@blocksuite/store';
function customLoadFonts(service: RootService): void {
if (runtimeConfig.isSelfHosted) {
const fonts = AffineCanvasTextFonts.map(font => ({
...font,
// self-hosted fonts are served from /assets
url: '/assets/' + new URL(font.url).pathname.split('/').pop(),
}));
service.fontLoader.load(fonts);
} else {
service.fontLoader.load(AffineCanvasTextFonts);
}
}
class CustomPageRootService extends PageRootService {
override loadFonts(): void {
customLoadFonts(this);
}
}
class CustomEdgelessRootService extends EdgelessRootService {
override loadFonts(): void {
customLoadFonts(this);
}
override addElement<T = Record<string, unknown>>(type: string, props: T) {
const res = super.addElement(type, props);
mixpanel.track('WhiteboardObjectCreated', {
page: 'whiteboard editor',
module: 'whiteboard',
segment: 'canvas',
// control:
type: 'whiteboard object',
category: type,
});
return res;
}
override addBlock(
flavour: string,
props: Record<string, unknown>,
parent?: string | BlockModel,
parentIndex?: number
) {
const res = super.addBlock(flavour, props, parent, parentIndex);
mixpanel.track('WhiteboardObjectCreated', {
page: 'whiteboard editor',
module: 'whiteboard',
segment: 'canvas',
// control:
type: 'whiteboard object',
category: flavour.split(':')[1], // affine:paragraph -> paragraph
});
return res;
}
}
export const CustomPageRootBlockSpec: BlockSpec = {
...AIPageRootBlockSpec,
service: CustomPageRootService,
};
export const CustomEdgelessRootBlockSpec: BlockSpec = {
...AIEdgelessRootBlockSpec,
service: CustomEdgelessRootService,
};

View File

@@ -0,0 +1,18 @@
import type { BlockSpec } from '@blocksuite/block-std';
import {
EdgelessSurfaceBlockSpec,
EdgelessSurfaceRefBlockSpec,
FrameBlockSpec,
} from '@blocksuite/blocks';
import { CommonBlockSpecs } from './common';
import { CustomEdgelessRootBlockSpec } from './custom/root-block';
export const EdgelessModeSpecs: BlockSpec[] = [
...CommonBlockSpecs,
EdgelessSurfaceBlockSpec,
EdgelessSurfaceRefBlockSpec,
FrameBlockSpec,
// special
CustomEdgelessRootBlockSpec,
];

View File

@@ -0,0 +1,16 @@
import type { BlockSpec } from '@blocksuite/block-std';
import {
PageSurfaceBlockSpec,
PageSurfaceRefBlockSpec,
} from '@blocksuite/blocks';
import { CommonBlockSpecs } from './common';
import { CustomPageRootBlockSpec } from './custom/root-block';
export const PageModeSpecs: BlockSpec[] = [
...CommonBlockSpecs,
PageSurfaceBlockSpec,
PageSurfaceRefBlockSpec,
// special
CustomPageRootBlockSpec,
];

View File

@@ -4,12 +4,12 @@ import { PageAIOnboarding } from '@affine/core/components/affine/ai-onboarding';
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
import type { PageRootService } from '@blocksuite/blocks';
import {
BookmarkService,
BookmarkBlockService,
customImageProxyMiddleware,
EmbedGithubService,
EmbedLoomService,
EmbedYoutubeService,
ImageService,
EmbedGithubBlockService,
EmbedLoomBlockService,
EmbedYoutubeBlockService,
ImageBlockService,
} from '@blocksuite/blocks';
import { DisposableGroup } from '@blocksuite/global/utils';
import { type AffineEditorContainer, AIProvider } from '@blocksuite/presets';
@@ -169,13 +169,19 @@ const DetailPageImpl = memo(function DetailPageImpl() {
editorHost.std.clipboard.use(
customImageProxyMiddleware(runtimeConfig.imageProxyUrl)
);
ImageService.setImageProxyURL(runtimeConfig.imageProxyUrl);
ImageBlockService.setImageProxyURL(runtimeConfig.imageProxyUrl);
// provide link preview endpoint to blocksuite
BookmarkService.setLinkPreviewEndpoint(runtimeConfig.linkPreviewUrl);
EmbedGithubService.setLinkPreviewEndpoint(runtimeConfig.linkPreviewUrl);
EmbedYoutubeService.setLinkPreviewEndpoint(runtimeConfig.linkPreviewUrl);
EmbedLoomService.setLinkPreviewEndpoint(runtimeConfig.linkPreviewUrl);
BookmarkBlockService.setLinkPreviewEndpoint(runtimeConfig.linkPreviewUrl);
EmbedGithubBlockService.setLinkPreviewEndpoint(
runtimeConfig.linkPreviewUrl
);
EmbedYoutubeBlockService.setLinkPreviewEndpoint(
runtimeConfig.linkPreviewUrl
);
EmbedLoomBlockService.setLinkPreviewEndpoint(
runtimeConfig.linkPreviewUrl
);
// provide page mode and updated date to blocksuite
const pageService =