Files
AFFiNE-Mirror/blocksuite/affine/block-note/src/components/edgeless-note-background.ts
L-Sun a5f36eb1d8 refactor(editor): configurable page block title (#10063)
### What changes
- make page block title rendering configurable so that a journal title can be rendered by AFFiNE side.
- move page block render logic to a seperate component
2025-02-10 18:17:28 +00:00

181 lines
4.7 KiB
TypeScript

import {
DefaultTheme,
NoteBlockModel,
StrokeStyle,
} from '@blocksuite/affine-model';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import {
getClosestBlockComponentByPoint,
handleNativeRangeAtPoint,
matchFlavours,
stopPropagation,
} from '@blocksuite/affine-shared/utils';
import {
type BlockComponent,
type BlockStdScope,
PropTypes,
requiredProperties,
ShadowlessElement,
stdContext,
TextSelection,
} from '@blocksuite/block-std';
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
import {
clamp,
Point,
SignalWatcher,
WithDisposable,
} from '@blocksuite/global/utils';
import type { BlockModel } from '@blocksuite/store';
import { consume } from '@lit/context';
import { computed } from '@preact/signals-core';
import { html, nothing } from 'lit';
import { property } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
import { isPageBlock } from '../utils';
import * as styles from './edgeless-note-background.css';
@requiredProperties({
note: PropTypes.instanceOf(NoteBlockModel),
})
export class EdgelessNoteBackground extends SignalWatcher(
WithDisposable(ShadowlessElement)
) {
readonly backgroundStyle$ = computed(() => {
const themeProvider = this.std.get(ThemeProvider);
const theme = themeProvider.theme$.value;
const backgroundColor = themeProvider.generateColorProperty(
this.note.background$.value,
DefaultTheme.noteBackgrounColor,
theme
);
const { borderRadius, borderSize, borderStyle, shadowType } =
this.note.edgeless$.value.style;
return {
borderRadius: borderRadius + 'px',
backgroundColor: backgroundColor,
borderWidth: `${borderSize}px`,
borderStyle: borderStyle === StrokeStyle.Dash ? 'dashed' : borderStyle,
boxShadow: !shadowType ? 'none' : `var(${shadowType})`,
};
});
get gfx() {
return this.std.get(GfxControllerIdentifier);
}
get doc() {
return this.std.host.doc;
}
private _tryAddParagraph(x: number, y: number) {
const nearest = getClosestBlockComponentByPoint(
new Point(x, y)
) as BlockComponent | null;
if (!nearest) return;
const nearestBBox = nearest.getBoundingClientRect();
const yRel = y - nearestBBox.top;
const insertPos: 'before' | 'after' =
yRel < nearestBBox.height / 2 ? 'before' : 'after';
const nearestModel = nearest.model as BlockModel;
const nearestModelIdx = this.note.children.indexOf(nearestModel);
const children = this.note.children;
const siblingModel =
children[
clamp(
nearestModelIdx + (insertPos === 'before' ? -1 : 1),
0,
children.length
)
];
if (
(!nearestModel.text ||
!matchFlavours(nearestModel, ['affine:paragraph', 'affine:list'])) &&
(!siblingModel ||
!siblingModel.text ||
!matchFlavours(siblingModel, ['affine:paragraph', 'affine:list']))
) {
const [pId] = this.doc.addSiblingBlocks(
nearestModel,
[{ flavour: 'affine:paragraph' }],
insertPos
);
this.updateComplete
.then(() => {
this.std.selection.setGroup('note', [
this.std.selection.create(TextSelection, {
from: {
blockId: pId,
index: 0,
length: 0,
},
to: null,
}),
]);
})
.catch(console.error);
}
}
private _handleClickAtBackground(e: MouseEvent) {
e.stopPropagation();
if (!this.editing) return;
const { zoom } = this.gfx.viewport;
const rect = this.getBoundingClientRect();
const offsetY = 16 * zoom;
const offsetX = 2 * zoom;
const x = clamp(e.x, rect.left + offsetX, rect.right - offsetX);
const y = clamp(e.y, rect.top + offsetY, rect.bottom - offsetY);
handleNativeRangeAtPoint(x, y);
if (this.std.host.doc.readonly) return;
this._tryAddParagraph(x, y);
}
private _renderHeader() {
const header = this.std
.getConfig('affine:note')
?.edgelessNoteHeader({ note: this.note, std: this.std });
return header;
}
override render() {
return html`<div
class=${styles.background}
style=${styleMap(this.backgroundStyle$.value)}
@pointerdown=${stopPropagation}
@click=${this._handleClickAtBackground}
>
${isPageBlock(this.std, this.note) ? this._renderHeader() : nothing}
</div>`;
}
@consume({ context: stdContext })
accessor std!: BlockStdScope;
@property({ attribute: false })
accessor editing: boolean = false;
@property({ attribute: false })
accessor note!: NoteBlockModel;
}
declare global {
interface HTMLElementTagNameMap {
'edgeless-note-background': EdgelessNoteBackground;
}
}