diff --git a/blocksuite/affine/block-root/src/common-specs/index.ts b/blocksuite/affine/block-root/src/common-specs/index.ts index 9396f36763..1cc89358d7 100644 --- a/blocksuite/affine/block-root/src/common-specs/index.ts +++ b/blocksuite/affine/block-root/src/common-specs/index.ts @@ -6,19 +6,19 @@ import { PageViewportServiceExtension, ThemeService, } from '@blocksuite/affine-shared/services'; +import { dragHandleWidget } from '@blocksuite/affine-widget-drag-handle'; +import { docRemoteSelectionWidget } from '@blocksuite/affine-widget-remote-selection'; +import { scrollAnchoringWidget } from '@blocksuite/affine-widget-scroll-anchoring'; import { FlavourExtension } from '@blocksuite/block-std'; import type { ExtensionType } from '@blocksuite/store'; import { RootBlockAdapterExtensions } from '../adapters/extension'; import { - docRemoteSelectionWidget, - dragHandleWidget, embedCardToolbarWidget, formatBarWidget, innerModalWidget, linkedDocWidget, modalWidget, - scrollAnchoringWidget, slashMenuWidget, viewportOverlayWidget, } from './widgets'; diff --git a/blocksuite/affine/block-root/src/common-specs/widgets.ts b/blocksuite/affine/block-root/src/common-specs/widgets.ts index b6076d1cdf..ecb6207427 100644 --- a/blocksuite/affine/block-root/src/common-specs/widgets.ts +++ b/blocksuite/affine/block-root/src/common-specs/widgets.ts @@ -1,5 +1,3 @@ -import { AFFINE_DRAG_HANDLE_WIDGET } from '@blocksuite/affine-widget-drag-handle'; -import { AFFINE_DOC_REMOTE_SELECTION_WIDGET } from '@blocksuite/affine-widget-remote-selection'; import { AFFINE_SCROLL_ANCHORING_WIDGET } from '@blocksuite/affine-widget-scroll-anchoring'; import { WidgetViewExtension } from '@blocksuite/block-std'; import { literal, unsafeStatic } from 'lit/static-html.js'; @@ -32,11 +30,6 @@ export const linkedDocWidget = WidgetViewExtension( AFFINE_LINKED_DOC_WIDGET, literal`${unsafeStatic(AFFINE_LINKED_DOC_WIDGET)}` ); -export const dragHandleWidget = WidgetViewExtension( - 'affine:page', - AFFINE_DRAG_HANDLE_WIDGET, - literal`${unsafeStatic(AFFINE_DRAG_HANDLE_WIDGET)}` -); export const embedCardToolbarWidget = WidgetViewExtension( 'affine:page', AFFINE_EMBED_CARD_TOOLBAR_WIDGET, @@ -47,11 +40,6 @@ export const formatBarWidget = WidgetViewExtension( AFFINE_FORMAT_BAR_WIDGET, literal`${unsafeStatic(AFFINE_FORMAT_BAR_WIDGET)}` ); -export const docRemoteSelectionWidget = WidgetViewExtension( - 'affine:page', - AFFINE_DOC_REMOTE_SELECTION_WIDGET, - literal`${unsafeStatic(AFFINE_DOC_REMOTE_SELECTION_WIDGET)}` -); export const viewportOverlayWidget = WidgetViewExtension( 'affine:page', AFFINE_VIEWPORT_OVERLAY_WIDGET, diff --git a/blocksuite/affine/block-root/src/edgeless/edgeless-root-spec.ts b/blocksuite/affine/block-root/src/edgeless/edgeless-root-spec.ts index dd4502bde6..3e7c5607ff 100644 --- a/blocksuite/affine/block-root/src/edgeless/edgeless-root-spec.ts +++ b/blocksuite/affine/block-root/src/edgeless/edgeless-root-spec.ts @@ -1,6 +1,6 @@ -import { AFFINE_EDGELESS_AUTO_CONNECT_WIDGET } from '@blocksuite/affine-widget-edgeless-auto-connect'; -import { AFFINE_FRAME_TITLE_WIDGET } from '@blocksuite/affine-widget-frame-title'; -import { AFFINE_EDGELESS_REMOTE_SELECTION_WIDGET } from '@blocksuite/affine-widget-remote-selection'; +import { autoConnectWidget } from '@blocksuite/affine-widget-edgeless-auto-connect'; +import { frameTitleWidget } from '@blocksuite/affine-widget-frame-title'; +import { edgelessRemoteSelectionWidget } from '@blocksuite/affine-widget-remote-selection'; import { BlockServiceWatcher, BlockViewExtension, @@ -20,31 +20,16 @@ import { EDGELESS_SELECTED_RECT_WIDGET } from './components/rects/edgeless-selec import { EDGELESS_TOOLBAR_WIDGET } from './components/toolbar/edgeless-toolbar.js'; import { EdgelessRootService } from './edgeless-root-service.js'; -export const edgelessRemoteSelectionWidget = WidgetViewExtension( - 'affine:page', - AFFINE_EDGELESS_REMOTE_SELECTION_WIDGET, - literal`${unsafeStatic(AFFINE_EDGELESS_REMOTE_SELECTION_WIDGET)}` -); export const edgelessZoomToolbarWidget = WidgetViewExtension( 'affine:page', AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET, literal`${unsafeStatic(AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET)}` ); -export const frameTitleWidget = WidgetViewExtension( - 'affine:page', - AFFINE_FRAME_TITLE_WIDGET, - literal`${unsafeStatic(AFFINE_FRAME_TITLE_WIDGET)}` -); export const elementToolbarWidget = WidgetViewExtension( 'affine:page', EDGELESS_ELEMENT_TOOLBAR_WIDGET, literal`${unsafeStatic(EDGELESS_ELEMENT_TOOLBAR_WIDGET)}` ); -export const autoConnectWidget = WidgetViewExtension( - 'affine:page', - AFFINE_EDGELESS_AUTO_CONNECT_WIDGET, - literal`${unsafeStatic(AFFINE_EDGELESS_AUTO_CONNECT_WIDGET)}` -); export const edgelessDraggingAreaWidget = WidgetViewExtension( 'affine:page', EDGELESS_DRAGGING_AREA_WIDGET, diff --git a/blocksuite/affine/shared/src/utils/spec/spec-builder.ts b/blocksuite/affine/shared/src/utils/spec/spec-builder.ts index bbe86d65c8..4dd277341e 100644 --- a/blocksuite/affine/shared/src/utils/spec/spec-builder.ts +++ b/blocksuite/affine/shared/src/utils/spec/spec-builder.ts @@ -13,10 +13,20 @@ export class SpecBuilder { extend(extensions: ExtensionType[]) { this._value = [...this._value, ...extensions]; + return this; } omit(target: ExtensionType) { this._value = this._value.filter(extension => extension !== target); + return this; + } + + hasAll(target: ExtensionType[]) { + return target.every(t => this._value.includes(t)); + } + + hasOneOf(target: ExtensionType[]) { + return target.some(t => this._value.includes(t)); } replace(target: ExtensionType[], newExtension: ExtensionType[]) { @@ -24,5 +34,6 @@ export class SpecBuilder { ...this._value.filter(extension => !target.includes(extension)), ...newExtension, ]; + return this; } } diff --git a/blocksuite/affine/widget-drag-handle/src/index.ts b/blocksuite/affine/widget-drag-handle/src/index.ts index 1703f9557f..e6882a6849 100644 --- a/blocksuite/affine/widget-drag-handle/src/index.ts +++ b/blocksuite/affine/widget-drag-handle/src/index.ts @@ -1,4 +1,15 @@ +import { WidgetViewExtension } from '@blocksuite/block-std'; +import { literal, unsafeStatic } from 'lit/static-html.js'; + +import { AFFINE_DRAG_HANDLE_WIDGET } from './consts'; + export * from './consts'; export * from './drag-handle'; export * from './utils'; export type { DragBlockPayload } from './watchers/drag-event-watcher'; + +export const dragHandleWidget = WidgetViewExtension( + 'affine:page', + AFFINE_DRAG_HANDLE_WIDGET, + literal`${unsafeStatic(AFFINE_DRAG_HANDLE_WIDGET)}` +); diff --git a/blocksuite/affine/widget-edgeless-auto-connect/src/index.ts b/blocksuite/affine/widget-edgeless-auto-connect/src/index.ts index bcc4322ca4..01b857d8aa 100644 --- a/blocksuite/affine/widget-edgeless-auto-connect/src/index.ts +++ b/blocksuite/affine/widget-edgeless-auto-connect/src/index.ts @@ -13,7 +13,7 @@ import { } from '@blocksuite/affine-model'; import { FeatureFlagService } from '@blocksuite/affine-shared/services'; import { matchModels, stopPropagation } from '@blocksuite/affine-shared/utils'; -import { WidgetComponent } from '@blocksuite/block-std'; +import { WidgetComponent, WidgetViewExtension } from '@blocksuite/block-std'; import { type GfxController, GfxControllerIdentifier, @@ -28,6 +28,7 @@ import { css, html, nothing, type TemplateResult } from 'lit'; import { state } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; import { styleMap } from 'lit/directives/style-map.js'; +import { literal, unsafeStatic } from 'lit/static-html.js'; const PAGE_VISIBLE_INDEX_LABEL_WIDTH = 44; const PAGE_VISIBLE_INDEX_LABEL_HEIGHT = 24; @@ -613,6 +614,12 @@ export class EdgelessAutoConnectWidget extends WidgetComponent { private accessor _show = false; } +export const autoConnectWidget = WidgetViewExtension( + 'affine:page', + AFFINE_EDGELESS_AUTO_CONNECT_WIDGET, + literal`${unsafeStatic(AFFINE_EDGELESS_AUTO_CONNECT_WIDGET)}` +); + declare global { interface HTMLElementTagNameMap { 'affine-edgeless-auto-connect-widget': EdgelessAutoConnectWidget; diff --git a/blocksuite/affine/widget-frame-title/src/index.ts b/blocksuite/affine/widget-frame-title/src/index.ts index bd694eafad..1c5f8fd518 100644 --- a/blocksuite/affine/widget-frame-title/src/index.ts +++ b/blocksuite/affine/widget-frame-title/src/index.ts @@ -1,7 +1,8 @@ import { FrameBlockModel, type RootBlockModel } from '@blocksuite/affine-model'; -import { WidgetComponent } from '@blocksuite/block-std'; +import { WidgetComponent, WidgetViewExtension } from '@blocksuite/block-std'; import { html } from 'lit'; import { repeat } from 'lit/directives/repeat.js'; +import { literal, unsafeStatic } from 'lit/static-html.js'; import type { AffineFrameTitle } from './frame-title.js'; @@ -36,3 +37,9 @@ export class AffineFrameTitleWidget extends WidgetComponent { } export * from './styles.js'; + +export const frameTitleWidget = WidgetViewExtension( + 'affine:page', + AFFINE_FRAME_TITLE_WIDGET, + literal`${unsafeStatic(AFFINE_FRAME_TITLE_WIDGET)}` +); diff --git a/blocksuite/affine/widget-remote-selection/src/index.ts b/blocksuite/affine/widget-remote-selection/src/index.ts index 0806b79eaa..92a9b7d6a9 100644 --- a/blocksuite/affine/widget-remote-selection/src/index.ts +++ b/blocksuite/affine/widget-remote-selection/src/index.ts @@ -1,6 +1,20 @@ -import type * as CommandsType from '@blocksuite/affine-shared/commands'; +import { WidgetViewExtension } from '@blocksuite/block-std'; +import { literal, unsafeStatic } from 'lit/static-html.js'; -declare type _GLOBAL_ = typeof CommandsType; +import { AFFINE_DOC_REMOTE_SELECTION_WIDGET } from './doc'; +import { AFFINE_EDGELESS_REMOTE_SELECTION_WIDGET } from './edgeless'; export * from './doc'; export * from './edgeless'; + +export const docRemoteSelectionWidget = WidgetViewExtension( + 'affine:page', + AFFINE_DOC_REMOTE_SELECTION_WIDGET, + literal`${unsafeStatic(AFFINE_DOC_REMOTE_SELECTION_WIDGET)}` +); + +export const edgelessRemoteSelectionWidget = WidgetViewExtension( + 'affine:page', + AFFINE_EDGELESS_REMOTE_SELECTION_WIDGET, + literal`${unsafeStatic(AFFINE_EDGELESS_REMOTE_SELECTION_WIDGET)}` +); diff --git a/blocksuite/affine/widget-scroll-anchoring/src/index.ts b/blocksuite/affine/widget-scroll-anchoring/src/index.ts index 43b770aced..90513a8c78 100644 --- a/blocksuite/affine/widget-scroll-anchoring/src/index.ts +++ b/blocksuite/affine/widget-scroll-anchoring/src/index.ts @@ -1 +1,12 @@ +import { WidgetViewExtension } from '@blocksuite/block-std'; +import { literal, unsafeStatic } from 'lit/static-html.js'; + +import { AFFINE_SCROLL_ANCHORING_WIDGET } from './scroll-anchoring.js'; + export * from './scroll-anchoring.js'; + +export const scrollAnchoringWidget = WidgetViewExtension( + 'affine:page', + AFFINE_SCROLL_ANCHORING_WIDGET, + literal`${unsafeStatic(AFFINE_SCROLL_ANCHORING_WIDGET)}` +); diff --git a/packages/frontend/core/src/blocksuite/ai/ai-spec.ts b/packages/frontend/core/src/blocksuite/ai/ai-spec.ts deleted file mode 100644 index 57cb64f01c..0000000000 --- a/packages/frontend/core/src/blocksuite/ai/ai-spec.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { - BlockServiceWatcher, - WidgetViewExtension, -} from '@blocksuite/affine/block-std'; -import { - AffineCodeToolbarWidget, - AffineFormatBarWidget, - AffineImageToolbarWidget, - AffineSlashMenuWidget, - CodeBlockSpec, - EdgelessElementToolbarWidget, - EdgelessRootBlockSpec, - ImageBlockSpec, - PageRootBlockSpec, - ParagraphBlockService, - ParagraphBlockSpec, -} from '@blocksuite/affine/blocks'; -import { assertInstanceOf } from '@blocksuite/affine/global/utils'; -import type { ExtensionType } from '@blocksuite/affine/store'; -import type { FrameworkProvider } from '@toeverything/infra'; -import { literal, unsafeStatic } from 'lit/static-html.js'; - -import { buildAIPanelConfig } from './ai-panel'; -import { setupCodeToolbarAIEntry } from './entries/code-toolbar/setup-code-toolbar'; -import { - setupEdgelessCopilot, - setupEdgelessElementToolbarAIEntry, -} from './entries/edgeless/index'; -import { setupFormatBarAIEntry } from './entries/format-bar/setup-format-bar'; -import { setupImageToolbarAIEntry } from './entries/image-toolbar/setup-image-toolbar'; -import { setupSlashMenuAIEntry } from './entries/slash-menu/setup-slash-menu'; -import { setupSpaceAIEntry } from './entries/space/setup-space'; -import { CopilotTool } from './tool/copilot-tool'; -import { - AFFINE_AI_PANEL_WIDGET, - AffineAIPanelWidget, -} from './widgets/ai-panel/ai-panel'; -import { - AFFINE_EDGELESS_COPILOT_WIDGET, - EdgelessCopilotWidget, -} from './widgets/edgeless-copilot'; - -function getAIPageRootWatcher(framework: FrameworkProvider) { - 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, framework); - setupSpaceAIEntry(view.component); - } - - if (view.component instanceof AffineFormatBarWidget) { - setupFormatBarAIEntry(view.component); - } - - if (view.component instanceof AffineSlashMenuWidget) { - setupSlashMenuAIEntry(view.component); - } - }); - } - } - return AIPageRootWatcher; -} - -const aiPanelWidget = WidgetViewExtension( - 'affine:page', - AFFINE_AI_PANEL_WIDGET, - literal`${unsafeStatic(AFFINE_AI_PANEL_WIDGET)}` -); - -const edgelessCopilotWidget = WidgetViewExtension( - 'affine:page', - AFFINE_EDGELESS_COPILOT_WIDGET, - literal`${unsafeStatic(AFFINE_EDGELESS_COPILOT_WIDGET)}` -); - -export function createAIPageRootBlockSpec( - framework: FrameworkProvider -): ExtensionType[] { - return [...PageRootBlockSpec, aiPanelWidget, getAIPageRootWatcher(framework)]; -} - -function getAIEdgelessRootWatcher(framework: FrameworkProvider) { - class AIEdgelessRootWatcher 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 = '430px'; - view.component.config = buildAIPanelConfig(view.component, framework); - setupSpaceAIEntry(view.component); - } - - if (view.component instanceof EdgelessCopilotWidget) { - setupEdgelessCopilot(view.component); - } - - if (view.component instanceof EdgelessElementToolbarWidget) { - setupEdgelessElementToolbarAIEntry(view.component); - } - - if (view.component instanceof AffineFormatBarWidget) { - setupFormatBarAIEntry(view.component); - } - - if (view.component instanceof AffineSlashMenuWidget) { - setupSlashMenuAIEntry(view.component); - } - }); - } - } - return AIEdgelessRootWatcher; -} - -export function createAIEdgelessRootBlockSpec( - framework: FrameworkProvider -): ExtensionType[] { - return [ - ...EdgelessRootBlockSpec, - CopilotTool, - aiPanelWidget, - edgelessCopilotWidget, - getAIEdgelessRootWatcher(framework), - ]; -} - -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) { - setupCodeToolbarAIEntry(view.component); - } - }); - } -} - -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) { - setupImageToolbarAIEntry(view.component); - } - }); - } -} - -export const AIImageBlockSpec: ExtensionType[] = [ - ...ImageBlockSpec, - AIImageBlockWatcher, -]; diff --git a/packages/frontend/core/src/blocksuite/ai/extensions/ai-code.ts b/packages/frontend/core/src/blocksuite/ai/extensions/ai-code.ts new file mode 100644 index 0000000000..188c9dd101 --- /dev/null +++ b/packages/frontend/core/src/blocksuite/ai/extensions/ai-code.ts @@ -0,0 +1,27 @@ +import { BlockServiceWatcher } from '@blocksuite/affine/block-std'; +import { + AffineCodeToolbarWidget, + CodeBlockSpec, +} from '@blocksuite/affine/blocks'; +import type { ExtensionType } from '@blocksuite/affine/store'; + +import { setupCodeToolbarAIEntry } from '../entries/code-toolbar/setup-code-toolbar'; + +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) { + setupCodeToolbarAIEntry(view.component); + } + }); + } +} + +export const AICodeBlockSpec: ExtensionType[] = [ + ...CodeBlockSpec, + AICodeBlockWatcher, +]; diff --git a/packages/frontend/core/src/blocksuite/ai/extensions/ai-edgeless-root.ts b/packages/frontend/core/src/blocksuite/ai/extensions/ai-edgeless-root.ts new file mode 100644 index 0000000000..9dafec75d4 --- /dev/null +++ b/packages/frontend/core/src/blocksuite/ai/extensions/ai-edgeless-root.ts @@ -0,0 +1,73 @@ +import { BlockServiceWatcher } from '@blocksuite/affine/block-std'; +import { + AffineFormatBarWidget, + AffineSlashMenuWidget, + EdgelessElementToolbarWidget, + EdgelessRootBlockSpec, +} from '@blocksuite/affine/blocks'; +import type { ExtensionType } from '@blocksuite/affine/store'; +import type { FrameworkProvider } from '@toeverything/infra'; + +import { buildAIPanelConfig } from '../ai-panel'; +import { + setupEdgelessCopilot, + setupEdgelessElementToolbarAIEntry, +} from '../entries/edgeless/index'; +import { setupFormatBarAIEntry } from '../entries/format-bar/setup-format-bar'; +import { setupSlashMenuAIEntry } from '../entries/slash-menu/setup-slash-menu'; +import { setupSpaceAIEntry } from '../entries/space/setup-space'; +import { CopilotTool } from '../tool/copilot-tool'; +import { + AffineAIPanelWidget, + aiPanelWidget, +} from '../widgets/ai-panel/ai-panel'; +import { + EdgelessCopilotWidget, + edgelessCopilotWidget, +} from '../widgets/edgeless-copilot'; + +export function createAIEdgelessRootBlockSpec( + framework: FrameworkProvider +): ExtensionType[] { + return [ + ...EdgelessRootBlockSpec, + CopilotTool, + aiPanelWidget, + edgelessCopilotWidget, + getAIEdgelessRootWatcher(framework), + ]; +} + +function getAIEdgelessRootWatcher(framework: FrameworkProvider) { + class AIEdgelessRootWatcher 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 = '430px'; + view.component.config = buildAIPanelConfig(view.component, framework); + setupSpaceAIEntry(view.component); + } + + if (view.component instanceof EdgelessCopilotWidget) { + setupEdgelessCopilot(view.component); + } + + if (view.component instanceof EdgelessElementToolbarWidget) { + setupEdgelessElementToolbarAIEntry(view.component); + } + + if (view.component instanceof AffineFormatBarWidget) { + setupFormatBarAIEntry(view.component); + } + + if (view.component instanceof AffineSlashMenuWidget) { + setupSlashMenuAIEntry(view.component); + } + }); + } + } + return AIEdgelessRootWatcher; +} diff --git a/packages/frontend/core/src/blocksuite/ai/extensions/ai-image.ts b/packages/frontend/core/src/blocksuite/ai/extensions/ai-image.ts new file mode 100644 index 0000000000..9d887e4b16 --- /dev/null +++ b/packages/frontend/core/src/blocksuite/ai/extensions/ai-image.ts @@ -0,0 +1,26 @@ +import { BlockServiceWatcher } from '@blocksuite/affine/block-std'; +import { + AffineImageToolbarWidget, + ImageBlockSpec, +} from '@blocksuite/affine/blocks'; +import type { ExtensionType } from '@blocksuite/affine/store'; + +import { setupImageToolbarAIEntry } from '../entries/image-toolbar/setup-image-toolbar'; + +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) { + setupImageToolbarAIEntry(view.component); + } + }); + } +} + +export const AIImageBlockSpec: ExtensionType[] = [ + ...ImageBlockSpec, + AIImageBlockWatcher, +]; diff --git a/packages/frontend/core/src/blocksuite/ai/extensions/ai-page-root.ts b/packages/frontend/core/src/blocksuite/ai/extensions/ai-page-root.ts new file mode 100644 index 0000000000..da8f40a24e --- /dev/null +++ b/packages/frontend/core/src/blocksuite/ai/extensions/ai-page-root.ts @@ -0,0 +1,49 @@ +import { BlockServiceWatcher } from '@blocksuite/affine/block-std'; +import { + AffineFormatBarWidget, + AffineSlashMenuWidget, + PageRootBlockSpec, +} from '@blocksuite/affine/blocks'; +import type { ExtensionType } from '@blocksuite/affine/store'; +import type { FrameworkProvider } from '@toeverything/infra'; + +import { buildAIPanelConfig } from '../ai-panel'; +import { setupFormatBarAIEntry } from '../entries/format-bar/setup-format-bar'; +import { setupSlashMenuAIEntry } from '../entries/slash-menu/setup-slash-menu'; +import { setupSpaceAIEntry } from '../entries/space/setup-space'; +import { + AffineAIPanelWidget, + aiPanelWidget, +} from '../widgets/ai-panel/ai-panel'; + +function getAIPageRootWatcher(framework: FrameworkProvider) { + 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, framework); + setupSpaceAIEntry(view.component); + } + + if (view.component instanceof AffineFormatBarWidget) { + setupFormatBarAIEntry(view.component); + } + + if (view.component instanceof AffineSlashMenuWidget) { + setupSlashMenuAIEntry(view.component); + } + }); + } + } + return AIPageRootWatcher; +} + +export function createAIPageRootBlockSpec( + framework: FrameworkProvider +): ExtensionType[] { + return [...PageRootBlockSpec, aiPanelWidget, getAIPageRootWatcher(framework)]; +} diff --git a/packages/frontend/core/src/blocksuite/ai/extensions/ai-paragraph.ts b/packages/frontend/core/src/blocksuite/ai/extensions/ai-paragraph.ts new file mode 100644 index 0000000000..58ed949900 --- /dev/null +++ b/packages/frontend/core/src/blocksuite/ai/extensions/ai-paragraph.ts @@ -0,0 +1,38 @@ +import { BlockServiceWatcher } from '@blocksuite/affine/block-std'; +import { + ParagraphBlockService, + ParagraphBlockSpec, +} from '@blocksuite/affine/blocks'; +import { assertInstanceOf } from '@blocksuite/affine/global/utils'; +import type { ExtensionType } from '@blocksuite/affine/store'; + +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, +]; diff --git a/packages/frontend/core/src/blocksuite/ai/extensions/enable-ai.ts b/packages/frontend/core/src/blocksuite/ai/extensions/enable-ai.ts new file mode 100644 index 0000000000..5db82b9674 --- /dev/null +++ b/packages/frontend/core/src/blocksuite/ai/extensions/enable-ai.ts @@ -0,0 +1,37 @@ +import { + CodeBlockSpec, + EdgelessRootBlockSpec, + ImageBlockSpec, + PageRootBlockSpec, + ParagraphBlockSpec, + type SpecBuilder, +} from '@blocksuite/affine/blocks'; +import type { FrameworkProvider } from '@toeverything/infra'; + +import { AIChatBlockSpec } from '../blocks'; +import { AICodeBlockSpec } from './ai-code'; +import { createAIEdgelessRootBlockSpec } from './ai-edgeless-root'; +import { AIImageBlockSpec } from './ai-image'; +import { createAIPageRootBlockSpec } from './ai-page-root'; +import { AIParagraphBlockSpec } from './ai-paragraph'; + +export function enableAIExtension( + specBuilder: SpecBuilder, + framework: FrameworkProvider +) { + specBuilder.replace(CodeBlockSpec, AICodeBlockSpec); + specBuilder.replace(ImageBlockSpec, AIImageBlockSpec); + specBuilder.replace(ParagraphBlockSpec, AIParagraphBlockSpec); + + if (specBuilder.hasAll(EdgelessRootBlockSpec)) { + const aiEdgeless = createAIEdgelessRootBlockSpec(framework); + specBuilder.replace(EdgelessRootBlockSpec, aiEdgeless); + } + + if (specBuilder.hasAll(PageRootBlockSpec)) { + const aiPage = createAIPageRootBlockSpec(framework); + specBuilder.replace(PageRootBlockSpec, aiPage); + } + + specBuilder.extend(AIChatBlockSpec); +} diff --git a/packages/frontend/core/src/blocksuite/ai/extensions/index.ts b/packages/frontend/core/src/blocksuite/ai/extensions/index.ts new file mode 100644 index 0000000000..566868dc50 --- /dev/null +++ b/packages/frontend/core/src/blocksuite/ai/extensions/index.ts @@ -0,0 +1 @@ +export * from './enable-ai'; diff --git a/packages/frontend/core/src/blocksuite/ai/index.ts b/packages/frontend/core/src/blocksuite/ai/index.ts index 9a7f27cd2f..44cedbe710 100644 --- a/packages/frontend/core/src/blocksuite/ai/index.ts +++ b/packages/frontend/core/src/blocksuite/ai/index.ts @@ -1,10 +1,10 @@ export * from './_common/config'; -export * from './actions/index'; -export * from './ai-spec'; -export { ChatPanel } from './chat-panel/index'; +export * from './actions'; +export { ChatPanel } from './chat-panel'; +export * from './entries'; export * from './entries/edgeless/actions-config'; -export * from './entries/index'; -export * from './messages/index'; +export * from './extensions'; +export * from './messages'; export { AIChatBlockPeekViewTemplate } from './peek-view/chat-block-peek-view'; export * from './provider'; export * from './utils/edgeless'; diff --git a/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/ai-panel.ts b/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/ai-panel.ts index 9438086043..858d4869de 100644 --- a/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/ai-panel.ts +++ b/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/ai-panel.ts @@ -1,4 +1,7 @@ -import { WidgetComponent } from '@blocksuite/affine/block-std'; +import { + WidgetComponent, + WidgetViewExtension, +} from '@blocksuite/affine/block-std'; import { GfxControllerIdentifier } from '@blocksuite/affine/block-std/gfx'; import { AFFINE_FORMAT_BAR_WIDGET, @@ -25,6 +28,7 @@ import { import { css, html, nothing, type PropertyValues } from 'lit'; import { property, query } from 'lit/decorators.js'; import { choose } from 'lit/directives/choose.js'; +import { literal, unsafeStatic } from 'lit/static-html.js'; import type { AIError } from '../../components/ai-item/types.js'; import type { AIPanelGenerating } from './components/index.js'; @@ -543,3 +547,9 @@ export class AffineAIPanelWidget extends WidgetComponent { @property() accessor state: AffineAIPanelState = 'hidden'; } + +export const aiPanelWidget = WidgetViewExtension( + 'affine:page', + AFFINE_AI_PANEL_WIDGET, + literal`${unsafeStatic(AFFINE_AI_PANEL_WIDGET)}` +); diff --git a/packages/frontend/core/src/blocksuite/ai/widgets/edgeless-copilot/index.ts b/packages/frontend/core/src/blocksuite/ai/widgets/edgeless-copilot/index.ts index 2c74a6ccde..36050f54a4 100644 --- a/packages/frontend/core/src/blocksuite/ai/widgets/edgeless-copilot/index.ts +++ b/packages/frontend/core/src/blocksuite/ai/widgets/edgeless-copilot/index.ts @@ -1,4 +1,7 @@ -import { WidgetComponent } from '@blocksuite/affine/block-std'; +import { + WidgetComponent, + WidgetViewExtension, +} from '@blocksuite/affine/block-std'; import { GfxControllerIdentifier } from '@blocksuite/affine/block-std/gfx'; import type { RootBlockModel } from '@blocksuite/affine/blocks'; import { @@ -22,6 +25,7 @@ import { effect } from '@preact/signals-core'; import { css, html, nothing } from 'lit'; import { query, state } from 'lit/decorators.js'; import { styleMap } from 'lit/directives/style-map.js'; +import { literal, unsafeStatic } from 'lit/static-html.js'; import type { AIItemGroupConfig } from '../../components/ai-item/types.js'; import { @@ -290,6 +294,12 @@ export class EdgelessCopilotWidget extends WidgetComponent { accessor selectionElem!: HTMLDivElement; } +export const edgelessCopilotWidget = WidgetViewExtension( + 'affine:page', + AFFINE_EDGELESS_COPILOT_WIDGET, + literal`${unsafeStatic(AFFINE_EDGELESS_COPILOT_WIDGET)}` +); + declare global { interface HTMLElementTagNameMap { [AFFINE_EDGELESS_COPILOT_WIDGET]: EdgelessCopilotWidget; diff --git a/packages/frontend/core/src/blocksuite/block-suite-editor/specs/common.ts b/packages/frontend/core/src/blocksuite/block-suite-editor/specs/common.ts deleted file mode 100644 index 39042e5e47..0000000000 --- a/packages/frontend/core/src/blocksuite/block-suite-editor/specs/common.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { - AICodeBlockSpec, - AIImageBlockSpec, - AIParagraphBlockSpec, -} from '@affine/core/blocksuite/ai'; -import { AIChatBlockSpec } from '@affine/core/blocksuite/ai/blocks'; -import { - AdapterFactoryExtensions, - AttachmentBlockSpec, - BookmarkBlockSpec, - CodeBlockSpec, - DatabaseBlockSpec, - DataViewBlockSpec, - DefaultOpenDocExtension, - DividerBlockSpec, - EditPropsStore, - EmbedExtensions, - FontLoaderService, - ImageBlockSpec, - LatexBlockSpec, - ListBlockSpec, - ParagraphBlockSpec, - RefNodeSlotsExtension, - RichTextExtensions, - TableBlockSpec, -} from '@blocksuite/affine/blocks'; -import type { ExtensionType } from '@blocksuite/affine/store'; - -const CommonBlockSpecs: ExtensionType[] = [ - RefNodeSlotsExtension, - EditPropsStore, - RichTextExtensions, - LatexBlockSpec, - ListBlockSpec, - DatabaseBlockSpec, - TableBlockSpec, - DataViewBlockSpec, - DividerBlockSpec, - EmbedExtensions, - BookmarkBlockSpec, - AttachmentBlockSpec, - AdapterFactoryExtensions, - FontLoaderService, - DefaultOpenDocExtension, -].flat(); - -export const DefaultBlockSpecs: ExtensionType[] = [ - CodeBlockSpec, - ImageBlockSpec, - ParagraphBlockSpec, - ...CommonBlockSpecs, -].flat(); - -export const AIBlockSpecs: ExtensionType[] = [ - AICodeBlockSpec, - AIImageBlockSpec, - AIParagraphBlockSpec, - AIChatBlockSpec, - ...CommonBlockSpecs, -].flat(); diff --git a/packages/frontend/core/src/blocksuite/block-suite-editor/specs/custom/root-block.ts b/packages/frontend/core/src/blocksuite/block-suite-editor/specs/custom/root-block.ts index 234a2ee267..97f31275a1 100644 --- a/packages/frontend/core/src/blocksuite/block-suite-editor/specs/custom/root-block.ts +++ b/packages/frontend/core/src/blocksuite/block-suite-editor/specs/custom/root-block.ts @@ -1,9 +1,3 @@ -import { - AICodeBlockSpec, - AIImageBlockSpec, - AIParagraphBlockSpec, -} from '@affine/core/blocksuite/ai'; -import { AIChatBlockSpec } from '@affine/core/blocksuite/ai/blocks'; import { DocService, DocsService } from '@affine/core/modules/doc'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { EditorSettingService } from '@affine/core/modules/editor-setting'; @@ -19,14 +13,11 @@ import type { ThemeExtension, } from '@blocksuite/affine/blocks'; import { - CodeBlockSpec, ColorScheme, createSignalFromObservable, DatabaseConfigExtension, DocDisplayMetaProvider, EditorSettingExtension, - ImageBlockSpec, - ParagraphBlockSpec, referenceToNode, RootBlockConfigExtension, SpecProvider, @@ -267,10 +258,3 @@ export function enableAffineExtension( ].flat() ); } - -export function enableAIExtension(specBuilder: SpecBuilder): void { - specBuilder.replace(CodeBlockSpec, AICodeBlockSpec); - specBuilder.replace(ImageBlockSpec, AIImageBlockSpec); - specBuilder.replace(ParagraphBlockSpec, AIParagraphBlockSpec); - specBuilder.extend(AIChatBlockSpec); -} diff --git a/packages/frontend/core/src/blocksuite/block-suite-editor/specs/edgeless.ts b/packages/frontend/core/src/blocksuite/block-suite-editor/specs/edgeless.ts index c6819f1750..50f2ade690 100644 --- a/packages/frontend/core/src/blocksuite/block-suite-editor/specs/edgeless.ts +++ b/packages/frontend/core/src/blocksuite/block-suite-editor/specs/edgeless.ts @@ -1,16 +1,12 @@ -import { createAIEdgelessRootBlockSpec } from '@affine/core/blocksuite/ai'; +import { enableAIExtension } from '@affine/core/blocksuite/ai'; import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { builtInTemplates as builtInEdgelessTemplates } from '@affine/templates/edgeless'; import { builtInTemplates as builtInStickersTemplates } from '@affine/templates/stickers'; import type { SpecBuilder, TemplateManager } from '@blocksuite/affine/blocks'; -import { - EdgelessRootBlockSpec, - EdgelessTemplatePanel, - SpecProvider, -} from '@blocksuite/affine/blocks'; +import { EdgelessTemplatePanel, SpecProvider } from '@blocksuite/affine/blocks'; import { type FrameworkProvider } from '@toeverything/infra'; -import { enableAffineExtension, enableAIExtension } from './custom/root-block'; +import { enableAffineExtension } from './custom/root-block'; export function createEdgelessModeSpecs( framework: FrameworkProvider @@ -20,11 +16,7 @@ export function createEdgelessModeSpecs( const edgelessSpec = SpecProvider._.getSpec('edgeless'); enableAffineExtension(framework, edgelessSpec); if (enableAI) { - enableAIExtension(edgelessSpec); - edgelessSpec.replace( - EdgelessRootBlockSpec, - createAIEdgelessRootBlockSpec(framework) - ); + enableAIExtension(edgelessSpec, framework); } return edgelessSpec; diff --git a/packages/frontend/core/src/blocksuite/block-suite-editor/specs/page.ts b/packages/frontend/core/src/blocksuite/block-suite-editor/specs/page.ts index 24287809a7..ea5ee6fecf 100644 --- a/packages/frontend/core/src/blocksuite/block-suite-editor/specs/page.ts +++ b/packages/frontend/core/src/blocksuite/block-suite-editor/specs/page.ts @@ -1,13 +1,9 @@ -import { createAIPageRootBlockSpec } from '@affine/core/blocksuite/ai'; +import { enableAIExtension } from '@affine/core/blocksuite/ai'; import { FeatureFlagService } from '@affine/core/modules/feature-flag'; -import { - PageRootBlockSpec, - type SpecBuilder, - SpecProvider, -} from '@blocksuite/affine/blocks'; +import { type SpecBuilder, SpecProvider } from '@blocksuite/affine/blocks'; import { type FrameworkProvider } from '@toeverything/infra'; -import { enableAffineExtension, enableAIExtension } from './custom/root-block'; +import { enableAffineExtension } from './custom/root-block'; export function createPageModeSpecs(framework: FrameworkProvider): SpecBuilder { const featureFlagService = framework.get(FeatureFlagService); @@ -16,8 +12,7 @@ export function createPageModeSpecs(framework: FrameworkProvider): SpecBuilder { const pageSpec = provider.getSpec('page'); enableAffineExtension(framework, pageSpec); if (enableAI) { - enableAIExtension(pageSpec); - pageSpec.replace(PageRootBlockSpec, createAIPageRootBlockSpec(framework)); + enableAIExtension(pageSpec, framework); } return pageSpec; }