From 63a874528aae9771e59727624f828ca80cd1e657 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sat, 3 Oct 2026 20:07:24 +0300 Subject: [PATCH] improve text selection --- .../template_builder/dynamic_menu.vue | 4 +- .../template_builder/dynamic_section.vue | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/app/javascript/template_builder/dynamic_menu.vue b/app/javascript/template_builder/dynamic_menu.vue index 2762ee78e..cd6ac19a8 100644 --- a/app/javascript/template_builder/dynamic_menu.vue +++ b/app/javascript/template_builder/dynamic_menu.vue @@ -468,7 +468,7 @@ export default { } }, mounted () { - this.editor.view.dom.addEventListener('mousedown', this.onMouseDown) + this.editor.view.dom.parentElement.addEventListener('mousedown', this.onMouseDown) document.addEventListener('mouseup', this.onMouseUp) @@ -476,7 +476,7 @@ export default { }, beforeUnmount () { if (!this.editor.isDestroyed) { - this.editor.view.dom.removeEventListener('mousedown', this.onMouseDown) + this.editor.view.dom.parentElement.removeEventListener('mousedown', this.onMouseDown) this.editor.off('transaction', this.onTransaction) } diff --git a/app/javascript/template_builder/dynamic_section.vue b/app/javascript/template_builder/dynamic_section.vue index bc9fe2b94..05aa949bc 100644 --- a/app/javascript/template_builder/dynamic_section.vue +++ b/app/javascript/template_builder/dynamic_section.vue @@ -2,6 +2,7 @@
import { shallowRef } from 'vue' import { DOMSerializer, Fragment } from '@tiptap/pm/model' +import { TextSelection } from '@tiptap/pm/state' import { findTable, CellSelection } from '@tiptap/pm/tables' import { v4 } from 'uuid' import { IconPlus, IconX } from '@tabler/icons-vue' @@ -964,6 +966,48 @@ export default { return true }, + closestPosAtCoords ({ clientX, clientY }) { + const view = this.editor.view + const rect = view.dom.getBoundingClientRect() + const left = Math.min(Math.max(clientX, rect.left + 1), rect.right - 1) + const top = Math.min(Math.max(clientY, rect.top + 1), rect.bottom - 1) + const caret = document.caretPositionFromPoint?.(left, top, { shadowRoots: [view.root] }) + + if (caret && view.dom.contains(caret.offsetNode)) { + return view.posAtDOM(caret.offsetNode, caret.offset) + } else { + return view.posAtCoords({ left, top }).pos + } + }, + onMarginMouseDown (event) { + const view = this.editor.view + + if (event.button !== 0 || !this.editable || this.isDrawMode || view.dom.contains(event.target)) { + return + } + + event.preventDefault() + + const anchor = this.closestPosAtCoords(event) + + const selectTo = (e) => { + const { doc, tr } = view.state + + view.dispatch(tr.setSelection(TextSelection.between(doc.resolve(anchor), doc.resolve(this.closestPosAtCoords(e))))) + } + + const onMouseUp = () => { + window.removeEventListener('mousemove', selectTo) + window.removeEventListener('mouseup', onMouseUp) + } + + selectTo(event) + + view.focus() + + window.addEventListener('mousemove', selectTo) + window.addEventListener('mouseup', onMouseUp) + }, onEditorPointerDown (event) { if (!this.isDrawMode || !this.editable || this.isDraggingField) { return