diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/commands/insert-embed-iframe-with-url.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/commands/insert-embed-iframe-with-url.ts index a481c3b3e7..20ab737ce9 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/commands/insert-embed-iframe-with-url.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/commands/insert-embed-iframe-with-url.ts @@ -90,15 +90,15 @@ export const insertEmbedIframeWithUrlCommand: Command< surfaceBlock.model ); - gfx.selection.set({ - elements: [newBlockId], - editing: false, - }); - gfx.tool.setTool( // @ts-expect-error FIXME: resolve after gfx tool refactor 'default' ); + + gfx.selection.set({ + elements: [newBlockId], + editing: false, + }); } if (!newBlockId) { diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-error-card.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-error-card.ts index 786aa43987..0ec87fdd88 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-error-card.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-error-card.ts @@ -1,5 +1,9 @@ import { createLitPortal } from '@blocksuite/affine-components/portal'; import type { EmbedIframeBlockModel } from '@blocksuite/affine-model'; +import { + DocModeProvider, + TelemetryProvider, +} from '@blocksuite/affine-shared/services'; import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme'; import { WithDisposable } from '@blocksuite/global/lit'; import { EditIcon, InformationIcon, ResetIcon } from '@blocksuite/icons/lit'; @@ -191,6 +195,7 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) { .model=${this.model} .abortController=${this._editAbortController} .std=${this.std} + .inSurface=${this.inSurface} >`, portalStyles: { zIndex: 'var(--affine-z-index-popover)', @@ -210,6 +215,15 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) { private readonly _handleRetry = (e: MouseEvent) => { e.stopPropagation(); this.onRetry(); + + // track retry event + this.telemetryService?.track('ReloadLink', { + type: 'embed iframe block', + page: this.editorMode === 'page' ? 'doc editor' : 'whiteboard editor', + segment: 'editor', + module: 'embed block', + control: 'reload button', + }); }; override render() { @@ -273,6 +287,16 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) { return this.model.doc.readonly; } + get telemetryService() { + return this.std.getOptional(TelemetryProvider); + } + + get editorMode() { + const docModeService = this.std.get(DocModeProvider); + const mode = docModeService.getEditorMode(); + return mode ?? 'page'; + } + @query('.button.edit') accessor _editButton: HTMLElement | null = null; @@ -288,6 +312,9 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) { @property({ attribute: false }) accessor std!: BlockStdScope; + @property({ attribute: false }) + accessor inSurface = false; + @property({ attribute: false }) accessor options: EmbedIframeStatusCardOptions = { layout: 'horizontal', diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-link-edit-popup.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-link-edit-popup.ts index 98592290ab..d33107910f 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-link-edit-popup.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-link-edit-popup.ts @@ -1,3 +1,7 @@ +import { + DocModeProvider, + TelemetryProvider, +} from '@blocksuite/affine-shared/services'; import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme'; import { SignalWatcher } from '@blocksuite/global/lit'; import { DoneIcon } from '@blocksuite/icons/lit'; @@ -67,6 +71,17 @@ export class EmbedIframeLinkEditPopup extends SignalWatcher( } `; + protected override track(status: 'success' | 'failure') { + this.telemetryService?.track('EditLink', { + type: 'embed iframe block', + page: this.editorMode === 'page' ? 'doc editor' : 'whiteboard editor', + segment: 'editor', + module: 'embed block', + control: 'edit button', + other: status, + }); + } + override render() { const isInputEmpty = this.isInputEmpty(); const { url$ } = this.model.props; @@ -94,4 +109,14 @@ export class EmbedIframeLinkEditPopup extends SignalWatcher( `; } + + get telemetryService() { + return this.std.getOptional(TelemetryProvider); + } + + get editorMode() { + const docModeService = this.std.get(DocModeProvider); + const mode = docModeService.getEditorMode(); + return mode ?? 'page'; + } } diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-link-input-base.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-link-input-base.ts index c3ed81ae58..4a8efcf89f 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-link-input-base.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-link-input-base.ts @@ -5,11 +5,22 @@ import { } from '@blocksuite/affine-shared/services'; import { isValidUrl, stopPropagation } from '@blocksuite/affine-shared/utils'; import { WithDisposable } from '@blocksuite/global/lit'; -import { BlockSelection, type BlockStdScope } from '@blocksuite/std'; +import { noop } from '@blocksuite/global/utils'; +import { + BlockSelection, + type BlockStdScope, + SurfaceSelection, +} from '@blocksuite/std'; import { LitElement } from 'lit'; import { property, query, state } from 'lit/decorators.js'; export class EmbedIframeLinkInputBase extends WithDisposable(LitElement) { + // this method is used to track the event when the user inputs the link + // it should be overridden by the subclass + protected track(status: 'success' | 'failure') { + noop(status); + } + protected isInputEmpty() { return this._linkInputValue.trim() === ''; } @@ -33,9 +44,20 @@ export class EmbedIframeLinkInputBase extends WithDisposable(LitElement) { this.store.transact(() => { const blockId = this.store.addBlock(flavour, { url }, parent, index); this.store.deleteBlock(model); - this.std.selection.setGroup('note', [ - this.std.selection.create(BlockSelection, { blockId }), - ]); + if (this.inSurface) { + this.std.selection.setGroup('gfx', [ + this.std.selection.create( + SurfaceSelection, + blockId, + [blockId], + false + ), + ]); + } else { + this.std.selection.setGroup('note', [ + this.std.selection.create(BlockSelection, { blockId }), + ]); + } }); this.abortController?.abort(); @@ -50,6 +72,7 @@ export class EmbedIframeLinkInputBase extends WithDisposable(LitElement) { const embedIframeService = this.std.get(EmbedIframeService); if (!embedIframeService) { console.error('iframe EmbedIframeService not found'); + this.track('failure'); return; } @@ -68,7 +91,9 @@ export class EmbedIframeLinkInputBase extends WithDisposable(LitElement) { title: '', description: '', }); + this.track('success'); } catch (error) { + this.track('failure'); this.notificationService?.notify({ title: 'Error in embed iframe creation', message: error instanceof Error ? error.message : 'Please try again', @@ -129,4 +154,7 @@ export class EmbedIframeLinkInputBase extends WithDisposable(LitElement) { @property({ attribute: false }) accessor abortController: AbortController | undefined = undefined; + + @property({ attribute: false }) + accessor inSurface = false; } diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-link-input-popup.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-link-input-popup.ts index 356616d4ea..61be4ea13d 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-link-input-popup.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/components/embed-iframe-link-input-popup.ts @@ -1,3 +1,7 @@ +import { + DocModeProvider, + TelemetryProvider, +} from '@blocksuite/affine-shared/services'; import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme'; import { CloseIcon } from '@blocksuite/icons/lit'; import { baseTheme } from '@toeverything/theme'; @@ -16,6 +20,7 @@ export type EmbedLinkInputPopupOptions = { title?: string; description?: string; placeholder?: string; + telemetrySegment?: string; }; const DEFAULT_OPTIONS: EmbedLinkInputPopupOptions = { @@ -24,6 +29,7 @@ const DEFAULT_OPTIONS: EmbedLinkInputPopupOptions = { title: 'Embed Link', description: 'Works with links of Google Drive, Spotify…', placeholder: 'Paste the Embed link...', + telemetrySegment: 'editor', }; export class EmbedIframeLinkInputPopup extends EmbedIframeLinkInputBase { @@ -216,6 +222,17 @@ export class EmbedIframeLinkInputPopup extends EmbedIframeLinkInputBase { this.abortController?.abort(); }; + protected override track(status: 'success' | 'failure') { + this.telemetryService?.track('CreateEmbedBlock', { + type: 'embed iframe block', + page: this.editorMode === 'page' ? 'doc editor' : 'whiteboard editor', + segment: this.options?.telemetrySegment ?? 'editor', + module: 'embed block', + control: 'confirm embed link', + other: status, + }); + } + override render() { const options = { ...DEFAULT_OPTIONS, ...this.options }; const { showCloseButton, variant, title, description, placeholder } = @@ -261,6 +278,16 @@ export class EmbedIframeLinkInputPopup extends EmbedIframeLinkInputBase { `; } + get telemetryService() { + return this.std.getOptional(TelemetryProvider); + } + + get editorMode() { + const docModeService = this.std.get(DocModeProvider); + const mode = docModeService.getEditorMode(); + return mode ?? 'page'; + } + @property({ attribute: false }) accessor options: EmbedLinkInputPopupOptions | undefined = undefined; } diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/slash-menu/slash-menu.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/slash-menu/slash-menu.ts index 89ecb8f69a..b184d3d77a 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/slash-menu/slash-menu.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/slash-menu/slash-menu.ts @@ -31,6 +31,9 @@ export const embedIframeSlashMenuConfig: SlashMenuConfig = { .pipe(insertEmptyEmbedIframeCommand, { place: 'after', removeEmptyLine: true, + linkInputPopupOptions: { + telemetrySegment: 'slash menu', + }, }) .run(); }, diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/toolbar.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/toolbar.ts index 4d4e74a024..b6472416cb 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/toolbar.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/toolbar.ts @@ -67,6 +67,11 @@ const openLinkAction = (id: string): ToolbarAction => { run(ctx) { const component = ctx.getCurrentBlockByType(EmbedIframeBlockComponent); component?.open(); + + ctx.track('OpenLink', { + ...trackBaseProps, + control: 'open original link', + }); }, }; }; @@ -263,6 +268,11 @@ export const builtinToolbarConfig = { .copySlice(slice) .then(() => toast(ctx.host, 'Copied to clipboard')) .catch(console.error); + + ctx.track('CopiedLink', { + ...trackBaseProps, + control: 'copy link', + }); }, }, { @@ -290,6 +300,11 @@ export const builtinToolbarConfig = { run(ctx) { const component = ctx.getCurrentBlockByType(EmbedIframeBlockComponent); component?.refreshData().catch(console.error); + + ctx.track('ReloadLink', { + ...trackBaseProps, + control: 'reload link', + }); }, }, { diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/embed-iframe-block.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/embed-iframe-block.ts index 82b7c37371..6c82df5e95 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/embed-iframe-block.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/embed-iframe-block.ts @@ -225,6 +225,7 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent`, portalStyles: { @@ -347,6 +348,7 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent`; } diff --git a/blocksuite/affine/blocks/block-root/src/widgets/keyboard-toolbar/config.ts b/blocksuite/affine/blocks/block-root/src/widgets/keyboard-toolbar/config.ts index 9670994c5d..5b7db6053f 100644 --- a/blocksuite/affine/blocks/block-root/src/widgets/keyboard-toolbar/config.ts +++ b/blocksuite/affine/blocks/block-root/src/widgets/keyboard-toolbar/config.ts @@ -493,6 +493,7 @@ const embedToolGroup: KeyboardToolPanelGroup = { linkInputPopupOptions: { showCloseButton: true, variant: 'mobile', + telemetrySegment: 'keyboard toolbar', }, }) .run(); diff --git a/blocksuite/affine/shared/src/services/telemetry-service/link.ts b/blocksuite/affine/shared/src/services/telemetry-service/link.ts index 7eb14d532d..8e81523a13 100644 --- a/blocksuite/affine/shared/src/services/telemetry-service/link.ts +++ b/blocksuite/affine/shared/src/services/telemetry-service/link.ts @@ -11,6 +11,9 @@ export type LinkEventType = | 'OpenedCardStyleSelector' | 'SelectedCardStyle' | 'OpenedCardScaleSelector' - | 'SelectedCardScale'; + | 'SelectedCardScale' + | 'OpenLink' + | 'EditLink' + | 'ReloadLink'; export type LinkToolbarEvents = Record; diff --git a/blocksuite/affine/shared/src/services/telemetry-service/telemetry-service.ts b/blocksuite/affine/shared/src/services/telemetry-service/telemetry-service.ts index 1111c663f5..e30b4fd6dd 100644 --- a/blocksuite/affine/shared/src/services/telemetry-service/telemetry-service.ts +++ b/blocksuite/affine/shared/src/services/telemetry-service/telemetry-service.ts @@ -30,6 +30,7 @@ export type TelemetryEventMap = OutDatabaseAllEvents & AttachmentUploadedEvent: AttachmentUploadedEvent; BlockCreated: BlockCreationEvent; EdgelessToolPicked: EdgelessToolPickedEvent; + CreateEmbedBlock: TelemetryEvent; }; export interface TelemetryService { diff --git a/blocksuite/affine/widgets/widget-drag-handle/src/watchers/drag-event-watcher.ts b/blocksuite/affine/widgets/widget-drag-handle/src/watchers/drag-event-watcher.ts index 5c2a47666f..c211e92afc 100644 --- a/blocksuite/affine/widgets/widget-drag-handle/src/watchers/drag-event-watcher.ts +++ b/blocksuite/affine/widgets/widget-drag-handle/src/watchers/drag-event-watcher.ts @@ -24,6 +24,7 @@ import { import { DndApiExtensionIdentifier, DocModeProvider, + EmbedIframeService, TelemetryProvider, } from '@blocksuite/affine-shared/services'; import { @@ -1058,8 +1059,20 @@ export class DragEventWatcher { new Bound(0, 0, 0, 0); if (block.flavour === 'affine:embed-iframe') { - blockBound.w = EMBED_IFRAME_DEFAULT_WIDTH_IN_SURFACE; - blockBound.h = EMBED_IFRAME_DEFAULT_HEIGHT_IN_SURFACE; + let width = EMBED_IFRAME_DEFAULT_WIDTH_IN_SURFACE; + let height = EMBED_IFRAME_DEFAULT_HEIGHT_IN_SURFACE; + if (block.props.url && typeof block.props.url === 'string') { + const embedIframeService = this.std.get(EmbedIframeService); + const options = embedIframeService.getConfig( + block.props.url + )?.options; + if (options) { + width = options.widthInSurface; + height = options.heightInSurface; + } + } + blockBound.w = width; + blockBound.h = height; } else if ( block.flavour === 'affine:attachment' || block.flavour === 'affine:bookmark' ||