diff --git a/blocksuite/affine/blocks/note/src/note-edgeless-block.ts b/blocksuite/affine/blocks/note/src/note-edgeless-block.ts index bc2829e7a14dd..d444f5b6ca47e 100644 --- a/blocksuite/affine/blocks/note/src/note-edgeless-block.ts +++ b/blocksuite/affine/blocks/note/src/note-edgeless-block.ts @@ -454,37 +454,58 @@ export const EdgelessNoteInteraction = return; } - if (model.children.length === 0) { - const blockId = std.store.addBlock( - 'affine:paragraph', - { type: 'text' }, - model.id + let isClickOnTitle = false; + const titleRect = view + .querySelector('edgeless-page-block-title') + ?.getBoundingClientRect(); + + if (titleRect) { + const titleBound = new Bound( + titleRect.x, + titleRect.y, + titleRect.width, + titleRect.height ); - - if (blockId) { - focusTextModel(std, blockId); + if (titleBound.isPointInBound([e.clientX, e.clientY])) { + isClickOnTitle = true; } + } + + if (isClickOnTitle) { + handleNativeRangeAtPoint(e.clientX, e.clientY); } else { - const rect = view - .querySelector('.affine-block-children-container') - ?.getBoundingClientRect(); - - if (rect) { - const offsetY = 8 * gfx.viewport.zoom; - const offsetX = 2 * gfx.viewport.zoom; - const x = clamp( - e.clientX, - rect.left + offsetX, - rect.right - offsetX + if (model.children.length === 0) { + const blockId = std.store.addBlock( + 'affine:paragraph', + { type: 'text' }, + model.id ); - const y = clamp( - e.clientY, - rect.top + offsetY, - rect.bottom - offsetY - ); - handleNativeRangeAtPoint(x, y); + + if (blockId) { + focusTextModel(std, blockId); + } } else { - handleNativeRangeAtPoint(e.clientX, e.clientY); + const rect = view + .querySelector('.affine-block-children-container') + ?.getBoundingClientRect(); + + if (rect) { + const offsetY = 8 * gfx.viewport.zoom; + const offsetX = 2 * gfx.viewport.zoom; + const x = clamp( + e.clientX, + rect.left + offsetX, + rect.right - offsetX + ); + const y = clamp( + e.clientY, + rect.top + offsetY, + rect.bottom - offsetY + ); + handleNativeRangeAtPoint(x, y); + } else { + handleNativeRangeAtPoint(e.clientX, e.clientY); + } } } }) diff --git a/blocksuite/affine/shared/src/utils/dom/point-to-range.ts b/blocksuite/affine/shared/src/utils/dom/point-to-range.ts index d7e879c844579..b63f3c0929ad4 100644 --- a/blocksuite/affine/shared/src/utils/dom/point-to-range.ts +++ b/blocksuite/affine/shared/src/utils/dom/point-to-range.ts @@ -90,9 +90,65 @@ export function getCurrentNativeRange(selection = window.getSelection()) { export function handleNativeRangeAtPoint(x: number, y: number) { const range = caretRangeFromPoint(x, y); + if (range) { + normalizeCaretRange(range); + } + const startContainer = range?.startContainer; // click on rich text if (startContainer instanceof Node) { resetNativeSelection(range); } } + +function lastMeaningfulTextNode(node: Node) { + const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT, { + acceptNode(node) { + return node.textContent && node.textContent?.trim().length > 0 + ? NodeFilter.FILTER_ACCEPT + : NodeFilter.FILTER_REJECT; + }, + }); + + let last = null; + while (walker.nextNode()) { + last = walker.currentNode; + } + return last; +} + +function normalizeCaretRange(range: Range) { + let { startContainer, startOffset } = range; + if (startContainer.nodeType === Node.TEXT_NODE) return; + + // Try to find text in the element at `startOffset` + const offsetEl = + startOffset > 0 + ? startContainer.childNodes[startOffset - 1] + : startContainer.childNodes[0]; + if (offsetEl) { + if (offsetEl.nodeType === Node.TEXT_NODE) { + range.setStart( + offsetEl, + startOffset > 0 ? (offsetEl.textContent?.length ?? 0) : 0 + ); + range.collapse(true); + return; + } + + const text = lastMeaningfulTextNode(offsetEl); + if (text) { + range.setStart(text, text.textContent?.length ?? 0); + range.collapse(true); + return; + } + } + + // Fallback, try to find text in startContainer + const text = lastMeaningfulTextNode(startContainer); + if (text) { + range.setStart(text, text.textContent?.length ?? 0); + range.collapse(true); + return; + } +}