@@ -127,11 +127,13 @@ class Popover {
127127 users : this . editor . users ,
128128 mention : atMention ,
129129 inserMention : ( user ) => {
130- const { $from } = this . state . selection ;
130+ // Use fresh state from the view, not the stale captured state
131+ const currentState = this . editor . view . state ;
132+ const { $from } = currentState . selection ;
131133 const length = atMention . length ;
132134 const attributes = { ...user } ;
133135 const mentionNode = this . editor . schema . nodes . mention . create ( attributes ) ;
134- const tr = this . state . tr . replaceWith ( $from . pos - length , $from . pos , mentionNode ) ;
136+ const tr = currentState . tr . replaceWith ( $from . pos - length , $from . pos , mentionNode ) ;
135137 this . editor . view . dispatch ( tr ) ;
136138 this . editor . view . focus ( ) ;
137139 } ,
@@ -148,19 +150,87 @@ class Popover {
148150 }
149151
150152 showPopoverAtPosition ( pos ) {
151- const end = this . view . coordsAtPos ( pos ) ;
153+ let left = 0 ;
154+ let top = 0 ;
155+ let source = 'fallback' ;
156+
157+ // In presentation mode, find position using DOM elements in painterHost
158+ const presentationEditor = this . editor . presentationEditor ;
159+ if ( presentationEditor ) {
160+ const result = this . getViewportCoordsFromPainterHost ( presentationEditor , pos ) ;
161+ if ( result ) {
162+ left = result . left ;
163+ top = result . bottom ;
164+ source = 'painterHost DOM' ;
165+ }
166+ }
167+
168+ // Fallback to view.coordsAtPos for non-presentation mode
169+ if ( source === 'fallback' ) {
170+ const coords = this . view . coordsAtPos ( pos ) ;
171+ left = coords . left ;
172+ top = coords . bottom ;
173+ }
174+
152175 this . popoverRect = {
153176 width : 0 ,
154177 height : 0 ,
155- top : end . bottom ,
156- left : end . left ,
157- bottom : end . bottom ,
158- right : end . left ,
178+ top : top ,
179+ left : left ,
180+ bottom : top ,
181+ right : left ,
159182 } ;
160183
161184 this . tippyInstance . show ( ) ;
162185 }
163186
187+ /**
188+ * Get viewport coordinates by finding the DOM element in the painted content.
189+ * This works in presentation mode where the actual DOM is off-screen but
190+ * painted elements exist in the painterHost.
191+ */
192+ getViewportCoordsFromPainterHost ( presentationEditor , pos ) {
193+ // Access painterHost through the DOM - it's a private field but we can find it by class
194+ const visibleHost = presentationEditor . element ;
195+ if ( ! visibleHost ) return null ;
196+
197+ // painterHost has class 'presentation-editor__pages'
198+ const painterHost = visibleHost . querySelector ( '.presentation-editor__pages' ) ;
199+ if ( ! painterHost ) return null ;
200+
201+ // Find all page elements
202+ const pageEls = painterHost . querySelectorAll ( '.superdoc-page[data-page-index]' ) ;
203+ if ( ! pageEls . length ) return null ;
204+
205+ // Search through pages for a span containing this position
206+ for ( const pageEl of pageEls ) {
207+ const spanEls = pageEl . querySelectorAll ( 'span[data-pm-start][data-pm-end]' ) ;
208+ for ( const spanEl of spanEls ) {
209+ const pmStart = Number ( spanEl . dataset . pmStart ) ;
210+ const pmEnd = Number ( spanEl . dataset . pmEnd ) ;
211+
212+ if ( pos >= pmStart && pos <= pmEnd && spanEl . firstChild ?. nodeType === Node . TEXT_NODE ) {
213+ const textNode = spanEl . firstChild ;
214+ const charIndex = Math . min ( pos - pmStart , textNode . length ) ;
215+
216+ const range = document . createRange ( ) ;
217+ range . setStart ( textNode , charIndex ) ;
218+ range . setEnd ( textNode , charIndex ) ;
219+
220+ const rect = range . getBoundingClientRect ( ) ;
221+
222+ return {
223+ left : rect . left ,
224+ top : rect . top ,
225+ bottom : rect . bottom ,
226+ } ;
227+ }
228+ }
229+ }
230+
231+ return null ;
232+ }
233+
164234 getMentionText ( from ) {
165235 const maxLookBehind = 20 ;
166236 const startPos = Math . max ( 0 , from - maxLookBehind ) ;
0 commit comments