forked from
margin.at/margin
Write on the margins of the internet. Powered by the AT Protocol.
1import { useState, useEffect } from "react";
2import { Link } from "react-router-dom";
3import { ExternalLink, Sun, Moon, Monitor } from "lucide-react";
4import {
5 SiFirefox,
6 SiGooglechrome,
7 SiGithub,
8 SiBluesky,
9 SiApple,
10 SiKofi,
11 SiDiscord,
12} from "react-icons/si";
13import { FaEdge } from "react-icons/fa";
14import { useAuth } from "../context/AuthContext";
15import { useTheme } from "../context/ThemeContext";
16import { getTrendingTags } from "../api/client";
17
18const isFirefox =
19 typeof navigator !== "undefined" && /Firefox/i.test(navigator.userAgent);
20const isEdge =
21 typeof navigator !== "undefined" && /Edg/i.test(navigator.userAgent);
22const isMobileSafari =
23 typeof navigator !== "undefined" &&
24 /iPhone|iPad|iPod/.test(navigator.userAgent) &&
25 /Safari/.test(navigator.userAgent) &&
26 !/CriOS|FxiOS|OPiOS|EdgiOS/.test(navigator.userAgent);
27
28function getExtensionInfo() {
29 if (isMobileSafari) {
30 return {
31 url: "https://margin.at/soon",
32 icon: SiApple,
33 name: "iOS",
34 label: "Coming Soon",
35 };
36 }
37 if (isFirefox) {
38 return {
39 url: "https://addons.mozilla.org/en-US/firefox/addon/margin/",
40 icon: SiFirefox,
41 name: "Firefox",
42 label: "Install for Firefox",
43 };
44 }
45 if (isEdge) {
46 return {
47 url: "https://microsoftedge.microsoft.com/addons/detail/margin/nfjnmllpdgcdnhmmggjihjbidmeadddn",
48 icon: FaEdge,
49 name: "Edge",
50 label: "Install for Edge",
51 };
52 }
53 return {
54 url: "https://chromewebstore.google.com/detail/margin/cgpmbiiagnehkikhcbnhiagfomajncpa/",
55 icon: SiGooglechrome,
56 name: "Chrome",
57 label: "Install for Chrome",
58 };
59}
60
61export default function RightSidebar() {
62 const { theme, setTheme } = useTheme();
63 const { isAuthenticated } = useAuth();
64 const ext = getExtensionInfo();
65 const ExtIcon = ext.icon;
66 const [trendingTags, setTrendingTags] = useState([]);
67 const [loading, setLoading] = useState(true);
68
69 useEffect(() => {
70 getTrendingTags()
71 .then((tags) => setTrendingTags(tags))
72 .catch((err) => console.error("Failed to fetch trending tags:", err))
73 .finally(() => setLoading(false));
74 }, []);
75
76 return (
77 <aside className="right-sidebar">
78 <div className="right-section">
79 <h3 className="right-section-title">
80 {isMobileSafari ? "Save from Safari" : "Get the Extension"}
81 </h3>
82 <p className="right-section-desc">
83 {isMobileSafari
84 ? "Bookmark pages using Safari's share sheet"
85 : "Annotate, highlight, and bookmark any webpage"}
86 </p>
87 <a
88 href={ext.url}
89 target="_blank"
90 rel="noopener noreferrer"
91 className="right-extension-btn"
92 >
93 <ExtIcon size={18} />
94 {ext.label}
95 <ExternalLink size={14} />
96 </a>
97 </div>
98
99 {isAuthenticated ? (
100 <div className="right-section">
101 <h3 className="right-section-title">Trending Tags</h3>
102 <div className="right-links">
103 {loading ? (
104 <span className="right-section-desc">Loading...</span>
105 ) : trendingTags.length > 0 ? (
106 trendingTags.map(({ tag, count }) => (
107 <Link
108 key={tag}
109 to={`/?tag=${encodeURIComponent(tag)}`}
110 className="right-link"
111 >
112 <span>#{tag}</span>
113 <span style={{ fontSize: "0.75rem", opacity: 0.6 }}>
114 {count}
115 </span>
116 </Link>
117 ))
118 ) : (
119 <span className="right-section-desc">No trending tags yet</span>
120 )}
121 </div>
122 </div>
123 ) : (
124 <div className="right-section">
125 <h3 className="right-section-title">Explore</h3>
126 <nav className="right-links">
127 <Link to="/url" className="right-link">
128 Browse by URL
129 </Link>
130 </nav>
131 </div>
132 )}
133
134 <div className="right-section">
135 <h3 className="right-section-title">Resources</h3>
136 <nav className="right-links">
137 <a
138 href="https://github.com/margin-at/margin"
139 target="_blank"
140 rel="noopener noreferrer"
141 className="right-link"
142 >
143 <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
144 <SiGithub size={16} />
145 GitHub
146 </div>
147 <ExternalLink size={12} />
148 </a>
149 <a
150 href="https://tangled.org/margin.at/margin"
151 target="_blank"
152 rel="noopener noreferrer"
153 className="right-link"
154 >
155 <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
156 <div className="tangled-icon" />
157 Tangled
158 </div>
159 <ExternalLink size={12} />
160 </a>
161 <a
162 href="https://bsky.app/profile/margin.at"
163 target="_blank"
164 rel="noopener noreferrer"
165 className="right-link"
166 >
167 <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
168 <SiBluesky size={16} />
169 Bluesky
170 </div>
171 <ExternalLink size={12} />
172 </a>
173 <a
174 href="https://discord.gg/ZQbkGqwzBH"
175 target="_blank"
176 rel="noopener noreferrer"
177 className="right-link"
178 >
179 <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
180 <SiDiscord size={16} />
181 Discord
182 </div>
183 <ExternalLink size={12} />
184 </a>
185 <a
186 href="https://ko-fi.com/scan"
187 target="_blank"
188 rel="noopener noreferrer"
189 className="right-link"
190 >
191 <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
192 <SiKofi size={16} />
193 Donate
194 </div>
195 <ExternalLink size={12} />
196 </a>
197 </nav>
198 </div>
199
200 <div className="right-footer">
201 <div className="footer-links">
202 <Link to="/privacy">Privacy</Link>
203 <span>·</span>
204 <Link to="/terms">Terms</Link>
205 </div>
206 <button
207 onClick={() => {
208 const next =
209 theme === "system"
210 ? "light"
211 : theme === "light"
212 ? "dark"
213 : "system";
214 setTheme(next);
215 }}
216 className="theme-toggle-mini"
217 title={`Theme: ${theme}`}
218 >
219 {theme === "system" && <Monitor size={14} />}
220 {theme === "light" && <Sun size={14} />}
221 {theme === "dark" && <Moon size={14} />}
222 </button>
223 </div>
224 </aside>
225 );
226}