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

Configure Feed

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

at main 115 lines 3.9 kB view raw
1import React, { useState } from "react"; 2import { Button } from "../ui"; 3import { ExternalLink, Shield } from "lucide-react"; 4import { addSkippedHostname } from "../../store/preferences"; 5import { useTranslation } from "react-i18next"; 6 7interface ExternalLinkModalProps { 8 isOpen: boolean; 9 onClose: () => void; 10 url: string | null; 11} 12 13export default function ExternalLinkModal({ 14 isOpen, 15 onClose, 16 url, 17}: ExternalLinkModalProps) { 18 const { t } = useTranslation(); 19 const [dontAskAgain, setDontAskAgain] = useState(false); 20 21 if (!isOpen || !url) return null; 22 23 const displayUrl = url.split("#:~:text=")[0]; 24 25 const handleContinue = () => { 26 if (dontAskAgain && url) { 27 try { 28 const hostname = new URL(url).hostname; 29 addSkippedHostname(hostname); 30 } catch (e) { 31 console.error("Invalid URL", e); 32 } 33 } 34 window.open(url, "_blank", "noopener,noreferrer"); 35 onClose(); 36 }; 37 38 const hostname = (() => { 39 try { 40 return new URL(url).hostname; 41 } catch { 42 return "this site"; 43 } 44 })(); 45 46 return ( 47 <div 48 className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/40 backdrop-blur-sm animate-fade-in" 49 onClick={onClose} 50 > 51 <div 52 className="bg-white dark:bg-surface-900 rounded-xl shadow-2xl max-w-md w-full animate-scale-in ring-1 ring-surface-200 dark:ring-surface-700 overflow-hidden" 53 onClick={(e) => e.stopPropagation()} 54 > 55 <div className="px-6 pt-6 pb-4"> 56 <div className="flex items-start gap-3"> 57 <div className="w-9 h-9 bg-primary-100 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400 rounded-lg flex items-center justify-center flex-shrink-0 mt-0.5"> 58 <Shield size={18} /> 59 </div> 60 <div className="min-w-0"> 61 <h2 className="text-base font-semibold text-surface-900 dark:text-white"> 62 {t("externalLink.title")} 63 </h2> 64 <p className="text-sm text-surface-500 dark:text-surface-400 mt-1"> 65 {t("externalLink.message")} 66 </p> 67 </div> 68 </div> 69 70 <div className="mt-4 flex items-center gap-2 bg-surface-50 dark:bg-surface-800/60 border border-surface-200 dark:border-surface-700 rounded-lg px-3 py-2.5"> 71 <ExternalLink 72 size={14} 73 className="text-surface-400 dark:text-surface-500 flex-shrink-0" 74 /> 75 <span className="text-sm text-surface-700 dark:text-surface-300 break-all line-clamp-2"> 76 {displayUrl} 77 </span> 78 </div> 79 </div> 80 81 <div className="px-6 pb-5 pt-2 flex flex-col gap-3"> 82 <label className="flex items-center gap-2 cursor-pointer select-none group"> 83 <input 84 type="checkbox" 85 checked={dontAskAgain} 86 onChange={(e) => setDontAskAgain(e.target.checked)} 87 className="rounded border-surface-300 dark:border-surface-600 text-primary-600 focus:ring-primary-500 w-3.5 h-3.5 cursor-pointer" 88 /> 89 <span className="text-xs text-surface-500 dark:text-surface-400 group-hover:text-surface-600 dark:group-hover:text-surface-300 transition-colors"> 90 {t("externalLink.alwaysAllow", { hostname })} 91 </span> 92 </label> 93 94 <div className="flex gap-2"> 95 <Button 96 onClick={onClose} 97 variant="ghost" 98 className="flex-1 justify-center" 99 > 100 {t("externalLink.cancel")} 101 </Button> 102 <Button 103 onClick={handleContinue} 104 variant="primary" 105 className="flex-1 justify-center" 106 icon={<ExternalLink size={14} />} 107 > 108 {t("externalLink.open")} 109 </Button> 110 </div> 111 </div> 112 </div> 113 </div> 114 ); 115}