mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-22 12:36:24 +08:00
chore(editor): reorg packages (#10702)
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
import {
|
||||
ACTIVE_NOTE_EXTRA_PADDING,
|
||||
edgelessNoteContainer,
|
||||
} from '../note-edgeless-block.css';
|
||||
|
||||
export const background = style({
|
||||
position: 'absolute',
|
||||
borderColor: cssVar('black10'),
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
|
||||
selectors: {
|
||||
[`${edgelessNoteContainer}[data-editing="true"] &`]: {
|
||||
left: `${-ACTIVE_NOTE_EXTRA_PADDING}px`,
|
||||
top: `${-ACTIVE_NOTE_EXTRA_PADDING}px`,
|
||||
width: `calc(100% + ${ACTIVE_NOTE_EXTRA_PADDING * 2}px)`,
|
||||
height: `calc(100% + ${ACTIVE_NOTE_EXTRA_PADDING * 2}px)`,
|
||||
transition: 'left 0.3s, top 0.3s, width 0.3s, height 0.3s',
|
||||
boxShadow: cssVar('activeShadow'),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,178 @@
|
||||
import {
|
||||
DefaultTheme,
|
||||
ListBlockModel,
|
||||
NoteBlockModel,
|
||||
ParagraphBlockModel,
|
||||
StrokeStyle,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
||||
import {
|
||||
getClosestBlockComponentByPoint,
|
||||
handleNativeRangeAtPoint,
|
||||
matchModels,
|
||||
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 } from '@blocksuite/global/gfx';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||
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 { NoteConfigExtension } from '../config';
|
||||
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 ||
|
||||
!matchModels(nearestModel, [ParagraphBlockModel, ListBlockModel])) &&
|
||||
(!siblingModel ||
|
||||
!siblingModel.text ||
|
||||
!matchModels(siblingModel, [ParagraphBlockModel, ListBlockModel]))
|
||||
) {
|
||||
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
|
||||
.getOptional(NoteConfigExtension.identifier)
|
||||
?.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}
|
||||
>
|
||||
${this.note.isPageBlock() ? 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import type { NoteBlockModel } from '@blocksuite/affine-model';
|
||||
import { type EditorHost, ShadowlessElement } from '@blocksuite/block-std';
|
||||
import { almostEqual, Bound } from '@blocksuite/global/gfx';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||
import { html } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import { ACTIVE_NOTE_EXTRA_PADDING } from '../note-edgeless-block.css';
|
||||
|
||||
export class EdgelessNoteMask extends SignalWatcher(
|
||||
WithDisposable(ShadowlessElement)
|
||||
) {
|
||||
protected override firstUpdated() {
|
||||
const maskDOM = this.renderRoot!.querySelector('.affine-note-mask');
|
||||
const observer = new ResizeObserver(entries => {
|
||||
for (const entry of entries) {
|
||||
if (!this.model.edgeless.collapse) {
|
||||
const bound = Bound.deserialize(this.model.xywh);
|
||||
const scale = this.model.edgeless.scale ?? 1;
|
||||
const height = entry.contentRect.height * scale;
|
||||
|
||||
if (!height || almostEqual(bound.h, height)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bound.h = height;
|
||||
this.model.stash('xywh');
|
||||
this.model.xywh = bound.serialize();
|
||||
this.model.pop('xywh');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(maskDOM!);
|
||||
|
||||
this._disposables.add(() => {
|
||||
observer.disconnect();
|
||||
});
|
||||
}
|
||||
|
||||
override render() {
|
||||
const extra = this.editing ? ACTIVE_NOTE_EXTRA_PADDING : 0;
|
||||
return html`
|
||||
<div
|
||||
class="affine-note-mask"
|
||||
style=${styleMap({
|
||||
position: 'absolute',
|
||||
top: `${-extra}px`,
|
||||
left: `${-extra}px`,
|
||||
bottom: `${-extra}px`,
|
||||
right: `${-extra}px`,
|
||||
zIndex: '1',
|
||||
pointerEvents: this.editing || this.disableMask ? 'none' : 'auto',
|
||||
borderRadius: `${
|
||||
this.model.edgeless.style.borderRadius * this.zoom
|
||||
}px`,
|
||||
})}
|
||||
></div>
|
||||
`;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor disableMask!: boolean;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor editing!: boolean;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor host!: EditorHost;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor model!: NoteBlockModel;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor zoom!: number;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'edgeless-note-mask': EdgelessNoteMask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { globalStyle, style } from '@vanilla-extract/css';
|
||||
|
||||
export const pageBlockTitle = style({
|
||||
position: 'relative',
|
||||
});
|
||||
|
||||
globalStyle(`${pageBlockTitle} .doc-title-container`, {
|
||||
padding: '26px 0px',
|
||||
marginLeft: 'unset',
|
||||
marginRight: 'unset',
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { NoteBlockModel } from '@blocksuite/affine-model';
|
||||
import {
|
||||
type BlockStdScope,
|
||||
PropTypes,
|
||||
requiredProperties,
|
||||
ShadowlessElement,
|
||||
stdContext,
|
||||
} from '@blocksuite/block-std';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||
import { consume } from '@lit/context';
|
||||
import { html } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import { NoteConfigExtension } from '../config';
|
||||
import * as styles from './edgeless-page-block-title.css';
|
||||
|
||||
@requiredProperties({
|
||||
note: PropTypes.instanceOf(NoteBlockModel),
|
||||
})
|
||||
export class EdgelessPageBlockTitle extends SignalWatcher(
|
||||
WithDisposable(ShadowlessElement)
|
||||
) {
|
||||
override render() {
|
||||
if (!this.note.isPageBlock()) return;
|
||||
|
||||
const title = this.std
|
||||
.getOptional(NoteConfigExtension.identifier)
|
||||
?.pageBlockTitle({
|
||||
note: this.note,
|
||||
std: this.std,
|
||||
});
|
||||
|
||||
return html`<div class=${styles.pageBlockTitle}>${title}</div>`;
|
||||
}
|
||||
|
||||
@consume({ context: stdContext })
|
||||
accessor std!: BlockStdScope;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor note!: NoteBlockModel;
|
||||
}
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'edgeless-page-block-title': EdgelessPageBlockTitle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { html } from 'lit';
|
||||
|
||||
export const MoreIndicator = html`<svg
|
||||
width="34"
|
||||
height="29"
|
||||
viewBox="0 0 34 29"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M3 14.4292L16.8345 18.9019C17.0345 18.9665 17.2498 18.9665 17.4498 18.9019L31.2843 14.4292"
|
||||
stroke="black"
|
||||
stroke-opacity="0.3"
|
||||
stroke-width="5"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>`;
|
||||
Reference in New Issue
Block a user