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

at main 150 lines 5.8 kB view raw
1import React, { useState, useEffect } from "react"; 2import { X, Loader2, History } from "lucide-react"; 3import { useTranslation } from "react-i18next"; 4import { formatDistanceToNow } from "date-fns"; 5import type { AnnotationItem, EditHistoryItem } from "../../types"; 6 7interface EditHistoryModalProps { 8 isOpen: boolean; 9 onClose: () => void; 10 item: AnnotationItem; 11} 12 13export default function EditHistoryModal({ 14 isOpen, 15 onClose, 16 item, 17}: EditHistoryModalProps) { 18 const { t } = useTranslation(); 19 const [history, setHistory] = useState<EditHistoryItem[]>([]); 20 const [loading, setLoading] = useState(false); 21 const [error, setError] = useState<string | null>(null); 22 23 useEffect(() => { 24 const fetchHistory = async () => { 25 if (!item.uri) return; 26 27 try { 28 setLoading(true); 29 setError(null); 30 const res = await fetch( 31 `/api/notes/history?uri=${encodeURIComponent(item.uri)}`, 32 ); 33 if (!res.ok) throw new Error("Failed to fetch history"); 34 const data = await res.json(); 35 setHistory(data); 36 } catch (err) { 37 console.error(err); 38 setError(t("editHistory.failedLoad")); 39 } finally { 40 setLoading(false); 41 } 42 }; 43 44 if (isOpen && item.uri) { 45 fetchHistory(); 46 document.body.style.overflow = "hidden"; 47 } 48 return () => { 49 document.body.style.overflow = "unset"; 50 }; 51 }, [isOpen, item.uri, t]); 52 53 if (!isOpen) return null; 54 55 return ( 56 <div 57 className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm animate-fade-in" 58 onClick={onClose} 59 > 60 <div 61 className="w-full max-w-lg bg-white dark:bg-surface-900 rounded-3xl shadow-2xl overflow-hidden flex flex-col max-h-[80vh]" 62 onClick={(e) => e.stopPropagation()} 63 > 64 <div className="p-4 flex justify-between items-center border-b border-surface-100 dark:border-surface-800 shrink-0"> 65 <div className="flex items-center gap-2"> 66 <History className="text-surface-500" size={20} /> 67 <h2 className="text-xl font-display font-bold text-surface-900 dark:text-white"> 68 {t("editHistory.title")} 69 </h2> 70 </div> 71 <button 72 onClick={onClose} 73 className="p-2 text-surface-400 hover:text-surface-900 dark:hover:text-white hover:bg-surface-50 dark:hover:bg-surface-800 rounded-full transition-colors" 74 > 75 <X size={20} /> 76 </button> 77 </div> 78 79 <div className="p-0 overflow-y-auto flex-1 custom-scrollbar"> 80 {loading ? ( 81 <div className="flex justify-center p-8"> 82 <Loader2 className="animate-spin text-primary-500" size={32} /> 83 </div> 84 ) : error ? ( 85 <div className="p-8 text-center text-red-500">{error}</div> 86 ) : history.length === 0 ? ( 87 <div className="p-8 text-center text-surface-500"> 88 {t("editHistory.noHistory")} 89 </div> 90 ) : ( 91 <div className="divide-y divide-surface-100 dark:divide-surface-800"> 92 <div className="p-4 bg-primary-50/50 dark:bg-primary-900/10"> 93 <div className="flex justify-between items-start mb-2"> 94 <span className="text-xs font-bold uppercase tracking-wider text-primary-600 dark:text-primary-400"> 95 {t("editHistory.currentVersion")} 96 </span> 97 <span className="text-xs text-surface-400"> 98 {item.editedAt 99 ? t("editHistory.editedAgo", { 100 time: formatDistanceToNow(new Date(item.editedAt)), 101 }) 102 : t("editHistory.postedAgo", { 103 time: formatDistanceToNow(new Date(item.createdAt)), 104 })} 105 </span> 106 </div> 107 <div className="text-surface-900 dark:text-white whitespace-pre-wrap text-sm"> 108 {item.text || item.body?.value} 109 </div> 110 </div> 111 112 {history.map((edit, index) => ( 113 <div 114 key={edit.id || index} 115 className="p-4 hover:bg-surface-50 dark:hover:bg-surface-800/50 transition-colors" 116 > 117 <div className="flex justify-between items-start mb-2"> 118 <span className="text-xs font-medium text-surface-500"> 119 {t("editHistory.previousVersion")} 120 </span> 121 <span 122 className="text-xs text-surface-400" 123 title={new Date(edit.editedAt).toLocaleString()} 124 > 125 {t("editHistory.timeAgo", { 126 time: formatDistanceToNow(new Date(edit.editedAt)), 127 })} 128 </span> 129 </div> 130 <div className="text-surface-600 dark:text-surface-300 whitespace-pre-wrap text-sm"> 131 {edit.previousContent} 132 </div> 133 </div> 134 ))} 135 </div> 136 )} 137 </div> 138 139 <div className="p-4 border-t border-surface-100 dark:border-surface-800 bg-surface-50 dark:bg-surface-800/50 shrink-0"> 140 <button 141 onClick={onClose} 142 className="w-full py-2.5 bg-white dark:bg-surface-800 border border-surface-200 dark:border-surface-700 text-surface-700 dark:text-surface-200 font-medium rounded-xl hover:bg-surface-50 dark:hover:bg-surface-700 transition-colors" 143 > 144 {t("editHistory.close")} 145 </button> 146 </div> 147 </div> 148 </div> 149 ); 150}