diff --git a/blocksuite/affine/blocks/paragraph/src/heading-icon.ts b/blocksuite/affine/blocks/paragraph/src/heading-icon.ts index d60eec2847..62145c243a 100644 --- a/blocksuite/affine/blocks/paragraph/src/heading-icon.ts +++ b/blocksuite/affine/blocks/paragraph/src/heading-icon.ts @@ -42,7 +42,7 @@ export class ParagraphHeadingIcon extends SignalWatcher( margin-top: 0.3em; position: absolute; left: 0; - transform: translateX(-64px); + transform: translateX(-80px); border-radius: 4px; padding: 2px; cursor: pointer; diff --git a/blocksuite/affine/widgets/drag-handle/package.json b/blocksuite/affine/widgets/drag-handle/package.json index 40f61c7aef..ff8aad4837 100644 --- a/blocksuite/affine/widgets/drag-handle/package.json +++ b/blocksuite/affine/widgets/drag-handle/package.json @@ -19,6 +19,7 @@ "@blocksuite/affine-components": "workspace:*", "@blocksuite/affine-ext-loader": "workspace:*", "@blocksuite/affine-model": "workspace:*", + "@blocksuite/affine-rich-text": "workspace:*", "@blocksuite/affine-shared": "workspace:*", "@blocksuite/global": "workspace:*", "@blocksuite/icons": "^2.2.17", diff --git a/blocksuite/affine/widgets/drag-handle/src/components/add-block-widget.ts b/blocksuite/affine/widgets/drag-handle/src/components/add-block-widget.ts new file mode 100644 index 0000000000..7ebf7d3631 --- /dev/null +++ b/blocksuite/affine/widgets/drag-handle/src/components/add-block-widget.ts @@ -0,0 +1,78 @@ +import { PlusIcon } from '@blocksuite/icons/lit'; +import { css, html, LitElement } from 'lit'; +import { property } from 'lit/decorators.js'; + +import type { AFFINE_ADD_BLOCK_WIDGET } from '../consts.js'; + +export class AffineAddBlockWidget extends LitElement { + static override styles = css` + :host { + display: block; + pointer-events: none; + } + + .affine-add-block-widget { + display: flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + margin-top: 8px; + cursor: pointer; + border-radius: 4px; + color: var(--affine-placeholder-color); + background: transparent; + border: none; + padding: 0; + transition: + color 0.2s ease, + background 0.2s ease; + pointer-events: auto; + user-select: none; + box-sizing: border-box; + } + + .affine-add-block-widget:hover { + background: var(--affine-hover-color); + color: var(--affine-text-primary-color); + } + + .affine-add-block-widget svg { + width: 12px; + height: 12px; + flex-shrink: 0; + } + `; + + @property({ type: Boolean }) + accessor visible = false; + + private readonly _handleClick = (e: MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + this.dispatchEvent( + new CustomEvent('add-block', { bubbles: true, composed: true }) + ); + }; + + override render() { + if (!this.visible) return html``; + + return html` + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + [AFFINE_ADD_BLOCK_WIDGET]: AffineAddBlockWidget; + } +} diff --git a/blocksuite/affine/widgets/drag-handle/src/config.ts b/blocksuite/affine/widgets/drag-handle/src/config.ts index a408dc811c..54f03b476e 100644 --- a/blocksuite/affine/widgets/drag-handle/src/config.ts +++ b/blocksuite/affine/widgets/drag-handle/src/config.ts @@ -1,3 +1,4 @@ +export const ADD_BLOCK_WIDGET_WIDTH = 16; export const DRAG_HANDLE_CONTAINER_HEIGHT = 24; export const DRAG_HANDLE_CONTAINER_WIDTH = 16; export const DRAG_HANDLE_CONTAINER_WIDTH_TOP_LEVEL = 8; diff --git a/blocksuite/affine/widgets/drag-handle/src/consts.ts b/blocksuite/affine/widgets/drag-handle/src/consts.ts index b623e0ed92..d59f439dbe 100644 --- a/blocksuite/affine/widgets/drag-handle/src/consts.ts +++ b/blocksuite/affine/widgets/drag-handle/src/consts.ts @@ -1 +1,2 @@ export const AFFINE_DRAG_HANDLE_WIDGET = 'affine-drag-handle-widget'; +export const AFFINE_ADD_BLOCK_WIDGET = 'affine-add-block-widget'; diff --git a/blocksuite/affine/widgets/drag-handle/src/drag-handle.ts b/blocksuite/affine/widgets/drag-handle/src/drag-handle.ts index 5cb083ec61..705dbca5e6 100644 --- a/blocksuite/affine/widgets/drag-handle/src/drag-handle.ts +++ b/blocksuite/affine/widgets/drag-handle/src/drag-handle.ts @@ -1,5 +1,8 @@ +import './components/add-block-widget.js'; + import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface'; import type { RootBlockModel } from '@blocksuite/affine-model'; +import { focusTextModel } from '@blocksuite/affine-rich-text'; import { DocModeProvider } from '@blocksuite/affine-shared/services'; import { isInsideEdgelessEditor, @@ -15,6 +18,7 @@ import { query, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { styleMap } from 'lit/directives/style-map.js'; +//focustextmodel rich text should be added in package.json file and import from there import type { AFFINE_DRAG_HANDLE_WIDGET } from './consts.js'; import { RectHelper } from './helpers/rect-helper.js'; import { SelectionHelper } from './helpers/selection-helper.js'; @@ -51,9 +55,48 @@ export class AffineDragHandleWidget extends WidgetComponent { this.pointerEventWatcher.reset(); }; + /** + * Insert a new empty paragraph block below the currently hovered block + * and move the cursor into it. + */ + private readonly _handleAddBlock = () => { + const anchorBlockId = this.anchorBlockId.peek(); + if (!anchorBlockId) return; + + const block = this.anchorBlockComponent.peek(); + if (!block) return; + + const { store } = this; + const parent = store.getParent(block.model); + if (!parent) return; + + const index = parent.children.indexOf(block.model); + if (index < 0) return; + store.captureSync(); + const newBlockId = store.addBlock( + 'affine:paragraph', + {}, + parent, + index + 1 + ); + + if (!newBlockId) return; + + this.host.updateComplete + .then(() => { + focusTextModel(this.std, newBlockId); + }) + .catch(console.error); + + this.hide(); + }; + @state() accessor activeDragHandle: 'block' | 'gfx' | null = null; + @state() + accessor showAddBlockWidget = false; + anchorBlockId = signal(null); anchorBlockComponent = computed(() => { @@ -115,6 +158,7 @@ export class AffineDragHandleWidget extends WidgetComponent { this.anchorBlockId.value = null; this.dragHoverRect = null; this.activeDragHandle = null; + this.showAddBlockWidget = false; if (this.dragHandleContainer) { this.dragHandleContainer.removeAttribute('style'); @@ -123,6 +167,10 @@ export class AffineDragHandleWidget extends WidgetComponent { if (this.dragHandleGrabber) { this.dragHandleGrabber.removeAttribute('style'); } + if (this.addBlockWidgetContainer) { + this.addBlockWidgetContainer.removeAttribute('style'); + this.addBlockWidgetContainer.style.display = 'none'; + } if (force) { this._reset(); @@ -211,6 +259,12 @@ export class AffineDragHandleWidget extends WidgetComponent { return html`
+
+ +
${isGfx @@ -236,6 +290,9 @@ export class AffineDragHandleWidget extends WidgetComponent { @query('.affine-drag-handle-grabber') accessor dragHandleGrabber!: HTMLDivElement; + @query('.affine-add-block-widget-container') + accessor addBlockWidgetContainer!: HTMLDivElement; + @state() accessor dragHoverRect: { width: number; diff --git a/blocksuite/affine/widgets/drag-handle/src/effects.ts b/blocksuite/affine/widgets/drag-handle/src/effects.ts index 70330667ba..cd02b7b335 100644 --- a/blocksuite/affine/widgets/drag-handle/src/effects.ts +++ b/blocksuite/affine/widgets/drag-handle/src/effects.ts @@ -1,12 +1,14 @@ +import { AffineAddBlockWidget } from './components/add-block-widget'; import { EDGELESS_DND_PREVIEW_ELEMENT, EdgelessDndPreviewElement, } from './components/edgeless-preview/preview'; -import { AFFINE_DRAG_HANDLE_WIDGET } from './consts'; +import { AFFINE_ADD_BLOCK_WIDGET, AFFINE_DRAG_HANDLE_WIDGET } from './consts'; import { AffineDragHandleWidget } from './drag-handle'; export function effects() { customElements.define(AFFINE_DRAG_HANDLE_WIDGET, AffineDragHandleWidget); + customElements.define(AFFINE_ADD_BLOCK_WIDGET, AffineAddBlockWidget); customElements.define( EDGELESS_DND_PREVIEW_ELEMENT, EdgelessDndPreviewElement diff --git a/blocksuite/affine/widgets/drag-handle/src/styles.ts b/blocksuite/affine/widgets/drag-handle/src/styles.ts index 7d61fb3b39..b4d62ada08 100644 --- a/blocksuite/affine/widgets/drag-handle/src/styles.ts +++ b/blocksuite/affine/widgets/drag-handle/src/styles.ts @@ -1,7 +1,10 @@ import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme'; import { css } from 'lit'; -import { DRAG_HANDLE_CONTAINER_WIDTH } from './config.js'; +import { + ADD_BLOCK_WIDGET_WIDTH, + DRAG_HANDLE_CONTAINER_WIDTH, +} from './config.js'; export const styles = css` .affine-drag-handle-widget { @@ -10,6 +13,20 @@ export const styles = css` left: 0; top: 0; contain: size layout; + pointer-events: none; + } + + .affine-add-block-widget-container { + top: 0; + left: 0; + position: absolute; + display: flex; + justify-content: center; + width: ${ADD_BLOCK_WIDGET_WIDTH}px; + min-height: 12px; + pointer-events: none; + user-select: none; + box-sizing: border-box; } .affine-drag-handle-container { diff --git a/blocksuite/affine/widgets/drag-handle/src/watchers/pointer-event-watcher.ts b/blocksuite/affine/widgets/drag-handle/src/watchers/pointer-event-watcher.ts index cc2059627c..bf1a072434 100644 --- a/blocksuite/affine/widgets/drag-handle/src/watchers/pointer-event-watcher.ts +++ b/blocksuite/affine/widgets/drag-handle/src/watchers/pointer-event-watcher.ts @@ -12,6 +12,7 @@ import { computed } from '@preact/signals-core'; import throttle from 'lodash-es/throttle'; import { + ADD_BLOCK_WIDGET_WIDTH, DRAG_HANDLE_CONTAINER_WIDTH, DRAG_HANDLE_GRABBER_BORDER_RADIUS, DRAG_HANDLE_GRABBER_HEIGHT, @@ -199,6 +200,7 @@ export class PointerEventWatcher { !this.widget.isDragHandleHovered ) { this.showDragHandleOnHoverBlock(); + this.widget.showAddBlockWidget = true; this._lastHoveredBlockId = this.widget.anchorBlockId.peek(); } }; @@ -251,8 +253,13 @@ export class PointerEventWatcher { return; } - // When pointer on drag handle, should do nothing - if (element.closest('.affine-drag-handle-container')) return; + // When pointer on drag handle or add-block widget, should do nothing + if ( + element.closest('.affine-drag-handle-container') || + element.closest('.affine-add-block-widget-container') + ) { + return; + } if (!this.widget.rootComponent) return; @@ -317,6 +324,7 @@ export class PointerEventWatcher { const container = this.widget.dragHandleContainer; const grabber = this.widget.dragHandleGrabber; + const addBlockWidgetContainer = this.widget.addBlockWidgetContainer; if (!container || !grabber) return; this.widget.activeDragHandle = 'block'; @@ -336,6 +344,21 @@ export class PointerEventWatcher { Object.assign(container.style, containerStyle); container.style.display = 'flex'; + + // Position the add-block widget beside the drag handle, aligned to the first line. + if ( + addBlockWidgetContainer && + this.widget.showAddBlockWidget && + this.widget.mode === 'page' + ) { + const posTop = this._getTopWithBlockComponent(block); + addBlockWidgetContainer.style.left = `${draggingAreaRect.left - ADD_BLOCK_WIDGET_WIDTH}px`; + addBlockWidgetContainer.style.top = `${posTop}px`; + addBlockWidgetContainer.style.height = 'auto'; + addBlockWidgetContainer.style.display = 'flex'; + } else if (addBlockWidgetContainer) { + addBlockWidgetContainer.style.display = 'none'; + } }; if (isBlockIdEqual(block.blockId, this._lastShowedBlock?.id)) { diff --git a/blocksuite/affine/widgets/drag-handle/tsconfig.json b/blocksuite/affine/widgets/drag-handle/tsconfig.json index 89887c9a74..b26acff316 100644 --- a/blocksuite/affine/widgets/drag-handle/tsconfig.json +++ b/blocksuite/affine/widgets/drag-handle/tsconfig.json @@ -16,6 +16,7 @@ { "path": "../../components" }, { "path": "../../ext-loader" }, { "path": "../../model" }, + { "path": "../../rich-text" }, { "path": "../../shared" }, { "path": "../../../framework/global" }, { "path": "../../../framework/std" }, diff --git a/tools/utils/src/workspace.gen.ts b/tools/utils/src/workspace.gen.ts index 52b7937595..053fbe2004 100644 --- a/tools/utils/src/workspace.gen.ts +++ b/tools/utils/src/workspace.gen.ts @@ -828,6 +828,7 @@ export const PackageList = [ 'blocksuite/affine/components', 'blocksuite/affine/ext-loader', 'blocksuite/affine/model', + 'blocksuite/affine/rich-text', 'blocksuite/affine/shared', 'blocksuite/framework/global', 'blocksuite/framework/std', diff --git a/yarn.lock b/yarn.lock index bbe350fb5c..75d57ed08f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2932,6 +2932,7 @@ __metadata: "@blocksuite/affine-components": "workspace:*" "@blocksuite/affine-ext-loader": "workspace:*" "@blocksuite/affine-model": "workspace:*" + "@blocksuite/affine-rich-text": "workspace:*" "@blocksuite/affine-shared": "workspace:*" "@blocksuite/global": "workspace:*" "@blocksuite/icons": "npm:^2.2.17"