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
@@ -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;
@@ -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;
}
@@ -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);
@@ -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;
}
`;