Skip to content

Commit 0f081ff

Browse files
committed
fix: popover menu renders at correct position in presentation mode
1 parent 2994622 commit 0f081ff

2 files changed

Lines changed: 78 additions & 7 deletions

File tree

packages/super-editor/src/extensions/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ const getStarterExtensions = () => {
153153
Image,
154154
BookmarkStart,
155155
BookmarkEnd,
156+
PopoverPlugin,
156157
Mention,
157158
Collaboration,
158159
CollaborationCursor,

packages/super-editor/src/extensions/popover-plugin/popover-plugin.js

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)