Write on the margins of the internet. Powered by the AT Protocol. margin.at
extension web atproto comments

various frontend bug fixes

+137 -66
-1
web/src/components/LeftSidebar.jsx
··· 14 14 Settings, 15 15 ChevronUp, 16 16 } from "lucide-react"; 17 - import logo from "../assets/logo.svg"; 18 17 import { getUnreadNotificationCount } from "../api/client"; 19 18 20 19 export default function LeftSidebar() {
+68 -8
web/src/components/ShareMenu.jsx
··· 101 101 const [isOpen, setIsOpen] = useState(false); 102 102 const [copied, setCopied] = useState(false); 103 103 const [copiedAturi, setCopiedAturi] = useState(false); 104 + const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 }); 104 105 const menuRef = useRef(null); 106 + const buttonRef = useRef(null); 107 + 108 + const calculatePosition = () => { 109 + if (!buttonRef.current) return; 110 + const rect = buttonRef.current.getBoundingClientRect(); 111 + const menuWidth = 220; 112 + const menuHeight = 300; 113 + const padding = 16; 114 + 115 + let left = rect.left + rect.width / 2 - menuWidth / 2; 116 + let top = rect.bottom + 8; 117 + 118 + if (left < padding) { 119 + left = padding; 120 + } else if (left + menuWidth > window.innerWidth - padding) { 121 + left = window.innerWidth - menuWidth - padding; 122 + } 123 + 124 + if (top + menuHeight > window.innerHeight - padding) { 125 + top = rect.top - menuHeight - 8; 126 + if (top < padding) top = padding; 127 + } 128 + 129 + setMenuPosition({ top, left }); 130 + }; 131 + 132 + const handleToggle = () => { 133 + if (!isOpen) { 134 + calculatePosition(); 135 + } 136 + setIsOpen(!isOpen); 137 + }; 105 138 106 139 const getShareUrl = () => { 107 140 if (customUrl) return customUrl; ··· 126 159 127 160 useEffect(() => { 128 161 const handleClickOutside = (e) => { 129 - if (menuRef.current && !menuRef.current.contains(e.target)) { 162 + if ( 163 + menuRef.current && 164 + !menuRef.current.contains(e.target) && 165 + buttonRef.current && 166 + !buttonRef.current.contains(e.target) 167 + ) { 130 168 setIsOpen(false); 131 169 } 132 170 }; 133 171 134 - const card = menuRef.current?.closest(".card"); 172 + const card = buttonRef.current?.closest( 173 + ".card, .annotation-card, .bookmark-card", 174 + ); 135 175 if (card) { 136 176 if (isOpen) { 137 - card.style.zIndex = "50"; 177 + card.classList.add("has-open-menu"); 138 178 } else { 139 - card.style.zIndex = ""; 179 + card.classList.remove("has-open-menu"); 140 180 } 141 181 } 142 182 143 183 if (isOpen) { 144 184 document.addEventListener("mousedown", handleClickOutside); 185 + window.addEventListener("scroll", calculatePosition, true); 186 + window.addEventListener("resize", calculatePosition); 145 187 } 146 - return () => document.removeEventListener("mousedown", handleClickOutside); 188 + return () => { 189 + document.removeEventListener("mousedown", handleClickOutside); 190 + window.removeEventListener("scroll", calculatePosition, true); 191 + window.removeEventListener("resize", calculatePosition); 192 + if (card) { 193 + card.classList.remove("has-open-menu"); 194 + } 195 + }; 147 196 }, [isOpen]); 148 197 149 198 const handleShareToFork = (domain) => { ··· 231 280 }; 232 281 233 282 return ( 234 - <div className="share-menu-container" ref={menuRef}> 283 + <div className="share-menu-container"> 235 284 <button 285 + ref={buttonRef} 236 286 className="annotation-action" 237 - onClick={() => setIsOpen(!isOpen)} 287 + onClick={handleToggle} 238 288 title="Share" 239 289 > 240 290 <svg ··· 256 306 </button> 257 307 258 308 {isOpen && ( 259 - <div className="share-menu"> 309 + <div 310 + className="share-menu" 311 + ref={menuRef} 312 + style={{ 313 + position: "fixed", 314 + top: menuPosition.top, 315 + left: menuPosition.left, 316 + right: "auto", 317 + transform: "none", 318 + }} 319 + > 260 320 {isSemble ? ( 261 321 <> 262 322 <div className="share-menu-section">
-1
web/src/components/TopNav.jsx
··· 31 31 import { FaEdge } from "react-icons/fa"; 32 32 import tangledLogo from "../assets/tangled.svg"; 33 33 import { getUnreadNotificationCount } from "../api/client"; 34 - import logo from "../assets/logo.svg"; 35 34 36 35 const isFirefox = 37 36 typeof navigator !== "undefined" && /Firefox/i.test(navigator.userAgent);
+1 -1
web/src/css/annotations.css
··· 163 163 gap: 8px; 164 164 padding-left: 0; 165 165 max-width: 100%; 166 - overflow: hidden; 166 + overflow: visible; 167 167 } 168 168 169 169 .annotation-source {
+10 -1
web/src/css/cards.css
··· 71 71 margin: 0; 72 72 display: -webkit-box; 73 73 -webkit-line-clamp: 2; 74 + line-clamp: 2; 74 75 -webkit-box-orient: vertical; 75 76 overflow: hidden; 76 77 } ··· 82 83 margin: 0; 83 84 display: -webkit-box; 84 85 -webkit-line-clamp: 2; 86 + line-clamp: 2; 85 87 -webkit-box-orient: vertical; 86 88 overflow: hidden; 87 89 } ··· 96 98 } 97 99 98 100 .bookmark-card:hover { 99 - z-index: 100 !important; 101 + z-index: 10; 100 102 overflow: visible !important; 101 103 } 102 104 105 + .bookmark-card.has-open-menu, 106 + .annotation-card.has-open-menu, 107 + .card.has-open-menu { 108 + z-index: 200 !important; 109 + } 110 + 103 111 .bookmark-site { 104 112 display: flex; 105 113 align-items: center; ··· 125 133 margin: 0; 126 134 display: -webkit-box; 127 135 -webkit-line-clamp: 2; 136 + line-clamp: 2; 128 137 -webkit-box-orient: vertical; 129 138 overflow: hidden; 130 139 }
+5 -3
web/src/css/feed.css
··· 47 47 border: 1px solid var(--border); 48 48 border-radius: var(--radius-lg); 49 49 position: relative; 50 - overflow: hidden; 50 + overflow: visible; 51 51 } 52 52 53 53 .feed > *:last-child { 54 54 border-bottom: 1px solid var(--border); 55 55 } 56 56 57 - .feed > *:hover { 58 - z-index: 10; 57 + .feed > *.has-open-menu, 58 + .feed > *:focus-within { 59 + z-index: 200 !important; 60 + overflow: visible !important; 59 61 } 60 62 61 63 .feed-page {
+48 -16
web/src/css/layout.css
··· 13 13 14 14 @media (max-width: 1024px) { 15 15 .app-layout { 16 - grid-template-columns: 240px 1fr; 16 + grid-template-columns: 220px 1fr; 17 17 } 18 18 } 19 19 ··· 44 44 45 45 .layout-mode-topnav .app-layout { 46 46 grid-template-columns: 1fr; 47 - max-width: 1600px; 48 - margin: 0 auto; 47 + max-width: 100%; 49 48 height: auto; 50 49 overflow: visible; 51 50 } ··· 60 59 } 61 60 62 61 .layout-mode-topnav .main-content > * { 63 - max-width: 1200px; 62 + max-width: 1100px; 64 63 margin: 0 auto; 64 + padding: 32px 32px 80px; 65 + } 66 + 67 + @media (min-width: 1400px) { 68 + .layout-mode-topnav .main-content > * { 69 + max-width: 1300px; 70 + } 71 + } 72 + 73 + @media (min-width: 1700px) { 74 + .layout-mode-topnav .main-content > * { 75 + max-width: 1500px; 76 + } 77 + } 78 + 79 + @media (max-width: 768px) { 80 + .layout-mode-topnav .app-layout { 81 + grid-template-columns: 1fr; 82 + max-width: none; 83 + height: auto; 84 + overflow: visible; 85 + } 86 + 87 + .layout-mode-topnav .top-nav { 88 + display: none; 89 + } 90 + 91 + .layout-mode-topnav .main-content { 92 + padding: 16px 12px 100px; 93 + } 94 + 95 + .layout-mode-topnav .main-content > * { 96 + max-width: 100%; 97 + padding: 0 4px; 98 + } 65 99 } 66 100 67 101 .top-nav-inner { ··· 237 271 border-radius: var(--radius-lg); 238 272 padding: 6px; 239 273 box-shadow: var(--shadow-lg); 240 - z-index: 200; 274 + z-index: 100; 241 275 } 242 276 243 277 .dropdown-right { ··· 335 369 width: 100%; 336 370 padding: 0; 337 371 overflow-y: auto; 372 + overflow-x: hidden; 338 373 height: 100%; 339 374 scrollbar-width: thin; 340 375 scrollbar-color: var(--bg-hover) transparent; 341 376 } 342 377 343 378 .main-content > * { 344 - max-width: 800px; 379 + max-width: 100%; 345 380 margin: 0 auto; 346 - padding: 32px 32px 80px; 381 + padding: 32px 40px 80px; 347 382 } 348 383 349 384 .main-content::-webkit-scrollbar { ··· 424 459 -webkit-backdrop-filter: blur(12px); 425 460 border-top: 1px solid var(--border); 426 461 padding: 8px 8px calc(8px + env(safe-area-inset-bottom)); 427 - z-index: 100; 428 - } 429 - 430 - .mobile-bottom-nav { 431 - display: none; 462 + z-index: 200; 432 463 justify-content: space-around; 433 464 align-items: center; 434 465 } ··· 574 605 } 575 606 576 607 .main-content > * { 577 - padding: 0; 608 + padding: 0 4px; 609 + max-width: 100%; 578 610 } 579 611 580 612 .feed-container { 581 613 border-radius: var(--radius-md); 582 - padding: 4px; 614 + padding: 0; 583 615 } 584 616 } 585 617 ··· 601 633 position: fixed; 602 634 inset: 0; 603 635 background: rgba(0, 0, 0, 0.5); 604 - z-index: 150; 636 + z-index: 300; 605 637 backdrop-filter: blur(2px); 606 638 -webkit-backdrop-filter: blur(2px); 607 639 animation: fadeIn 0.15s ease-out; ··· 616 648 border: 1px solid var(--border); 617 649 border-radius: var(--radius-xl); 618 650 padding: 6px; 619 - z-index: 151; 651 + z-index: 301; 620 652 box-shadow: var(--shadow-2xl); 621 653 animation: mobileMenuSlide 0.2s cubic-bezier(0.16, 1, 0.3, 1); 622 654 display: flex;
+1 -1
web/src/css/modals.css
··· 6 6 align-items: center; 7 7 justify-content: center; 8 8 padding: var(--spacing-md); 9 - z-index: 100; 9 + z-index: 500; 10 10 animation: fadeIn 0.15s ease-out; 11 11 } 12 12
+3 -7
web/src/css/utilities.css
··· 539 539 540 540 .share-menu-container { 541 541 position: relative; 542 - z-index: 50; 543 542 } 544 543 545 544 .share-menu { 546 - position: absolute; 547 - top: 100%; 548 - right: 0; 549 - margin-top: 8px; 550 545 background: var(--bg-elevated); 551 546 border: 1px solid var(--border); 552 547 border-radius: var(--radius-lg); 553 548 box-shadow: var(--shadow-lg); 554 - min-width: 200px; 549 + min-width: 220px; 550 + max-width: calc(100vw - 32px); 555 551 padding: 8px; 556 - z-index: 1000; 552 + z-index: 9999; 557 553 animation: fadeInUp 0.15s ease; 558 554 } 559 555
-1
web/src/pages/Login.jsx
··· 3 3 import { useAuth } from "../context/AuthContext"; 4 4 import { searchActors, startLogin } from "../api/client"; 5 5 import { AtSign } from "lucide-react"; 6 - import logo from "../assets/logo.svg"; 7 6 import SignUpModal from "../components/SignUpModal"; 8 7 9 8 export default function Login() {
-25
web/src/pages/Profile.jsx
··· 41 41 } 42 42 } 43 43 44 - function KeyIcon({ size = 16 }) { 45 - return ( 46 - <svg 47 - width={size} 48 - height={size} 49 - viewBox="0 0 24 24" 50 - fill="none" 51 - stroke="currentColor" 52 - strokeWidth="2" 53 - strokeLinecap="round" 54 - strokeLinejoin="round" 55 - > 56 - <path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4" /> 57 - </svg> 58 - ); 59 - } 60 - 61 44 export default function Profile() { 62 45 const { handle: routeHandle } = useParams(); 63 46 const { user, loading: authLoading } = useAuth(); ··· 425 408 </div> 426 409 ); 427 410 } 428 - 429 - function AppleIcon({ size = 16 }) { 430 - return ( 431 - <svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor"> 432 - <path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z" /> 433 - </svg> 434 - ); 435 - }
+1 -1
web/src/pages/Settings.jsx
··· 3 3 import { useTheme } from "../context/ThemeContext"; 4 4 import { useAuth } from "../context/AuthContext"; 5 5 import { Navigate } from "react-router-dom"; 6 - import { Monitor, Columns, Layout } from "lucide-react"; 6 + import { Columns, Layout } from "lucide-react"; 7 7 8 8 function KeyIcon({ size = 16 }) { 9 9 return (