(READ ONLY) Margin is an open annotation layer for the internet. Powered by the AT Protocol. margin.at
extension web atproto comments
98
fork

Configure Feed

Select the types of activity you want to include in your feed.

various frontend bug fixes

scanash00 3ec15276 ac7f76cb

+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 (