@@ -61,7 +61,12 @@ import {
6161 createTaskListNodeView ,
6262 createTaskItemNodeView ,
6363} from "./nodeviews/list" ;
64- import { toggleSourceView , setSourceView , decorationPluginKey } from "./decorations" ;
64+ import {
65+ findSyntaxMarkerRegions ,
66+ toggleSourceView ,
67+ setSourceView ,
68+ decorationPluginKey ,
69+ } from "./decorations" ;
6570import type { MilkupConfig , MilkupEditor as IMilkupEditor , MilkupPlugin } from "./types" ;
6671import {
6772 insertTable ,
@@ -133,6 +138,10 @@ export class MilkupEditor implements IMilkupEditor {
133138 private searchInSelection = false ;
134139 private searchSelectionRange : { from : number ; to : number } | null = null ;
135140 private containerKeydownHandler : ( ( e : KeyboardEvent ) => void ) | null = null ;
141+ private selectionCorrectionHandler : ( ( ) => void ) | null = null ;
142+ private suppressSelectionCorrection = false ;
143+ private recentMouseupSelectionHead : number | null = null ;
144+ private recentMouseupAt = 0 ;
136145 private _destroyed = false ;
137146
138147 constructor ( container : HTMLElement , config : MilkupConfig = { } ) {
@@ -370,6 +379,10 @@ export class MilkupEditor implements IMilkupEditor {
370379 this . view . dom . parentElement ?. removeEventListener ( "keydown" , this . containerKeydownHandler ) ;
371380 this . containerKeydownHandler = null ;
372381 }
382+ if ( this . selectionCorrectionHandler ) {
383+ document . removeEventListener ( "selectionchange" , this . selectionCorrectionHandler ) ;
384+ this . selectionCorrectionHandler = null ;
385+ }
373386 this . searchWrapper ?. remove ( ) ;
374387 this . searchWrapper = null ;
375388 this . searchPanel = null ;
@@ -392,7 +405,6 @@ export class MilkupEditor implements IMilkupEditor {
392405 private handleEditorClick ( view : EditorView , pos : number , event : MouseEvent ) : boolean {
393406 const { state } = view ;
394407 const { doc } = state ;
395- const editorRect = view . dom . getBoundingClientRect ( ) ;
396408 const clickY = event . clientY ;
397409
398410 // 获取第一个和最后一个块节点的位置
@@ -545,6 +557,7 @@ export class MilkupEditor implements IMilkupEditor {
545557 "click" ,
546558 ( e : Event ) => {
547559 const me = e as MouseEvent ;
560+ this . correctSelectionFromHiddenSyntaxMarker ( ) ;
548561 const linkEl = this . findLinkElement ( me . target as HTMLElement ) ;
549562 if ( ! linkEl ) return ;
550563
@@ -554,6 +567,16 @@ export class MilkupEditor implements IMilkupEditor {
554567 true // capture 阶段
555568 ) ;
556569
570+ dom . addEventListener (
571+ "mouseup" ,
572+ ( e : Event ) => {
573+ this . recordMouseupSelectionSnapshot ( ) ;
574+ queueMicrotask ( ( ) => this . correctSelectionFromHiddenSyntaxMarker ( ) ) ;
575+ setTimeout ( ( ) => this . correctSelectionFromHiddenSyntaxMarker ( ) , 0 ) ;
576+ } ,
577+ true
578+ ) ;
579+
557580 // mousemove 检测链接 hover
558581 dom . addEventListener ( "mousemove" , ( e : Event ) => {
559582 const me = e as MouseEvent ;
@@ -582,6 +605,70 @@ export class MilkupEditor implements IMilkupEditor {
582605 passive : true ,
583606 } ) ;
584607 }
608+
609+ if ( ! this . selectionCorrectionHandler ) {
610+ this . selectionCorrectionHandler = ( ) => {
611+ if ( ! this . view . hasFocus ( ) ) return ;
612+ this . correctSelectionFromHiddenSyntaxMarker ( ) ;
613+ } ;
614+ document . addEventListener ( "selectionchange" , this . selectionCorrectionHandler ) ;
615+ }
616+ }
617+
618+ private recordMouseupSelectionSnapshot ( ) : void {
619+ const { selection } = this . view . state ;
620+ if ( ! selection . empty ) return ;
621+ if ( this . isPositionInsideSyntaxMarker ( selection . head ) ) return ;
622+ this . recentMouseupSelectionHead = selection . head ;
623+ this . recentMouseupAt = Date . now ( ) ;
624+ }
625+
626+ private isPositionInsideSyntaxMarker ( pos : number ) : boolean {
627+ const syntaxRegions = findSyntaxMarkerRegions ( this . view . state . doc ) ;
628+ return syntaxRegions . some ( ( region ) => pos >= region . from && pos <= region . to ) ;
629+ }
630+
631+ private correctSelectionFromHiddenSyntaxMarker ( ) : void {
632+ if ( this . suppressSelectionCorrection ) return ;
633+
634+ const nativeSelection = window . getSelection ( ) ;
635+ if ( ! nativeSelection ?. isCollapsed ) return ;
636+
637+ const anchorNode = nativeSelection . anchorNode ;
638+ const now = Date . now ( ) ;
639+ const recentHead = this . recentMouseupSelectionHead ;
640+ if ( recentHead === null || now - this . recentMouseupAt > 250 ) return ;
641+ if ( this . isPositionInsideSyntaxMarker ( recentHead ) ) return ;
642+
643+ let nativePos : number | null = null ;
644+ try {
645+ if ( anchorNode ) {
646+ nativePos = this . view . posAtDOM ( anchorNode , nativeSelection . anchorOffset ) ;
647+ }
648+ } catch {
649+ nativePos = null ;
650+ }
651+
652+ const { selection } = this . view . state ;
653+ const anchorText = anchorNode ?. textContent ?? "" ;
654+ const nativeLooksLikeSyntaxMarker =
655+ ( nativePos !== null && this . isPositionInsideSyntaxMarker ( nativePos ) ) ||
656+ ( anchorText === "`" && this . isPositionInsideSyntaxMarker ( selection . head ) ) ;
657+
658+ if ( ! nativeLooksLikeSyntaxMarker ) return ;
659+ if ( selection . head === recentHead ) return ;
660+
661+ this . suppressSelectionCorrection = true ;
662+ try {
663+ this . view . dispatch (
664+ this . view . state . tr . setSelection ( TextSelection . create ( this . view . state . doc , recentHead ) )
665+ ) ;
666+ this . view . focus ( ) ;
667+ } finally {
668+ requestAnimationFrame ( ( ) => {
669+ this . suppressSelectionCorrection = false ;
670+ } ) ;
671+ }
585672 }
586673
587674 private showLinkTooltip ( linkEl : HTMLAnchorElement , href : string ) : void {
0 commit comments