chore: bump bs with new extension api (#8042)

This commit is contained in:
Saul-Mirone
2024-09-02 10:32:22 +00:00
parent 61e37d8873
commit 56f4634c1f
90 changed files with 1300 additions and 988 deletions
@@ -4,11 +4,12 @@ import type {
EditorHost,
TextSelection,
} from '@blocksuite/block-std';
import type {
DocMode,
EdgelessRootService,
ImageSelection,
PageRootService,
import {
type DocMode,
DocModeProvider,
type EdgelessRootService,
type ImageSelection,
type PageRootService,
} from '@blocksuite/blocks';
import { BlocksUtils, NoteDisplayMode } from '@blocksuite/blocks';
import {
@@ -176,8 +177,7 @@ function addAIChatBlock(
}
export function promptDocTitle(host: EditorHost, autofill?: string) {
const notification =
host.std.spec.getService('affine:page').notificationService;
const notification = host.std.getService('affine:page').notificationService;
if (!notification) return Promise.resolve(undefined);
return notification.prompt({
@@ -297,11 +297,12 @@ const SAVE_CHAT_TO_BLOCK_ACTION: ChatAction = {
return false;
}
const rootService = host.spec.getService('affine:page');
const surfaceService = host.spec.getService('affine:surface');
const rootService = host.std.getService('affine:page');
const surfaceService = host.std.getService('affine:surface');
if (!rootService || !surfaceService) return false;
const { docModeService, notificationService } = rootService;
const { notificationService } = rootService;
const docModeService = host.std.get(DocModeProvider);
const { layer } = surfaceService;
const curMode = docModeService.getMode();
const viewportCenter = getViewportCenter(
@@ -312,7 +313,7 @@ const SAVE_CHAT_TO_BLOCK_ACTION: ChatAction = {
// If current mode is not edgeless, switch to edgeless mode first
if (curMode !== 'edgeless') {
// Set mode to edgeless
docModeService.setMode('edgeless');
docModeService.setMode('edgeless' as DocMode);
// Notify user to switch to edgeless mode
notificationService?.notify({
title: 'Save chat to a block',
@@ -382,7 +383,7 @@ const ADD_TO_EDGELESS_AS_NOTE = {
handler: async (host: EditorHost, content: string) => {
reportResponse('result:add-note');
const { doc } = host;
const service = host.spec.getService<EdgelessRootService>('affine:page');
const service = host.std.getService<EdgelessRootService>('affine:page');
const elements = service.selection.selectedElements;
const props: { displayMode: NoteDisplayMode; xywh?: SerializedXYWH } = {
@@ -423,8 +424,8 @@ const CREATE_AS_DOC = {
newDoc.addBlock('affine:surface', {}, rootId);
const noteId = newDoc.addBlock('affine:note', {}, rootId);
host.spec.getService('affine:page').slots.docLinkClicked.emit({
docId: newDoc.id,
host.std.getService('affine:page').slots.docLinkClicked.emit({
pageId: newDoc.id,
});
let complete = false;
(function addContent() {
@@ -460,8 +461,9 @@ const CREATE_AS_LINKED_DOC = {
return false;
}
const service = host.spec.getService<EdgelessRootService>('affine:page');
const mode = service.docModeService.getMode();
const service = host.std.getService<EdgelessRootService>('affine:page');
const docModeService = host.std.get(DocModeProvider);
const mode = docModeService.getMode();
if (mode !== 'edgeless') {
return false;
}
@@ -1,6 +1,7 @@
import { type EditorHost, WithDisposable } from '@blocksuite/block-std';
import {
type AIItemGroupConfig,
DocMode,
EdgelessRootService,
scrollbarStyle,
} from '@blocksuite/blocks';
@@ -63,7 +64,7 @@ export class AskAIPanel extends WithDisposable(LitElement) {
item.showWhen
? item.showWhen(
this.host.command.chain(),
this._edgeless ? 'edgeless' : 'page',
this._edgeless ? DocMode.Edgeless : DocMode.Page,
this.host
)
: true
@@ -60,7 +60,7 @@ export class ChatActionList extends LitElement {
}
private get _rootService() {
return this.host.spec.getService('affine:page');
return this.host.std.getService('affine:page');
}
private get _currentTextSelection(): TextSelection | undefined {
@@ -73,7 +73,7 @@ export class ChatCopyMore extends WithDisposable(LitElement) {
`;
private get _rootService() {
return this.host.spec.getService('affine:page');
return this.host.std.getService('affine:page');
}
private get _selectionValue() {
@@ -12,7 +12,7 @@ import { Slice } from '@blocksuite/store';
import { getMarkdownFromSlice } from './markdown-utils';
export const getRootService = (host: EditorHost) => {
return host.std.spec.getService('affine:page');
return host.std.getService('affine:page');
};
export function getEdgelessRootFromEditor(editor: EditorHost) {
@@ -24,7 +24,7 @@ export function getEdgelessRootFromEditor(editor: EditorHost) {
return edgelessRoot;
}
export function getEdgelessService(editor: EditorHost) {
const rootService = editor.std.spec.getService('affine:page');
const rootService = editor.std.getService('affine:page');
if (rootService instanceof EdgelessRootService) {
return rootService;
}
@@ -507,7 +507,7 @@ export const responses: {
const contents = data.contents as unknown[];
if (!contents) return;
const images = data.images as { url: string; id: string }[][];
const service = host.spec.getService<EdgelessRootService>('affine:page');
const service = host.std.getService<EdgelessRootService>('affine:page');
(async function () {
for (let i = 0; i < contents.length - 1; i++) {
@@ -1,4 +1,8 @@
import type { BlockSpec } from '@blocksuite/block-std';
import {
BlockServiceWatcher,
type ExtensionType,
WidgetViewMapIdentifier,
} from '@blocksuite/block-std';
import {
AFFINE_AI_PANEL_WIDGET,
AFFINE_EDGELESS_COPILOT_WIDGET,
@@ -11,8 +15,10 @@ import {
EdgelessCopilotWidget,
EdgelessElementToolbarWidget,
EdgelessRootBlockSpec,
edgelessRootWigetViewMap,
ImageBlockSpec,
PageRootBlockSpec,
pageRootWidgetViewMap,
ParagraphBlockService,
ParagraphBlockSpec,
} from '@blocksuite/blocks';
@@ -30,56 +36,52 @@ import { setupImageToolbarEntry } from './entries/image-toolbar/setup-image-tool
import { setupSlashMenuEntry } from './entries/slash-menu/setup-slash-menu';
import { setupSpaceEntry } from './entries/space/setup-space';
export const AIPageRootBlockSpec: BlockSpec = {
class AIPageRootWatcher extends BlockServiceWatcher {
static override readonly flavour = 'affine:page';
override mounted() {
super.mounted();
this.blockService.specSlots.widgetConnected.on(view => {
if (view.component instanceof AffineAIPanelWidget) {
view.component.style.width = '630px';
view.component.config = buildAIPanelConfig(view.component);
setupSpaceEntry(view.component);
}
if (view.component instanceof AffineFormatBarWidget) {
setupFormatBarEntry(view.component);
}
if (view.component instanceof AffineSlashMenuWidget) {
setupSlashMenuEntry(view.component);
}
});
}
}
export const AIPageRootBlockSpec: ExtensionType[] = [
...PageRootBlockSpec,
view: {
...PageRootBlockSpec.view,
widgets: {
...PageRootBlockSpec.view.widgets,
[AFFINE_AI_PANEL_WIDGET]: literal`${unsafeStatic(
AFFINE_AI_PANEL_WIDGET
)}`,
AIPageRootWatcher,
{
setup: di => {
di.override(WidgetViewMapIdentifier('affine:page'), () => {
return {
...pageRootWidgetViewMap,
[AFFINE_AI_PANEL_WIDGET]: literal`${unsafeStatic(
AFFINE_AI_PANEL_WIDGET
)}`,
};
});
},
},
setup: (slots, disposableGroup) => {
PageRootBlockSpec.setup?.(slots, disposableGroup);
disposableGroup.add(
slots.widgetConnected.on(view => {
if (view.component instanceof AffineAIPanelWidget) {
view.component.style.width = '630px';
view.component.config = buildAIPanelConfig(view.component);
setupSpaceEntry(view.component);
}
];
if (view.component instanceof AffineFormatBarWidget) {
setupFormatBarEntry(view.component);
}
class AIEdgelessRootWatcher extends BlockServiceWatcher {
static override readonly flavour = 'affine:page';
if (view.component instanceof AffineSlashMenuWidget) {
setupSlashMenuEntry(view.component);
}
})
);
},
};
export const AIEdgelessRootBlockSpec: BlockSpec = {
...EdgelessRootBlockSpec,
view: {
...EdgelessRootBlockSpec.view,
widgets: {
...EdgelessRootBlockSpec.view.widgets,
[AFFINE_EDGELESS_COPILOT_WIDGET]: literal`${unsafeStatic(
AFFINE_EDGELESS_COPILOT_WIDGET
)}`,
[AFFINE_AI_PANEL_WIDGET]: literal`${unsafeStatic(
AFFINE_AI_PANEL_WIDGET
)}`,
},
},
setup(slots, disposableGroup) {
EdgelessRootBlockSpec.setup?.(slots, disposableGroup);
slots.widgetConnected.on(view => {
override mounted() {
super.mounted();
this.blockService.specSlots.widgetConnected.on(view => {
if (view.component instanceof AffineAIPanelWidget) {
view.component.style.width = '430px';
view.component.config = buildAIPanelConfig(view.component);
@@ -102,55 +104,93 @@ export const AIEdgelessRootBlockSpec: BlockSpec = {
setupSlashMenuEntry(view.component);
}
});
},
};
}
}
export const AIParagraphBlockSpec: BlockSpec = {
...ParagraphBlockSpec,
setup(slots, disposableGroup) {
ParagraphBlockSpec.setup?.(slots, disposableGroup);
slots.mounted.on(({ service }) => {
assertInstanceOf(service, ParagraphBlockService);
service.placeholderGenerator = model => {
if (model.type === 'text') {
return "Type '/' for commands, 'space' for AI";
}
const placeholders = {
h1: 'Heading 1',
h2: 'Heading 2',
h3: 'Heading 3',
h4: 'Heading 4',
h5: 'Heading 5',
h6: 'Heading 6',
quote: '',
export const AIEdgelessRootBlockSpec: ExtensionType[] = [
...EdgelessRootBlockSpec,
AIEdgelessRootWatcher,
{
setup: di => {
di.override(WidgetViewMapIdentifier('affine:page'), () => {
return {
...edgelessRootWigetViewMap,
[AFFINE_EDGELESS_COPILOT_WIDGET]: literal`${unsafeStatic(
AFFINE_EDGELESS_COPILOT_WIDGET
)}`,
[AFFINE_AI_PANEL_WIDGET]: literal`${unsafeStatic(
AFFINE_AI_PANEL_WIDGET
)}`,
};
return placeholders[model.type];
};
});
});
},
},
};
];
export const AICodeBlockSpec: BlockSpec = {
...CodeBlockSpec,
setup(slots, disposableGroup) {
CodeBlockSpec.setup?.(slots, disposableGroup);
slots.widgetConnected.on(view => {
class AIParagraphBlockWatcher extends BlockServiceWatcher {
static override readonly flavour = 'affine:paragraph';
override mounted() {
super.mounted();
const service = this.blockService;
assertInstanceOf(service, ParagraphBlockService);
service.placeholderGenerator = model => {
if (model.type === 'text') {
return "Type '/' for commands, 'space' for AI";
}
const placeholders = {
h1: 'Heading 1',
h2: 'Heading 2',
h3: 'Heading 3',
h4: 'Heading 4',
h5: 'Heading 5',
h6: 'Heading 6',
quote: '',
};
return placeholders[model.type];
};
}
}
export const AIParagraphBlockSpec: ExtensionType[] = [
...ParagraphBlockSpec,
AIParagraphBlockWatcher,
];
class AICodeBlockWatcher extends BlockServiceWatcher {
static override readonly flavour = 'affine:code';
override mounted() {
super.mounted();
const service = this.blockService;
service.specSlots.widgetConnected.on(view => {
if (view.component instanceof AffineCodeToolbarWidget) {
setupCodeToolbarEntry(view.component);
}
});
},
};
}
}
export const AIImageBlockSpec: BlockSpec = {
...ImageBlockSpec,
setup(slots, disposableGroup) {
ImageBlockSpec.setup?.(slots, disposableGroup);
slots.widgetConnected.on(view => {
export const AICodeBlockSpec: ExtensionType[] = [
...CodeBlockSpec,
AICodeBlockWatcher,
];
class AIImageBlockWatcher extends BlockServiceWatcher {
static override readonly flavour = 'affine:image';
override mounted() {
super.mounted();
this.blockService.specSlots.widgetConnected.on(view => {
if (view.component instanceof AffineImageToolbarWidget) {
setupImageToolbarEntry(view.component);
}
});
},
};
}
}
export const AIImageBlockSpec: ExtensionType[] = [
...ImageBlockSpec,
AIImageBlockWatcher,
];
@@ -17,6 +17,7 @@ import type { BaseSelection, EditorHost } from '@blocksuite/block-std';
import { ShadowlessElement, WithDisposable } from '@blocksuite/block-std';
import {
type AIError,
DocModeProvider,
isInsidePageEditor,
PaymentRequiredError,
UnauthorizedError,
@@ -161,7 +162,7 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
this._selectionValue = this.host.selection.value;
})
);
const { docModeService } = this.host.spec.getService('affine:page');
const docModeService = this.host.std.get(DocModeProvider);
disposables.add(docModeService.onModeChange(() => this.requestUpdate()));
}
}
@@ -160,7 +160,7 @@ export class ChatPanel extends WithDisposable(ShadowlessElement) {
private readonly _cleanupHistories = async () => {
const notification =
this.host.std.spec.getService('affine:page').notificationService;
this.host.std.getService('affine:page').notificationService;
if (!notification) return;
if (
@@ -1,9 +1,6 @@
import '../../_common/components/ask-ai-button';
import type {
AffineCodeToolbarWidget,
CodeBlockComponent,
} from '@blocksuite/blocks';
import type { AffineCodeToolbarWidget } from '@blocksuite/blocks';
import { html } from 'lit';
const AICodeItemGroups = buildAICodeItemGroups();
@@ -14,42 +11,34 @@ const buttonOptions: AskAIButtonOptions = {
import type { AskAIButtonOptions } from '../../_common/components/ask-ai-button';
import { buildAICodeItemGroups } from '../../_common/config';
import { AIStarIcon } from '../../_common/icons';
export function setupCodeToolbarEntry(codeToolbar: AffineCodeToolbarWidget) {
const onAskAIClick = () => {
const { host } = codeToolbar;
const { selection } = host;
const codeBlock = codeToolbar.block;
selection.setGroup('note', [
selection.create('block', { blockId: codeBlock.blockId }),
]);
};
codeToolbar.setupDefaultConfig();
codeToolbar.addItems(
[
{
type: 'custom',
name: 'Ask AI',
tooltip: 'Ask AI',
icon: AIStarIcon,
showWhen: CodeBlockComponent => !CodeBlockComponent.doc.readonly,
render(codeBlock: CodeBlockComponent, onClick?: () => void) {
return html`<ask-ai-button
class="code-toolbar-button ask-ai"
.host=${codeBlock.host}
.actionGroups=${AICodeItemGroups}
.toggleType=${'click'}
.options=${buttonOptions}
@click=${(e: MouseEvent) => {
e.stopPropagation();
onAskAIClick();
onClick?.();
}}
></ask-ai-button>`;
},
codeToolbar.addPrimaryItems([
{
type: 'ask-ai',
when: ({ doc }) => !doc.readonly,
generate: ({ host, blockComponent }) => {
return {
action: () => {
const { selection } = host;
selection.setGroup('note', [
selection.create('block', { blockId: blockComponent.blockId }),
]);
},
render: item =>
html`<ask-ai-button
class="code-toolbar-button ask-ai"
.host=${host}
.actionGroups=${AICodeItemGroups}
.toggleType=${'click'}
.options=${buttonOptions}
@click=${(e: MouseEvent) => {
e.stopPropagation();
item.action();
}}
></ask-ai-button>`,
};
},
],
0
);
},
]);
}
@@ -1,5 +1,6 @@
import type {
AIItemGroupConfig,
DocMode,
EdgelessCopilotWidget,
EdgelessElementToolbarWidget,
EdgelessRootBlockComponent,
@@ -27,7 +28,7 @@ export function setupEdgelessElementToolbarEntry(
const chain = edgeless.service.std.command.chain();
const filteredGroups = edgelessActionGroups.reduce((pre, group) => {
const filtered = group.items.filter(item =>
item.showWhen?.(chain, 'edgeless', edgeless.host)
item.showWhen?.(chain, 'edgeless' as DocMode, edgeless.host)
);
if (filtered.length > 0) pre.push({ ...group, items: filtered });
@@ -1,9 +1,6 @@
import '../../_common/components/ask-ai-button';
import type {
AffineImageToolbarWidget,
ImageBlockComponent,
} from '@blocksuite/blocks';
import type { AffineImageToolbarWidget } from '@blocksuite/blocks';
import { html } from 'lit';
import type { AskAIButtonOptions } from '../../_common/components/ask-ai-button';
@@ -17,34 +14,33 @@ const buttonOptions: AskAIButtonOptions = {
};
export function setupImageToolbarEntry(imageToolbar: AffineImageToolbarWidget) {
const onAskAIClick = () => {
const { host } = imageToolbar;
const { selection } = host;
const imageBlock = imageToolbar.block;
selection.setGroup('note', [
selection.create('image', { blockId: imageBlock.blockId }),
]);
};
imageToolbar.buildDefaultConfig();
imageToolbar.addConfigItems(
imageToolbar.addPrimaryItems(
[
{
type: 'custom',
render(imageBlock: ImageBlockComponent, onClick?: () => void) {
return html`<ask-ai-button
class="image-toolbar-button ask-ai"
.host=${imageBlock.host}
.actionGroups=${AIImageItemGroups}
.toggleType=${'click'}
.options=${buttonOptions}
@click=${(e: MouseEvent) => {
e.stopPropagation();
onAskAIClick();
onClick?.();
}}
></ask-ai-button>`;
type: 'ask-ai',
when: ({ doc }) => !doc.readonly,
generate: ({ host, blockComponent }) => {
return {
action: () => {
const { selection } = host;
selection.setGroup('note', [
selection.create('image', { blockId: blockComponent.blockId }),
]);
},
render: item =>
html`<ask-ai-button
class="image-toolbar-button ask-ai"
.host=${host}
.actionGroups=${AIImageItemGroups}
.toggleType=${'click'}
.options=${buttonOptions}
@click=${(e: MouseEvent) => {
e.stopPropagation();
item.action();
}}
></ask-ai-button>`,
};
},
showWhen: imageBlockComponent => !imageBlockComponent.doc.readonly,
},
],
0
@@ -1,10 +1,11 @@
import type {
AffineAIPanelWidget,
AffineSlashMenuActionItem,
AffineSlashMenuContext,
AffineSlashMenuItem,
AffineSlashSubMenu,
AIItemConfig,
import {
type AffineAIPanelWidget,
type AffineSlashMenuActionItem,
type AffineSlashMenuContext,
type AffineSlashMenuItem,
type AffineSlashSubMenu,
type AIItemConfig,
DocModeProvider,
} from '@blocksuite/blocks';
import {
AFFINE_AI_PANEL_WIDGET,
@@ -38,9 +39,8 @@ export function setupSlashMenuEntry(slashMenu: AffineSlashMenuWidget) {
if (affineAIPanelWidget === null) return false;
const chain = rootComponent.host.command.chain();
const editorMode = rootComponent.service.docModeService.getMode(
rootComponent.doc.id
);
const docModeService = rootComponent.std.get(DocModeProvider);
const editorMode = docModeService.getMode(rootComponent.doc.id);
return item?.showWhen?.(chain, editorMode, rootComponent.host) ?? true;
};
@@ -1,4 +1,4 @@
import type { EditorHost } from '@blocksuite/block-std';
import { BlockStdScope, type EditorHost } from '@blocksuite/block-std';
import { WithDisposable } from '@blocksuite/block-std';
import {
type AffineAIPanelWidgetConfig,
@@ -207,7 +207,10 @@ export class AISlidesRenderer extends WithDisposable(LitElement) {
class="edgeless-container affine-edgeless-viewport"
${ref(this._editorContainer)}
>
${this.host.renderSpecPortal(this._doc, EdgelessEditorBlockSpecs)}
${new BlockStdScope({
doc: this._doc,
extensions: EdgelessEditorBlockSpecs,
}).render()}
</div>
<div class="mask"></div>
</div>`;
@@ -1,7 +1,11 @@
import { type EditorHost, WithDisposable } from '@blocksuite/block-std';
import type {
AffineAIPanelState,
AffineAIPanelWidgetConfig,
import {
BlockStdScope,
type EditorHost,
WithDisposable,
} from '@blocksuite/block-std';
import {
type AffineAIPanelState,
type AffineAIPanelWidgetConfig,
} from '@blocksuite/blocks';
import {
CodeBlockComponent,
@@ -271,7 +275,10 @@ export class AIAnswerText extends WithDisposable(LitElement) {
${keyed(
this._doc,
html`<div class="ai-answer-text-editor affine-page-viewport">
${this.host.renderSpecPortal(this._doc, CustomPageEditorBlockSpecs)}
${new BlockStdScope({
doc: this._doc,
extensions: CustomPageEditorBlockSpecs,
}).render()}
</div>`
)}
</div>
@@ -8,6 +8,7 @@ import {
type AIError,
CanvasElementType,
ConnectorMode,
DocModeProvider,
type EdgelessRootService,
} from '@blocksuite/blocks';
import {
@@ -37,11 +38,11 @@ export class AIChatBlockPeekView extends LitElement {
static override styles = PeekViewStyles;
private get _rootService() {
return this.host.spec.getService('affine:page');
return this.host.std.getService('affine:page');
}
private get _modeService() {
return this._rootService.docModeService;
return this.host.std.get(DocModeProvider);
}
private get parentSessionId() {
@@ -12,7 +12,7 @@ import {
} from './template';
export const PPTBuilder = (host: EditorHost) => {
const service = host.spec.getService<EdgelessRootService>('affine:page');
const service = host.std.getService<EdgelessRootService>('affine:page');
const docs: PPTDoc[] = [];
const contents: unknown[] = [];
const allImages: TemplateImage[][] = [];
@@ -1,3 +1,10 @@
import {
BlockFlavourIdentifier,
BlockServiceIdentifier,
BlockViewIdentifier,
type ExtensionType,
StdIdentifier,
} from '@blocksuite/block-std';
import { PageEditorBlockSpecs, PageRootService } from '@blocksuite/blocks';
import { literal } from 'lit/static-html.js';
@@ -8,15 +15,19 @@ class CustomPageRootService extends PageRootService {
override loadFonts() {}
}
export const CustomPageEditorBlockSpecs = PageEditorBlockSpecs.map(spec => {
if (spec.schema.model.flavour === 'affine:page') {
return {
...spec,
service: CustomPageRootService,
view: {
component: literal`affine-page-root`,
},
};
}
return spec;
});
export const CustomPageEditorBlockSpecs: ExtensionType[] = [
...PageEditorBlockSpecs,
{
setup: di => {
di.override(
BlockServiceIdentifier('affine:page'),
CustomPageRootService,
[StdIdentifier, BlockFlavourIdentifier('affine:page')]
);
di.override(
BlockViewIdentifier('affine:page'),
() => literal`affine-page-root`
);
},
},
];
@@ -40,7 +40,7 @@ export function isMindmapChild(ele: BlockSuite.EdgelessModel) {
}
export function getService(host: EditorHost) {
const edgelessService = host.spec.getService(
const edgelessService = host.std.getService(
'affine:page'
) as EdgelessRootService;
@@ -18,7 +18,7 @@ import { getEdgelessCopilotWidget, getService } from './edgeless';
import { getContentFromSlice } from './markdown-utils';
export const getRootService = (host: EditorHost) => {
return host.std.spec.getService('affine:page');
return host.std.getService('affine:page');
};
export function getEdgelessRootFromEditor(editor: EditorHost) {
@@ -30,7 +30,7 @@ export function getEdgelessRootFromEditor(editor: EditorHost) {
return edgelessRoot;
}
export function getEdgelessService(editor: EditorHost) {
const rootService = editor.std.spec.getService('affine:page');
const rootService = editor.std.getService('affine:page');
if (rootService instanceof EdgelessRootService) {
return rootService;
}