diff --git a/packages/blocks/src/page-block/edgeless/components/text/edgeless-text-editor.ts b/packages/blocks/src/page-block/edgeless/components/text/edgeless-text-editor.ts index 84ad3d2c984f..699c745a97aa 100644 --- a/packages/blocks/src/page-block/edgeless/components/text/edgeless-text-editor.ts +++ b/packages/blocks/src/page-block/edgeless/components/text/edgeless-text-editor.ts @@ -8,7 +8,10 @@ import { styleMap } from 'lit/directives/style-map.js'; import type { RichText } from '../../../../_common/components/rich-text/rich-text.js'; import { isCssVariable } from '../../../../_common/theme/css-variables.js'; -import { wrapFontFamily } from '../../../../surface-block/elements/text/utils.js'; +import { + getLineHeight, + wrapFontFamily, +} from '../../../../surface-block/elements/text/utils.js'; import { Bound, type TextElement, @@ -28,45 +31,35 @@ export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) { static override styles = css` .edgeless-text-editor { + box-sizing: content-box; position: absolute; + left: 0; + top: 0; z-index: 10; - margin-right: -100%; + transform-origin: left top; border: ${EdgelessTextEditor.BORDER_WIDTH}px solid var(--affine-primary-color, #1e96eb); - box-shadow: 0px 0px 0px 2px rgba(30, 150, 235, 0.3); border-radius: 4px; + box-shadow: 0px 0px 0px 2px rgba(30, 150, 235, 0.3); padding: ${EdgelessTextEditor.VERTICAL_PADDING}px ${EdgelessTextEditor.HORIZONTAL_PADDING}px; - line-height: initial; overflow: visible; - box-sizing: content-box; - left: 0; - top: 0; - } - - .edgeless-text-editor-placeholder { - font-size: 12px; - pointer-events: none; - color: var(--affine-text-disable-color); - white-space: nowrap; } - .edgeless-text-editor .inline-editor-container { - white-space: nowrap; + .edgeless-text-editor .inline-editor { + white-space: pre-wrap !important; outline: none; - width: fit-content; } - .edgeless-text-editor .inline-editor-container span { - white-space: pre !important; - word-break: keep-all !important; - overflow-wrap: normal !important; + .edgeless-text-editor .inline-editor span { + word-break: normal !important; + overflow-wrap: anywhere !important; } - /* We cannot add styles directly from the top, as this would cause a shift in the inline elements inside. */ - /* https://github.com/toeverything/blocksuite/issues/5723 */ - .edgeless-text-editor rich-text v-text { - text-align: var(--text-align); + .edgeless-text-editor-placeholder { + pointer-events: none; + color: var(--affine-text-disable-color); + white-space: nowrap; } `; @@ -83,6 +76,7 @@ export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) { assertExists(this.richText.inlineEditor); return this.richText.inlineEditor; } + get inlineEditorContainer() { return this.inlineEditor.rootElement; } @@ -167,7 +161,7 @@ export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) { return { x: newCenterX - w1 / 2, y: newCenterY - h1 / 2 }; } - private _updateRect() { + private _updateRect = () => { const edgeless = this.edgeless; const element = this.element; @@ -238,80 +232,19 @@ export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) { edgeless.surface.updateElement(element.id, { xywh: bound.serialize(), }); - } - - private _renderPlaceholder() { - if (this.element.text.length === 0 && !this._isComposition) { - return html`Type from here`; - } - - return nothing; - } - - getTransformOrigin(textAlign: TextElement['textAlign']) { - switch (textAlign) { - case 'left': - return 'left top'; - case 'center': - return 'center top'; - case 'right': - return 'right top'; - } - } - - getTransformOffset(textAlign: TextElement['textAlign']) { - switch (textAlign) { - case 'left': - return '0%, 0%'; - case 'center': - return '-50%, 0%'; - case 'right': - return '-100%, 0%'; - } - } + }; getVisualPosition(element: TextElement) { - const { x, y, w, h, rotate, textAlign } = element; - switch (textAlign) { - case 'left': - return Vec.rotWith([x, y], [x + w / 2, y + h / 2], toRadian(rotate)); - case 'center': - return Vec.rotWith( - [x + w / 2, y], - [x + w / 2, y + h / 2], - toRadian(rotate) - ); - case 'right': - return Vec.rotWith( - [x + w, y], - [x + w / 2, y + h / 2], - toRadian(rotate) - ); - } + const { x, y, w, h, rotate } = element; + return Vec.rotWith([x, y], [x + w / 2, y + h / 2], toRadian(rotate)); } - getPaddingOffset(textAlign: TextElement['textAlign']) { + getContainerOffset() { const { VERTICAL_PADDING, HORIZONTAL_PADDING, BORDER_WIDTH } = EdgelessTextEditor; - - switch (textAlign) { - case 'left': - return `-${HORIZONTAL_PADDING + BORDER_WIDTH}px, -${ - VERTICAL_PADDING + BORDER_WIDTH - }px`; - case 'center': - return `0, -${VERTICAL_PADDING + BORDER_WIDTH}px`; - case 'right': - return `${HORIZONTAL_PADDING + BORDER_WIDTH}px, -${ - VERTICAL_PADDING + BORDER_WIDTH - }px`; - } + return `-${HORIZONTAL_PADDING + BORDER_WIDTH}px, -${ + VERTICAL_PADDING + BORDER_WIDTH + }px`; } override connectedCallback(): void { @@ -342,13 +275,16 @@ export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) { if (id === element.id) this.requestUpdate(); }) ); + this.disposables.add( edgeless.surface.viewport.slots.viewportUpdated.on(() => { this.requestUpdate(); }) ); + this.disposables.add(dispatcher.add('click', () => true)); this.disposables.add(dispatcher.add('doubleClick', () => true)); + this.disposables.add(() => { element.display = true; @@ -361,11 +297,13 @@ export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) { editing: false, }); }); + this.disposables.addFromEvent( this.inlineEditorContainer, 'blur', () => !this._keeping && this.remove() ); + this.disposables.addFromEvent( this.inlineEditorContainer, 'compositionstart', @@ -395,66 +333,67 @@ export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) { } override render() { - const { zoom, translateX, translateY } = this.edgeless.surface.viewport; - const { fontFamily, fontSize, textAlign, rotate, fontWeight } = - this.element; - const transformOrigin = this.getTransformOrigin(textAlign); - const offset = this.getTransformOffset(textAlign); - const paddingOffset = this.getPaddingOffset(textAlign); + const { + text, + fontFamily, + fontSize, + fontWeight, + color, + textAlign, + rotate, + hasMaxWidth, + w, + } = this.element; + const lineHeight = getLineHeight(fontFamily, fontSize); const rect = getSelectedRect([this.element]); - const hasMaxWidth = this.element.hasMaxWidth; - const w = this.element.w; - const [x, y] = this.getVisualPosition(this.element); - const placeholder = this._renderPlaceholder(); - const hasPlaceholder = placeholder !== nothing; + const { translateX, translateY, zoom } = this.edgeless.surface.viewport; + const [visualX, visualY] = this.getVisualPosition(this.element); + const containerOffset = this.getContainerOffset(); const transformOperation = [ `translate(${translateX}px, ${translateY}px)`, - `translate(${x * zoom}px, ${y * zoom}px)`, - `translate(${offset})`, + `translate(${visualX * zoom}px, ${visualY * zoom}px)`, `scale(${zoom})`, `rotate(${rotate}deg)`, - `translate(${paddingOffset})`, + `translate(${containerOffset})`, ]; - const left = - textAlign === 'left' - ? EdgelessTextEditor.HORIZONTAL_PADDING + 'px' - : textAlign === 'center' - ? '50%' - : `calc(100% - ${EdgelessTextEditor.HORIZONTAL_PADDING}px)`; - this.style.setProperty('--text-align', textAlign); + const isEmpty = !text.length && !this._isComposition; return html`
- ${placeholder} + ${isEmpty + ? html` + Type from here + ` + : nothing}
`; } } diff --git a/packages/blocks/src/page-block/font-loader/font-loader.ts b/packages/blocks/src/page-block/font-loader/font-loader.ts index ed5c12459080..39335e68f3d0 100644 --- a/packages/blocks/src/page-block/font-loader/font-loader.ts +++ b/packages/blocks/src/page-block/font-loader/font-loader.ts @@ -20,6 +20,7 @@ export class FontLoader { style, }); document.fonts.add(fontFace); + fontFace.load().catch(console.error); return fontFace; }) ); diff --git a/packages/blocks/src/surface-block/elements/text/utils.ts b/packages/blocks/src/surface-block/elements/text/utils.ts index 3df44ac2649c..a3ffc0d38312 100644 --- a/packages/blocks/src/surface-block/elements/text/utils.ts +++ b/packages/blocks/src/surface-block/elements/text/utils.ts @@ -87,7 +87,7 @@ export function getFontString({ const lineHeight = getLineHeight(fontFamily, fontSize); return `${fontStyle} ${fontWeight} ${fontSize}px/${lineHeight}px ${wrapFontFamily( fontFamily - )}`.trim(); + )}, sans-serif`.trim(); } export function normalizeText(text: string): string { diff --git a/tests/paragraph.spec.ts b/tests/paragraph.spec.ts index cc898cb5c235..54b227649e39 100644 --- a/tests/paragraph.spec.ts +++ b/tests/paragraph.spec.ts @@ -1281,7 +1281,7 @@ test('press arrow up in the second line should move caret to the first line', as title: new page.Text(), }); const note = page.addBlock('affine:note', {}, pageId); - const delta = Array.from({ length: 120 }, (_, i) => { + const delta = Array.from({ length: 150 }, (_, i) => { return i % 2 === 0 ? { insert: 'i', attributes: { italic: true } } : { insert: 'b', attributes: { bold: true } }; @@ -1293,9 +1293,13 @@ test('press arrow up in the second line should move caret to the first line', as // Focus the empty paragraph await focusRichText(page, 1); + await page.waitForTimeout(100); + await assertRichTexts(page, ['ib'.repeat(75), '']); await pressArrowUp(page); await pressArrowUp(page); await type(page, '0'); + await assertTitle(page, ''); + await assertRichTexts(page, ['0' + 'ib'.repeat(75), '']); await pressArrowUp(page); // workaround for selection manager @@ -1308,6 +1312,7 @@ test('press arrow up in the second line should move caret to the first line', as // At title await type(page, '1'); await assertTitle(page, '1'); + await assertRichTexts(page, ['0' + 'ib'.repeat(75), '']); // At the first line of the first paragraph await pressArrowDown(page); @@ -1316,7 +1321,7 @@ test('press arrow up in the second line should move caret to the first line', as await pressArrowRight(page); await type(page, '2'); - await assertRichTexts(page, ['0' + 'ib'.repeat(60), '2']); + await assertRichTexts(page, ['0' + 'ib'.repeat(75), '2']); // Go to the start of the second paragraph await pressArrowLeft(page); @@ -1324,7 +1329,7 @@ test('press arrow up in the second line should move caret to the first line', as await pressArrowDown(page); // Should be inserted at the start of the second paragraph await type(page, '3'); - await assertRichTexts(page, ['0' + 'ib'.repeat(60), '32']); + await assertRichTexts(page, ['0' + 'ib'.repeat(75), '32']); }); test('press arrow down in indent line should not move caret to the start of line', async ({