a tool for shared writing and social publishing

fix placeholder and positioning for bsky mention popover

+85 -70
+18 -4
app/[leaflet_id]/publish/BskyPostEditorProsemirror.tsx
··· 148 148 const pos = view.state.selection.from; 149 149 setMentionInsertPos(pos); 150 150 const coords = view.coordsAtPos(pos - 1); 151 - setMentionCoords({ 152 - top: coords.bottom + window.scrollY, 153 - left: coords.left + window.scrollX, 154 - }); 151 + 152 + // Get coordinates relative to the positioned parent container 153 + const editorEl = view.dom; 154 + const container = editorEl.closest(".relative") as HTMLElement | null; 155 + 156 + if (container) { 157 + const containerRect = container.getBoundingClientRect(); 158 + setMentionCoords({ 159 + top: coords.bottom - containerRect.top, 160 + left: coords.left - containerRect.left, 161 + }); 162 + } else { 163 + setMentionCoords({ 164 + top: coords.bottom, 165 + left: coords.left, 166 + }); 167 + } 155 168 setMentionOpen(true); 156 169 }, []); 157 170 ··· 270 283 view={viewRef} 271 284 onSelect={handleMentionSelect} 272 285 coords={mentionCoords} 286 + placeholder="Search people..." 273 287 /> 274 288 {editorState?.doc.textContent.length === 0 && ( 275 289 <div className="italic text-tertiary absolute top-0 left-0 pointer-events-none">
+67 -66
components/Mention.tsx
··· 18 18 view: React.RefObject<EditorView | null>; 19 19 onSelect: (mention: Mention) => void; 20 20 coords: { top: number; left: number } | null; 21 + placeholder?: string; 21 22 }) { 22 23 const [searchQuery, setSearchQuery] = useState(""); 23 24 const [noResults, setNoResults] = useState(false); ··· 207 208 placeholder={ 208 209 scope.type === "publication" 209 210 ? "Search posts..." 210 - : "Search people & publications..." 211 + : props.placeholder ?? "Search people & publications..." 211 212 } 212 213 className="flex-1 w-full min-w-0 bg-transparent border-none outline-none text-sm placeholder:text-tertiary" 213 214 /> ··· 219 220 No results found 220 221 </div> 221 222 )} 222 - <ul className="list-none p-0 text-sm flex flex-col group-data-[side=top]/mention-menu:flex-col-reverse"> 223 - {sortedSuggestions.map((result, index) => { 224 - const prevResult = sortedSuggestions[index - 1]; 225 - const showHeader = 226 - index === 0 || 227 - (prevResult && prevResult.type !== result.type); 223 + <ul className="list-none p-0 text-sm flex flex-col group-data-[side=top]/mention-menu:flex-col-reverse"> 224 + {sortedSuggestions.map((result, index) => { 225 + const prevResult = sortedSuggestions[index - 1]; 226 + const showHeader = 227 + index === 0 || 228 + (prevResult && prevResult.type !== result.type); 228 229 229 - return ( 230 - <Fragment 231 - key={result.type === "did" ? result.did : result.uri} 232 - > 233 - {showHeader && ( 234 - <> 235 - {index > 0 && ( 236 - <hr className="border-border-light mx-1 my-1" /> 237 - )} 238 - <div className="text-xs text-tertiary font-bold pt-1 px-2"> 239 - {getHeader(result.type, scope)} 240 - </div> 241 - </> 242 - )} 243 - {result.type === "did" ? ( 244 - <DidResult 245 - onClick={() => { 246 - props.onSelect(result); 247 - props.onOpenChange(false); 248 - }} 249 - onMouseDown={(e) => e.preventDefault()} 250 - displayName={result.displayName} 251 - handle={result.handle} 252 - avatar={result.avatar} 253 - selected={index === suggestionIndex} 254 - /> 255 - ) : result.type === "publication" ? ( 256 - <PublicationResult 257 - onClick={() => { 258 - props.onSelect(result); 259 - props.onOpenChange(false); 260 - }} 261 - onMouseDown={(e) => e.preventDefault()} 262 - pubName={result.name} 263 - uri={result.uri} 264 - selected={index === suggestionIndex} 265 - onPostsClick={() => { 266 - handleScopeChange({ 267 - type: "publication", 268 - uri: result.uri, 269 - name: result.name, 270 - }); 271 - }} 272 - /> 273 - ) : ( 274 - <PostResult 275 - onClick={() => { 276 - props.onSelect(result); 277 - props.onOpenChange(false); 278 - }} 279 - onMouseDown={(e) => e.preventDefault()} 280 - title={result.title} 281 - selected={index === suggestionIndex} 282 - /> 283 - )} 284 - </Fragment> 285 - ); 286 - })} 287 - </ul> 230 + return ( 231 + <Fragment 232 + key={result.type === "did" ? result.did : result.uri} 233 + > 234 + {showHeader && ( 235 + <> 236 + {index > 0 && ( 237 + <hr className="border-border-light mx-1 my-1" /> 238 + )} 239 + <div className="text-xs text-tertiary font-bold pt-1 px-2"> 240 + {getHeader(result.type, scope)} 241 + </div> 242 + </> 243 + )} 244 + {result.type === "did" ? ( 245 + <DidResult 246 + onClick={() => { 247 + props.onSelect(result); 248 + props.onOpenChange(false); 249 + }} 250 + onMouseDown={(e) => e.preventDefault()} 251 + displayName={result.displayName} 252 + handle={result.handle} 253 + avatar={result.avatar} 254 + selected={index === suggestionIndex} 255 + /> 256 + ) : result.type === "publication" ? ( 257 + <PublicationResult 258 + onClick={() => { 259 + props.onSelect(result); 260 + props.onOpenChange(false); 261 + }} 262 + onMouseDown={(e) => e.preventDefault()} 263 + pubName={result.name} 264 + uri={result.uri} 265 + selected={index === suggestionIndex} 266 + onPostsClick={() => { 267 + handleScopeChange({ 268 + type: "publication", 269 + uri: result.uri, 270 + name: result.name, 271 + }); 272 + }} 273 + /> 274 + ) : ( 275 + <PostResult 276 + onClick={() => { 277 + props.onSelect(result); 278 + props.onOpenChange(false); 279 + }} 280 + onMouseDown={(e) => e.preventDefault()} 281 + title={result.title} 282 + selected={index === suggestionIndex} 283 + /> 284 + )} 285 + </Fragment> 286 + ); 287 + })} 288 + </ul> 288 289 </div> 289 290 </Popover.Content> 290 291 </Popover.Portal>