chore: merge blocksuite source code (#9213)

This commit is contained in:
Mirone
2024-12-20 15:38:06 +08:00
committed by GitHub
parent 2c9ef916f4
commit 30200ff86d
2031 changed files with 238888 additions and 229 deletions

View File

@@ -0,0 +1,180 @@
import type { DocMode } from '@blocksuite/affine-model';
import { stopPropagation } from '@blocksuite/affine-shared/utils';
import type { BlockStdScope } from '@blocksuite/block-std';
import {
docContext,
modelContext,
ShadowlessElement,
stdContext,
} from '@blocksuite/block-std';
import { WithDisposable } from '@blocksuite/global/utils';
import type { BlockModel, Doc } from '@blocksuite/store';
import { Text } from '@blocksuite/store';
import { consume } from '@lit/context';
import { css, html, nothing } from 'lit';
import { query, state } from 'lit/decorators.js';
import { focusTextModel } from '../rich-text/index.js';
export interface BlockCaptionProps {
caption: string | null | undefined;
}
export class BlockCaptionEditor<
Model extends BlockModel<BlockCaptionProps> = BlockModel<BlockCaptionProps>,
> extends WithDisposable(ShadowlessElement) {
static override styles = css`
.block-caption-editor {
display: inline-table;
resize: none;
width: 100%;
outline: none;
border: 0;
background: transparent;
color: var(--affine-icon-color);
font-size: var(--affine-font-sm);
font-family: inherit;
text-align: center;
field-sizing: content;
padding: 0;
margin-top: 4px;
}
.block-caption-editor::placeholder {
color: var(--affine-placeholder-color);
}
`;
private _focus = false;
show = () => {
this.display = true;
this.updateComplete.then(() => this.input.focus()).catch(console.error);
};
get mode(): DocMode {
return this.doc.getParent(this.model)?.flavour === 'affine:surface'
? 'edgeless'
: 'page';
}
private _onCaptionKeydown(event: KeyboardEvent) {
event.stopPropagation();
if (this.mode === 'edgeless' || event.isComposing) {
return;
}
if (event.key === 'Enter') {
event.preventDefault();
const doc = this.doc;
const target = event.target as HTMLInputElement;
const start = target.selectionStart;
if (start === null) {
return;
}
const model = this.model;
const parent = doc.getParent(model);
if (!parent) {
return;
}
const value = target.value;
const caption = value.slice(0, start);
doc.updateBlock(model, { caption });
const nextBlockText = value.slice(start);
const index = parent.children.indexOf(model);
const id = doc.addBlock(
'affine:paragraph',
{ text: new Text(nextBlockText) },
parent,
index + 1
);
focusTextModel(this.std, id);
}
}
private _onInputBlur() {
this._focus = false;
this.display = !!this.caption?.length;
}
private _onInputChange(e: InputEvent) {
const target = e.target as HTMLInputElement;
this.caption = target.value;
this.doc.updateBlock(this.model, {
caption: this.caption,
});
}
private _onInputFocus() {
this._focus = true;
}
override connectedCallback(): void {
super.connectedCallback();
this.caption = this.model.caption;
this.disposables.add(
this.model.propsUpdated.on(({ key }) => {
if (key === 'caption') {
this.caption = this.model.caption;
if (!this._focus) {
this.display = !!this.caption?.length;
}
}
})
);
}
override render() {
if (!this.display && !this.caption) {
return nothing;
}
return html`<textarea
.disabled=${this.doc.readonly}
placeholder="Write a caption"
class="block-caption-editor"
.value=${this.caption ?? ''}
@input=${this._onInputChange}
@focus=${this._onInputFocus}
@blur=${this._onInputBlur}
@pointerdown=${stopPropagation}
@click=${stopPropagation}
@dblclick=${stopPropagation}
@cut=${stopPropagation}
@copy=${stopPropagation}
@paste=${stopPropagation}
@keydown=${this._onCaptionKeydown}
@keyup=${stopPropagation}
></textarea>`;
}
@state()
accessor caption: string | null | undefined = undefined;
@state()
accessor display = false;
@consume({ context: docContext })
accessor doc!: Doc;
@query('.block-caption-editor')
accessor input!: HTMLInputElement;
@consume({ context: modelContext })
accessor model!: Model;
@consume({ context: stdContext })
accessor std!: BlockStdScope;
}
declare global {
interface HTMLElementTagNameMap {
'block-caption-editor': BlockCaptionEditor;
}
}

View File

@@ -0,0 +1,80 @@
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import { BlockComponent, type BlockService } from '@blocksuite/block-std';
import type { BlockModel } from '@blocksuite/store';
import { html, nothing } from 'lit';
import { classMap } from 'lit/directives/class-map.js';
import { createRef, type Ref, ref } from 'lit/directives/ref.js';
import { type StyleInfo, styleMap } from 'lit/directives/style-map.js';
import type { BlockCaptionEditor } from './block-caption.js';
import { styles } from './styles.js';
export enum SelectedStyle {
Background = 'Background',
Border = 'Border',
}
export class CaptionedBlockComponent<
Model extends BlockModel = BlockModel,
Service extends BlockService = BlockService,
WidgetName extends string = string,
> extends BlockComponent<Model, Service, WidgetName> {
static override styles = styles;
get captionEditor() {
if (!this.useCaptionEditor || !this._captionEditorRef.value) {
console.error(
'Oops! Please enable useCaptionEditor before accessing captionEditor'
);
}
return this._captionEditorRef.value;
}
constructor() {
super();
this.addRenderer(this._renderWithWidget);
}
private _renderWithWidget(content: unknown) {
const style = styleMap({
position: 'relative',
...this.blockContainerStyles,
});
const theme = this.std.get(ThemeProvider).theme;
const isBorder = this.selectedStyle === SelectedStyle.Border;
return html`<div
style=${style}
class=${classMap({
'affine-block-component': true,
[theme]: true,
border: isBorder,
})}
>
${content}
${this.useCaptionEditor
? html`<block-caption-editor
${ref(this._captionEditorRef)}
></block-caption-editor>`
: nothing}
${this.selectedStyle === SelectedStyle.Background
? html`<affine-block-selection .block=${this}></affine-block-selection>`
: null}
${this.useZeroWidth && !this.doc.readonly
? html`<block-zero-width .block=${this}></block-zero-width>`
: nothing}
</div>`;
}
// There may be multiple block-caption-editors in a nested structure.
private accessor _captionEditorRef: Ref<BlockCaptionEditor> =
createRef<BlockCaptionEditor>();
protected accessor blockContainerStyles: StyleInfo | undefined = undefined;
protected accessor selectedStyle = SelectedStyle.Background;
protected accessor useCaptionEditor = false;
protected accessor useZeroWidth = false;
}

View File

@@ -0,0 +1,10 @@
import { BlockCaptionEditor } from './block-caption.js';
export { BlockCaptionEditor, type BlockCaptionProps } from './block-caption.js';
export {
CaptionedBlockComponent,
SelectedStyle,
} from './captioned-block-component.js';
export function effects() {
customElements.define('block-caption-editor', BlockCaptionEditor);
}

View File

@@ -0,0 +1,18 @@
import { css } from 'lit';
export const styles = css`
.affine-block-component.border.light .selected-style {
border-radius: 8px;
box-shadow: 0px 0px 0px 1px var(--affine-brand-color);
}
.affine-block-component.border.dark .selected-style {
border-radius: 8px;
box-shadow: 0px 0px 0px 1px var(--affine-brand-color);
}
@media print {
.affine-block-component.border.light .selected-style,
.affine-block-component.border.dark .selected-style {
box-shadow: none;
}
}
`;