feat(editor): audio block (#10947)

AudioMedia entity for loading & controlling a single audio media
AudioMediaManagerService: Global audio state synchronization across tabs
AudioAttachmentService + AudioAttachmentBlock for manipulating AttachmentBlock in affine - e.g., filling transcription (using mock endpoint for now)
Added AudioBlock + AudioPlayer for rendering audio block in affine (new transcription block whose renderer is provided in affine)

fix AF-2292
fix AF-2337
This commit is contained in:
pengx17
2025-03-20 12:46:14 +00:00
parent 8a5393ea50
commit fad49bb070
120 changed files with 5407 additions and 950 deletions

View File

@@ -29,6 +29,7 @@ import {
RootBlockSchemaExtension,
SurfaceRefBlockSchemaExtension,
TableBlockSchemaExtension,
TranscriptionBlockSchemaExtension,
} from '@blocksuite/affine-model';
import {
HighlightSelectionExtension,
@@ -84,6 +85,7 @@ export const StoreExtensions: ExtensionType[] = [
LatexBlockSchemaExtension,
TableBlockSchemaExtension,
CalloutBlockSchemaExtension,
TranscriptionBlockSchemaExtension,
BlockSelectionExtension,
TextSelectionExtension,

View File

@@ -25,6 +25,7 @@ import {
RootBlockSchema,
SurfaceRefBlockSchema,
TableBlockSchema,
TranscriptionBlockSchema,
} from '@blocksuite/affine-model';
import type { BlockSchema } from '@blocksuite/store';
import type { z } from 'zod';
@@ -56,4 +57,5 @@ export const AffineSchemas: z.infer<typeof BlockSchema>[] = [
LatexBlockSchema,
TableBlockSchema,
CalloutBlockSchema,
TranscriptionBlockSchema,
];

View File

@@ -18,7 +18,7 @@ import { humanFileSize } from '@blocksuite/affine-shared/utils';
import { BlockSelection } from '@blocksuite/block-std';
import { Slice } from '@blocksuite/store';
import { html } from 'lit';
import { property, state } from 'lit/decorators.js';
import { property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
@@ -34,10 +34,6 @@ import { checkAttachmentBlob, downloadAttachmentBlob } from './utils';
export class AttachmentBlockComponent extends CaptionedBlockComponent<AttachmentBlockModel> {
static override styles = styles;
protected _isDragging = false;
protected _isResizing = false;
blockDraggable = true;
protected containerStyleMap = styleMap({
@@ -127,25 +123,6 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
this.disposables.add(
this.std.get(ThemeProvider).theme$.subscribe(() => this.requestUpdate())
);
// this is required to prevent iframe from capturing pointer events
this.disposables.add(
this.selected$.subscribe(selected => {
this._showOverlay = this._isResizing || this._isDragging || !selected;
})
);
// this is required to prevent iframe from capturing pointer events
this.handleEvent('dragStart', () => {
this._isDragging = true;
this._showOverlay =
this._isResizing || this._isDragging || !this.selected$.peek();
});
this.handleEvent('dragEnd', () => {
this._isDragging = false;
this._showOverlay =
this._isResizing || this._isDragging || !this.selected$.peek();
});
}
override disconnectedCallback() {
@@ -166,7 +143,9 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
event.stopPropagation();
this._selectBlock();
if (!this.selected$.peek()) {
this._selectBlock();
}
}
override renderBlock() {
@@ -190,13 +169,6 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
${embedView
? html`<div class="affine-attachment-embed-container">
${embedView}
<div
class=${classMap({
'affine-attachment-iframe-overlay': true,
hide: !this._showOverlay,
})}
></div>
</div>`
: html`<div
class=${classMap({
@@ -227,9 +199,6 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
`;
}
@state()
protected accessor _showOverlay = true;
@property({ attribute: false })
accessor allowEmbed = false;

View File

@@ -18,25 +18,6 @@ export class AttachmentEdgelessBlockComponent extends toGfxBlockComponent(
return this.std.get(EdgelessLegacySlotIdentifier);
}
override connectedCallback(): void {
super.connectedCallback();
this._disposables.add(
this.slots.elementResizeStart.subscribe(() => {
this._isResizing = true;
this._showOverlay = true;
})
);
this._disposables.add(
this.slots.elementResizeEnd.subscribe(() => {
this._isResizing = false;
this._showOverlay =
this._isResizing || this._isDragging || !this.selected$.peek();
})
);
}
override onClick(_: MouseEvent) {
return;
}

View File

@@ -29,7 +29,8 @@ export const attachmentSlashMenuConfig: SlashMenuConfig = {
std.host,
[file],
maxFileSize,
model
model,
'after'
);
if (model.text?.length === 0) {
std.store.deleteBlock(model);
@@ -60,8 +61,7 @@ export const attachmentSlashMenuConfig: SlashMenuConfig = {
[file],
maxFileSize,
model,
'after',
true
'after'
);
if (model.text?.length === 0) {
std.store.deleteBlock(model);

View File

@@ -136,16 +136,4 @@ export const styles = css`
width: 100%;
height: 100%;
}
.affine-attachment-iframe-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.affine-attachment-iframe-overlay.hide {
display: none;
}
`;

View File

@@ -89,6 +89,7 @@ export const AttachmentBlockSchema = defineBlockSchema({
'affine:paragraph',
'affine:list',
],
children: ['@attachment-viewer'],
},
transformer: transformerConfigs =>
new AttachmentBlockTransformer(transformerConfigs),

View File

@@ -31,6 +31,7 @@ export const CalloutBlockSchema = defineBlockSchema({
'affine:paragraph',
'affine:list',
'affine:edgeless-text',
'affine:transcription',
],
children: ['affine:paragraph'],
},

View File

@@ -15,3 +15,4 @@ export * from './paragraph/index.js';
export * from './root/index.js';
export * from './surface-ref/index.js';
export * from './table';
export * from './transcription';

View File

@@ -44,6 +44,7 @@ export const ParagraphBlockSchema = defineBlockSchema({
'affine:list',
'affine:edgeless-text',
'affine:callout',
'affine:transcription',
],
},
toModel: () => new ParagraphBlockModel(),

View File

@@ -0,0 +1 @@
export * from './transcription-model.js';

View File

@@ -0,0 +1,33 @@
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
export const TranscriptionBlockFlavour = 'affine:transcription';
export const TranscriptionBlockSchema = defineBlockSchema({
flavour: TranscriptionBlockFlavour,
props: () => ({
transcription: {},
jobId: '',
}),
metadata: {
version: 1,
role: 'attachment-viewer',
parent: ['affine:attachment'],
children: ['affine:callout'],
},
toModel: () => new TranscriptionBlockModel(),
});
export type TranscriptionBlockProps = {
transcription: Record<string, any>;
jobId: string;
};
export class TranscriptionBlockModel extends BlockModel<TranscriptionBlockProps> {}
export const TranscriptionBlockSchemaExtension = BlockSchemaExtension(
TranscriptionBlockSchema
);