mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-17 22:37:04 +08:00
### 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
181 lines
4.7 KiB
TypeScript
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;
|
|
}
|
|
}
|