mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-16 05:47:09 +08:00
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:
@@ -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();
|
||||
@@ -1,8 +0,0 @@
|
||||
import { getAISpecs } from '@blocksuite/presets';
|
||||
|
||||
import { setupAIProvider } from './provider';
|
||||
|
||||
export function getParsedAISpecs() {
|
||||
setupAIProvider();
|
||||
return getAISpecs();
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
export * from './blocksuite-editor';
|
||||
|
||||
import './ai/setup-provider';
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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,
|
||||
];
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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,
|
||||
];
|
||||
@@ -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,
|
||||
];
|
||||
@@ -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 =
|
||||
|
||||
Reference in New Issue
Block a user