Explore the margin.at codebase, lexicons, and more! margin.at
at v0.1.11 9.9 kB view raw
1const API_BASE = "/api"; 2const AUTH_BASE = "/auth"; 3 4async function request(endpoint, options = {}) { 5 const response = await fetch(endpoint, { 6 credentials: "include", 7 headers: { 8 "Content-Type": "application/json", 9 ...options.headers, 10 }, 11 ...options, 12 }); 13 14 if (!response.ok) { 15 const error = await response.text(); 16 throw new Error(error || `HTTP ${response.status}`); 17 } 18 19 return response.json(); 20} 21 22export async function getURLMetadata(url) { 23 return request(`${API_BASE}/url-metadata?url=${encodeURIComponent(url)}`); 24} 25 26export async function getAnnotationFeed(limit = 50, offset = 0) { 27 return request( 28 `${API_BASE}/annotations/feed?limit=${limit}&offset=${offset}`, 29 ); 30} 31 32export async function getAnnotations({ 33 source, 34 motivation, 35 limit = 50, 36 offset = 0, 37} = {}) { 38 let url = `${API_BASE}/annotations?limit=${limit}&offset=${offset}`; 39 if (source) url += `&source=${encodeURIComponent(source)}`; 40 if (motivation) url += `&motivation=${motivation}`; 41 return request(url); 42} 43 44export async function getByTarget(source, limit = 50, offset = 0) { 45 return request( 46 `${API_BASE}/targets?source=${encodeURIComponent(source)}&limit=${limit}&offset=${offset}`, 47 ); 48} 49 50export async function getAnnotation(uri) { 51 return request(`${API_BASE}/annotation?uri=${encodeURIComponent(uri)}`); 52} 53 54export async function getUserAnnotations(did, limit = 50, offset = 0) { 55 return request( 56 `${API_BASE}/users/${encodeURIComponent(did)}/annotations?limit=${limit}&offset=${offset}`, 57 ); 58} 59 60export async function getUserHighlights(did, limit = 50, offset = 0) { 61 return request( 62 `${API_BASE}/users/${encodeURIComponent(did)}/highlights?limit=${limit}&offset=${offset}`, 63 ); 64} 65 66export async function getUserBookmarks(did, limit = 50, offset = 0) { 67 return request( 68 `${API_BASE}/users/${encodeURIComponent(did)}/bookmarks?limit=${limit}&offset=${offset}`, 69 ); 70} 71 72export async function getHighlights(creatorDid, limit = 50, offset = 0) { 73 return request( 74 `${API_BASE}/highlights?creator=${encodeURIComponent(creatorDid)}&limit=${limit}&offset=${offset}`, 75 ); 76} 77 78export async function getBookmarks(creatorDid, limit = 50, offset = 0) { 79 return request( 80 `${API_BASE}/bookmarks?creator=${encodeURIComponent(creatorDid)}&limit=${limit}&offset=${offset}`, 81 ); 82} 83 84export async function getReplies(annotationUri) { 85 return request( 86 `${API_BASE}/replies?uri=${encodeURIComponent(annotationUri)}`, 87 ); 88} 89 90export async function updateAnnotation(uri, text, tags) { 91 return request(`${API_BASE}/annotations?uri=${encodeURIComponent(uri)}`, { 92 method: "PUT", 93 body: JSON.stringify({ text, tags }), 94 }); 95} 96 97export async function updateHighlight(uri, color, tags) { 98 return request(`${API_BASE}/highlights?uri=${encodeURIComponent(uri)}`, { 99 method: "PUT", 100 body: JSON.stringify({ color, tags }), 101 }); 102} 103 104export async function createBookmark(url, title, description) { 105 return request(`${API_BASE}/bookmarks`, { 106 method: "POST", 107 body: JSON.stringify({ url, title, description }), 108 }); 109} 110 111export async function updateBookmark(uri, title, description, tags) { 112 return request(`${API_BASE}/bookmarks?uri=${encodeURIComponent(uri)}`, { 113 method: "PUT", 114 body: JSON.stringify({ title, description, tags }), 115 }); 116} 117 118export async function getCollections(did) { 119 let url = `${API_BASE}/collections`; 120 if (did) url += `?author=${encodeURIComponent(did)}`; 121 return request(url); 122} 123 124export async function getCollectionsContaining(annotationUri) { 125 return request( 126 `${API_BASE}/collections/containing?uri=${encodeURIComponent(annotationUri)}`, 127 ); 128} 129 130export async function getEditHistory(uri) { 131 return request( 132 `${API_BASE}/annotations/history?uri=${encodeURIComponent(uri)}`, 133 ); 134} 135 136export async function getNotifications(limit = 50, offset = 0) { 137 return request(`${API_BASE}/notifications?limit=${limit}&offset=${offset}`); 138} 139 140export async function getUnreadNotificationCount() { 141 return request(`${API_BASE}/notifications/count`); 142} 143 144export async function markNotificationsRead() { 145 return request(`${API_BASE}/notifications/read`, { method: "POST" }); 146} 147 148export async function updateCollection(uri, name, description, icon) { 149 return request(`${API_BASE}/collections?uri=${encodeURIComponent(uri)}`, { 150 method: "PUT", 151 body: JSON.stringify({ name, description, icon }), 152 }); 153} 154 155export async function createCollection(name, description, icon) { 156 return request(`${API_BASE}/collections`, { 157 method: "POST", 158 body: JSON.stringify({ name, description, icon }), 159 }); 160} 161 162export async function deleteCollection(uri) { 163 return request(`${API_BASE}/collections?uri=${encodeURIComponent(uri)}`, { 164 method: "DELETE", 165 }); 166} 167 168export async function getCollectionItems(collectionUri) { 169 return request( 170 `${API_BASE}/collections/${encodeURIComponent(collectionUri)}/items`, 171 ); 172} 173 174export async function addItemToCollection( 175 collectionUri, 176 annotationUri, 177 position = 0, 178) { 179 return request( 180 `${API_BASE}/collections/${encodeURIComponent(collectionUri)}/items`, 181 { 182 method: "POST", 183 body: JSON.stringify({ annotationUri, position }), 184 }, 185 ); 186} 187 188export async function removeItemFromCollection(itemUri) { 189 return request( 190 `${API_BASE}/collections/items?uri=${encodeURIComponent(itemUri)}`, 191 { 192 method: "DELETE", 193 }, 194 ); 195} 196 197export async function getLikeCount(annotationUri) { 198 return request(`${API_BASE}/likes?uri=${encodeURIComponent(annotationUri)}`); 199} 200 201export async function deleteHighlight(rkey) { 202 return request(`${API_BASE}/highlights?rkey=${encodeURIComponent(rkey)}`, { 203 method: "DELETE", 204 }); 205} 206 207export async function deleteBookmark(rkey) { 208 return request(`${API_BASE}/bookmarks?rkey=${encodeURIComponent(rkey)}`, { 209 method: "DELETE", 210 }); 211} 212 213export async function createAnnotation({ url, text, quote, title, selector }) { 214 return request(`${API_BASE}/annotations`, { 215 method: "POST", 216 body: JSON.stringify({ url, text, quote, title, selector }), 217 }); 218} 219 220export async function deleteAnnotation(rkey, type = "annotation") { 221 return request( 222 `${API_BASE}/annotations?rkey=${encodeURIComponent(rkey)}&type=${encodeURIComponent(type)}`, 223 { 224 method: "DELETE", 225 }, 226 ); 227} 228 229export async function likeAnnotation(subjectUri, subjectCid) { 230 return request(`${API_BASE}/annotations/like`, { 231 method: "POST", 232 headers: { 233 "Content-Type": "application/json", 234 }, 235 body: JSON.stringify({ 236 subjectUri, 237 subjectCid, 238 }), 239 }); 240} 241 242export async function unlikeAnnotation(subjectUri) { 243 return request( 244 `${API_BASE}/annotations/like?uri=${encodeURIComponent(subjectUri)}`, 245 { 246 method: "DELETE", 247 }, 248 ); 249} 250 251export async function createReply({ 252 parentUri, 253 parentCid, 254 rootUri, 255 rootCid, 256 text, 257}) { 258 return request(`${API_BASE}/annotations/reply`, { 259 method: "POST", 260 body: JSON.stringify({ parentUri, parentCid, rootUri, rootCid, text }), 261 }); 262} 263 264export async function deleteReply(uri) { 265 return request( 266 `${API_BASE}/annotations/reply?uri=${encodeURIComponent(uri)}`, 267 { 268 method: "DELETE", 269 }, 270 ); 271} 272 273export async function getSession() { 274 return request(`${AUTH_BASE}/session`); 275} 276 277export async function logout() { 278 return request(`${AUTH_BASE}/logout`, { method: "POST" }); 279} 280 281export function normalizeAnnotation(item) { 282 if (!item) return {}; 283 284 if (item.type === "Annotation") { 285 return { 286 uri: item.id, 287 author: item.creator, 288 url: item.target?.source, 289 title: item.target?.title, 290 text: item.body?.value, 291 selector: item.target?.selector, 292 motivation: item.motivation, 293 tags: item.tags || [], 294 createdAt: item.created, 295 cid: item.cid || item.CID, 296 }; 297 } 298 299 if (item.type === "Bookmark") { 300 return { 301 uri: item.id, 302 author: item.creator, 303 url: item.source, 304 title: item.title, 305 description: item.description, 306 tags: item.tags || [], 307 createdAt: item.created, 308 cid: item.cid || item.CID, 309 }; 310 } 311 312 if (item.type === "Highlight") { 313 return { 314 uri: item.id, 315 author: item.creator, 316 url: item.target?.source, 317 title: item.target?.title, 318 selector: item.target?.selector, 319 color: item.color, 320 tags: item.tags || [], 321 createdAt: item.created, 322 cid: item.cid || item.CID, 323 }; 324 } 325 326 return { 327 uri: item.uri || item.id, 328 author: item.author || item.creator, 329 url: item.url || item.source || item.target?.source, 330 title: item.title || item.target?.title, 331 text: item.text || item.body?.value, 332 description: item.description, 333 selector: item.selector || item.target?.selector, 334 color: item.color, 335 tags: item.tags || [], 336 createdAt: item.createdAt || item.created, 337 cid: item.cid || item.CID, 338 }; 339} 340 341export function normalizeHighlight(highlight) { 342 return { 343 uri: highlight.id, 344 author: highlight.creator, 345 url: highlight.target?.source, 346 title: highlight.target?.title, 347 selector: highlight.target?.selector, 348 color: highlight.color, 349 tags: highlight.tags || [], 350 createdAt: highlight.created, 351 }; 352} 353 354export function normalizeBookmark(bookmark) { 355 return { 356 uri: bookmark.id, 357 author: bookmark.creator, 358 url: bookmark.source, 359 title: bookmark.title, 360 description: bookmark.description, 361 tags: bookmark.tags || [], 362 createdAt: bookmark.created, 363 }; 364} 365 366export async function searchActors(query) { 367 const res = await fetch( 368 `https://public.api.bsky.app/xrpc/app.bsky.actor.searchActorsTypeahead?q=${encodeURIComponent(query)}&limit=5`, 369 ); 370 if (!res.ok) throw new Error("Search failed"); 371 return res.json(); 372} 373 374export async function startLogin(handle, inviteCode) { 375 return request(`${AUTH_BASE}/start`, { 376 method: "POST", 377 body: JSON.stringify({ handle, invite_code: inviteCode }), 378 }); 379}