From d6f96bb9ade928fe8f2ef5e7544e25a90e97ceed Mon Sep 17 00:00:00 2001 From: scanash00 Date: Wed, 4 Feb 2026 22:26:50 -0900 Subject: [PATCH] delete old frontend files --- web/eslint.config.js | 40 - web/index.html | 24 - web/src/App.jsx | 81 -- web/src/api/client.js | 503 ----------- web/src/components/AddToCollectionModal.jsx | 262 ------ web/src/components/AnnotationCard.jsx | 674 -------------- web/src/components/AnnotationSkeleton.jsx | 26 - web/src/components/AppLayout.jsx | 26 - web/src/components/BookmarkCard.jsx | 190 ---- web/src/components/CollectionIcon.jsx | 117 --- web/src/components/CollectionItemCard.jsx | 75 -- web/src/components/CollectionModal.jsx | 396 --------- web/src/components/CollectionRow.jsx | 42 - web/src/components/Composer.jsx | 184 ---- web/src/components/EditProfileModal.jsx | 258 ------ web/src/components/IOSInstallBanner.jsx | 52 -- web/src/components/Icons.jsx | 505 ----------- web/src/components/LeftSidebar.jsx | 189 ---- web/src/components/MobileNav.jsx | 248 ------ web/src/components/ReplyList.jsx | 337 ------- web/src/components/RichText.jsx | 60 -- web/src/components/RightSidebar.jsx | 155 ---- web/src/components/ScrollToTop.jsx | 12 - web/src/components/ShareMenu.jsx | 401 --------- web/src/components/SignUpModal.jsx | 267 ------ web/src/components/TopNav.jsx | 425 --------- web/src/components/UserMeta.jsx | 63 -- web/src/context/AuthContext.jsx | 76 -- web/src/context/ThemeContext.jsx | 88 -- web/src/css/annotations.css | 560 ------------ web/src/css/base.css | 224 ----- web/src/css/buttons.css | 143 --- web/src/css/cards.css | 278 ------ web/src/css/collections.css | 340 ------- web/src/css/feed.css | 445 ---------- web/src/css/landing.css | 925 -------------------- web/src/css/layout.css | 737 ---------------- web/src/css/login.css | 380 -------- web/src/css/modals.css | 630 ------------- web/src/css/notifications.css | 67 -- web/src/css/profile.css | 311 ------- web/src/css/sidebar.css | 499 ----------- web/src/css/skeleton.css | 104 --- web/src/css/utilities.css | 747 ---------------- web/src/index.css | 15 - web/src/main.jsx | 13 - web/src/pages/AnnotationDetail.jsx | 254 ------ web/src/pages/Bookmarks.jsx | 333 ------- web/src/pages/CollectionDetail.jsx | 314 ------- web/src/pages/Collections.jsx | 134 --- web/src/pages/Feed.jsx | 429 --------- web/src/pages/Highlights.jsx | 133 --- web/src/pages/Landing.jsx | 790 ----------------- web/src/pages/Login.jsx | 282 ------ web/src/pages/New.jsx | 99 --- web/src/pages/Notifications.jsx | 233 ----- web/src/pages/Privacy.jsx | 142 --- web/src/pages/Profile.jsx | 410 --------- web/src/pages/Settings.jsx | 344 -------- web/src/pages/Terms.jsx | 81 -- web/src/pages/Url.jsx | 390 --------- web/src/pages/UserUrl.jsx | 239 ----- web/src/utils/formatting.js | 23 - web/vite.config.js | 23 - 64 files changed, 16847 deletions(-) delete mode 100644 web/eslint.config.js delete mode 100644 web/index.html delete mode 100644 web/src/App.jsx delete mode 100644 web/src/api/client.js delete mode 100644 web/src/components/AddToCollectionModal.jsx delete mode 100644 web/src/components/AnnotationCard.jsx delete mode 100644 web/src/components/AnnotationSkeleton.jsx delete mode 100644 web/src/components/AppLayout.jsx delete mode 100644 web/src/components/BookmarkCard.jsx delete mode 100644 web/src/components/CollectionIcon.jsx delete mode 100644 web/src/components/CollectionItemCard.jsx delete mode 100644 web/src/components/CollectionModal.jsx delete mode 100644 web/src/components/CollectionRow.jsx delete mode 100644 web/src/components/Composer.jsx delete mode 100644 web/src/components/EditProfileModal.jsx delete mode 100644 web/src/components/IOSInstallBanner.jsx delete mode 100644 web/src/components/Icons.jsx delete mode 100644 web/src/components/LeftSidebar.jsx delete mode 100644 web/src/components/MobileNav.jsx delete mode 100644 web/src/components/ReplyList.jsx delete mode 100644 web/src/components/RichText.jsx delete mode 100644 web/src/components/RightSidebar.jsx delete mode 100644 web/src/components/ScrollToTop.jsx delete mode 100644 web/src/components/ShareMenu.jsx delete mode 100644 web/src/components/SignUpModal.jsx delete mode 100644 web/src/components/TopNav.jsx delete mode 100644 web/src/components/UserMeta.jsx delete mode 100644 web/src/context/AuthContext.jsx delete mode 100644 web/src/context/ThemeContext.jsx delete mode 100644 web/src/css/annotations.css delete mode 100644 web/src/css/base.css delete mode 100644 web/src/css/buttons.css delete mode 100644 web/src/css/cards.css delete mode 100644 web/src/css/collections.css delete mode 100644 web/src/css/feed.css delete mode 100644 web/src/css/landing.css delete mode 100644 web/src/css/layout.css delete mode 100644 web/src/css/login.css delete mode 100644 web/src/css/modals.css delete mode 100644 web/src/css/notifications.css delete mode 100644 web/src/css/profile.css delete mode 100644 web/src/css/sidebar.css delete mode 100644 web/src/css/skeleton.css delete mode 100644 web/src/css/utilities.css delete mode 100644 web/src/index.css delete mode 100644 web/src/main.jsx delete mode 100644 web/src/pages/AnnotationDetail.jsx delete mode 100644 web/src/pages/Bookmarks.jsx delete mode 100644 web/src/pages/CollectionDetail.jsx delete mode 100644 web/src/pages/Collections.jsx delete mode 100644 web/src/pages/Feed.jsx delete mode 100644 web/src/pages/Highlights.jsx delete mode 100644 web/src/pages/Landing.jsx delete mode 100644 web/src/pages/Login.jsx delete mode 100644 web/src/pages/New.jsx delete mode 100644 web/src/pages/Notifications.jsx delete mode 100644 web/src/pages/Privacy.jsx delete mode 100644 web/src/pages/Profile.jsx delete mode 100644 web/src/pages/Settings.jsx delete mode 100644 web/src/pages/Terms.jsx delete mode 100644 web/src/pages/Url.jsx delete mode 100644 web/src/pages/UserUrl.jsx delete mode 100644 web/src/utils/formatting.js delete mode 100644 web/vite.config.js diff --git a/web/eslint.config.js b/web/eslint.config.js deleted file mode 100644 index 38602d2..0000000 --- a/web/eslint.config.js +++ /dev/null @@ -1,40 +0,0 @@ -import js from "@eslint/js"; -import globals from "globals"; -import react from "eslint-plugin-react"; -import reactHooks from "eslint-plugin-react-hooks"; -import reactRefresh from "eslint-plugin-react-refresh"; - -export default [ - { ignores: ["dist"] }, - { - files: ["**/*.{js,jsx}"], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, - parserOptions: { - ecmaVersion: "latest", - ecmaFeatures: { jsx: true }, - sourceType: "module", - }, - }, - settings: { react: { version: "18.3" } }, - plugins: { - react, - "react-hooks": reactHooks, - "react-refresh": reactRefresh, - }, - rules: { - ...js.configs.recommended.rules, - ...react.configs.recommended.rules, - ...react.configs["jsx-runtime"].rules, - ...reactHooks.configs.recommended.rules, - "react/jsx-no-target-blank": "off", - "react-refresh/only-export-components": [ - "warn", - { allowConstantExport: true }, - ], - "no-unused-vars": ["warn", { argsIgnorePattern: "^_" }], - "react/prop-types": "off", - }, - }, -]; diff --git a/web/index.html b/web/index.html deleted file mode 100644 index 5c26768..0000000 --- a/web/index.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - Margin - Write in the margins of the web - - - - - - -
- - - diff --git a/web/src/App.jsx b/web/src/App.jsx deleted file mode 100644 index 3139892..0000000 --- a/web/src/App.jsx +++ /dev/null @@ -1,81 +0,0 @@ -import { Routes, Route } from "react-router-dom"; -import { useEffect } from "react"; -import { AuthProvider, useAuth } from "./context/AuthContext"; -import AppLayout from "./components/AppLayout"; -import Feed from "./pages/Feed"; -import Url from "./pages/Url"; -import UserUrl from "./pages/UserUrl"; -import Profile from "./pages/Profile"; -import Login from "./pages/Login"; -import New from "./pages/New"; -import Bookmarks from "./pages/Bookmarks"; -import Highlights from "./pages/Highlights"; -import Notifications from "./pages/Notifications"; -import AnnotationDetail from "./pages/AnnotationDetail"; -import Collections from "./pages/Collections"; -import CollectionDetail from "./pages/CollectionDetail"; -import Privacy from "./pages/Privacy"; -import Terms from "./pages/Terms"; -import Settings from "./pages/Settings"; -import Landing from "./pages/Landing"; -import ScrollToTop from "./components/ScrollToTop"; -import { ThemeProvider } from "./context/ThemeContext"; - -function AppContent() { - const { user } = useAuth(); - - useEffect(() => { - if (user) { - fetch("/api/sync", { method: "POST" }).catch(console.error); - } - }, [user]); - - return ( - - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - } - /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - ); -} - -export default function App() { - return ( - - - - } /> - } /> - - - - ); -} diff --git a/web/src/api/client.js b/web/src/api/client.js deleted file mode 100644 index fe0a7ef..0000000 --- a/web/src/api/client.js +++ /dev/null @@ -1,503 +0,0 @@ -const API_BASE = "/api"; -const AUTH_BASE = "/auth"; - -async function request(endpoint, options = {}) { - const response = await fetch(endpoint, { - credentials: "include", - headers: { - "Content-Type": "application/json", - ...options.headers, - }, - ...options, - }); - - if (!response.ok) { - const error = await response.text(); - throw new Error(error || `HTTP ${response.status}`); - } - - return response.json(); -} - -export async function getURLMetadata(url) { - return request(`${API_BASE}/url-metadata?url=${encodeURIComponent(url)}`); -} - -export async function getAnnotationFeed( - limit = 50, - offset = 0, - tag = "", - creator = "", - feedType = "", - motivation = "", -) { - let url = `${API_BASE}/annotations/feed?limit=${limit}&offset=${offset}`; - if (tag) url += `&tag=${encodeURIComponent(tag)}`; - if (creator) url += `&creator=${encodeURIComponent(creator)}`; - if (feedType) url += `&type=${encodeURIComponent(feedType)}`; - if (motivation) url += `&motivation=${encodeURIComponent(motivation)}`; - return request(url); -} - -export async function getAnnotations({ - source, - motivation, - limit = 50, - offset = 0, -} = {}) { - let url = `${API_BASE}/annotations?limit=${limit}&offset=${offset}`; - if (source) url += `&source=${encodeURIComponent(source)}`; - if (motivation) url += `&motivation=${motivation}`; - return request(url); -} - -export async function getByTarget(source, limit = 50, offset = 0) { - return request( - `${API_BASE}/targets?source=${encodeURIComponent(source)}&limit=${limit}&offset=${offset}`, - ); -} - -export async function getAnnotation(uri) { - return request(`${API_BASE}/annotation?uri=${encodeURIComponent(uri)}`); -} - -export async function getProfile(did) { - return request(`${API_BASE}/profile/${encodeURIComponent(did)}`); -} - -export async function getUserAnnotations(did, limit = 50, offset = 0) { - return request( - `${API_BASE}/users/${encodeURIComponent(did)}/annotations?limit=${limit}&offset=${offset}`, - ); -} - -export async function getUserHighlights(did, limit = 50, offset = 0) { - return request( - `${API_BASE}/users/${encodeURIComponent(did)}/highlights?limit=${limit}&offset=${offset}`, - ); -} - -export async function getUserBookmarks(did, limit = 50, offset = 0) { - return request( - `${API_BASE}/users/${encodeURIComponent(did)}/bookmarks?limit=${limit}&offset=${offset}`, - ); -} - -export async function getUserTargetItems(did, url, limit = 50, offset = 0) { - return request( - `${API_BASE}/users/${encodeURIComponent(did)}/targets?source=${encodeURIComponent(url)}&limit=${limit}&offset=${offset}`, - ); -} - -export async function getHighlights(creatorDid, limit = 50, offset = 0) { - return request( - `${API_BASE}/highlights?creator=${encodeURIComponent(creatorDid)}&limit=${limit}&offset=${offset}`, - ); -} - -export async function getBookmarks(creatorDid, limit = 50, offset = 0) { - return request( - `${API_BASE}/bookmarks?creator=${encodeURIComponent(creatorDid)}&limit=${limit}&offset=${offset}`, - ); -} - -export async function getReplies(annotationUri) { - return request( - `${API_BASE}/replies?uri=${encodeURIComponent(annotationUri)}`, - ); -} - -export async function updateAnnotation(uri, text, tags) { - return request(`${API_BASE}/annotations?uri=${encodeURIComponent(uri)}`, { - method: "PUT", - body: JSON.stringify({ text, tags }), - }); -} - -export async function updateHighlight(uri, color, tags) { - return request(`${API_BASE}/highlights?uri=${encodeURIComponent(uri)}`, { - method: "PUT", - body: JSON.stringify({ color, tags }), - }); -} - -export async function createBookmark(url, title, description) { - return request(`${API_BASE}/bookmarks`, { - method: "POST", - body: JSON.stringify({ url, title, description }), - }); -} - -export async function updateBookmark(uri, title, description, tags) { - return request(`${API_BASE}/bookmarks?uri=${encodeURIComponent(uri)}`, { - method: "PUT", - body: JSON.stringify({ title, description, tags }), - }); -} - -export async function getCollections(did) { - let url = `${API_BASE}/collections`; - if (did) url += `?author=${encodeURIComponent(did)}`; - return request(url); -} - -export async function getCollection(uri) { - return request(`${API_BASE}/collection?uri=${encodeURIComponent(uri)}`); -} - -export async function getCollectionsContaining(annotationUri) { - return request( - `${API_BASE}/collections/containing?uri=${encodeURIComponent(annotationUri)}`, - ); -} - -export async function getEditHistory(uri) { - return request( - `${API_BASE}/annotations/history?uri=${encodeURIComponent(uri)}`, - ); -} - -export async function getNotifications(limit = 50, offset = 0) { - return request(`${API_BASE}/notifications?limit=${limit}&offset=${offset}`); -} - -export async function getUnreadNotificationCount() { - return request(`${API_BASE}/notifications/count`); -} - -export async function markNotificationsRead() { - return request(`${API_BASE}/notifications/read`, { method: "POST" }); -} - -export async function updateCollection(uri, name, description, icon) { - return request(`${API_BASE}/collections?uri=${encodeURIComponent(uri)}`, { - method: "PUT", - body: JSON.stringify({ name, description, icon }), - }); -} - -export async function updateProfile({ - displayName, - avatar, - bio, - website, - links, -}) { - return request(`${API_BASE}/profile`, { - method: "PUT", - body: JSON.stringify({ displayName, avatar, bio, website, links }), - }); -} - -export async function uploadAvatar(file) { - const formData = new FormData(); - formData.append("avatar", file); - - const response = await fetch(`${API_BASE}/profile/avatar`, { - method: "POST", - credentials: "include", - body: formData, - }); - - if (!response.ok) { - const error = await response.text(); - throw new Error(error || `HTTP ${response.status}`); - } - - return response.json(); -} - -export async function createCollection(name, description, icon) { - return request(`${API_BASE}/collections`, { - method: "POST", - body: JSON.stringify({ name, description, icon }), - }); -} - -export async function deleteCollection(uri) { - return request(`${API_BASE}/collections?uri=${encodeURIComponent(uri)}`, { - method: "DELETE", - }); -} - -export async function getCollectionItems(collectionUri) { - return request( - `${API_BASE}/collections/${encodeURIComponent(collectionUri)}/items`, - ); -} - -export async function addItemToCollection( - collectionUri, - annotationUri, - position = 0, -) { - return request( - `${API_BASE}/collections/${encodeURIComponent(collectionUri)}/items`, - { - method: "POST", - body: JSON.stringify({ annotationUri, position }), - }, - ); -} - -export async function removeItemFromCollection(itemUri) { - return request( - `${API_BASE}/collections/items?uri=${encodeURIComponent(itemUri)}`, - { - method: "DELETE", - }, - ); -} - -export async function getLikeCount(annotationUri) { - return request(`${API_BASE}/likes?uri=${encodeURIComponent(annotationUri)}`); -} - -export async function deleteHighlight(rkey) { - return request(`${API_BASE}/highlights?rkey=${encodeURIComponent(rkey)}`, { - method: "DELETE", - }); -} - -export async function deleteBookmark(rkey) { - return request(`${API_BASE}/bookmarks?rkey=${encodeURIComponent(rkey)}`, { - method: "DELETE", - }); -} - -export async function createHighlight({ url, title, selector, color, tags }) { - return request(`${API_BASE}/highlights`, { - method: "POST", - body: JSON.stringify({ url, title, selector, color, tags }), - }); -} - -export async function createAnnotation({ - url, - text, - quote, - title, - selector, - tags, -}) { - return request(`${API_BASE}/annotations`, { - method: "POST", - body: JSON.stringify({ url, text, quote, title, selector, tags }), - }); -} - -export async function deleteAnnotation(rkey, type = "annotation") { - return request( - `${API_BASE}/annotations?rkey=${encodeURIComponent(rkey)}&type=${encodeURIComponent(type)}`, - { - method: "DELETE", - }, - ); -} - -export async function likeAnnotation(subjectUri, subjectCid) { - return request(`${API_BASE}/annotations/like`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - subjectUri, - subjectCid, - }), - }); -} - -export async function unlikeAnnotation(subjectUri) { - return request( - `${API_BASE}/annotations/like?uri=${encodeURIComponent(subjectUri)}`, - { - method: "DELETE", - }, - ); -} - -export async function createReply({ - parentUri, - parentCid, - rootUri, - rootCid, - text, -}) { - return request(`${API_BASE}/annotations/reply`, { - method: "POST", - body: JSON.stringify({ parentUri, parentCid, rootUri, rootCid, text }), - }); -} - -export async function deleteReply(uri) { - return request( - `${API_BASE}/annotations/reply?uri=${encodeURIComponent(uri)}`, - { - method: "DELETE", - }, - ); -} - -export async function getSession() { - return request(`${AUTH_BASE}/session`); -} - -export async function logout() { - return request(`${AUTH_BASE}/logout`, { method: "POST" }); -} - -export function normalizeAnnotation(item) { - if (!item) return {}; - - if (item.type === "Annotation") { - return { - type: item.type, - uri: item.uri || item.id, - author: item.author || item.creator, - url: item.url || item.target?.source, - title: item.title || item.target?.title, - text: item.text || item.body?.value, - selector: item.selector || item.target?.selector, - motivation: item.motivation, - tags: item.tags || [], - createdAt: item.createdAt || item.created, - cid: item.cid || item.CID, - likeCount: item.likeCount || 0, - replyCount: item.replyCount || 0, - viewerHasLiked: item.viewerHasLiked || false, - }; - } - - if (item.type === "Bookmark") { - return { - type: item.type, - uri: item.uri || item.id, - author: item.author || item.creator, - url: item.url || item.source, - title: item.title, - description: item.description, - tags: item.tags || [], - createdAt: item.createdAt || item.created, - cid: item.cid || item.CID, - likeCount: item.likeCount || 0, - replyCount: item.replyCount || 0, - viewerHasLiked: item.viewerHasLiked || false, - }; - } - - if (item.type === "Highlight") { - return { - type: item.type, - uri: item.uri || item.id, - author: item.author || item.creator, - url: item.url || item.target?.source, - title: item.title || item.target?.title, - selector: item.selector || item.target?.selector, - color: item.color, - tags: item.tags || [], - createdAt: item.createdAt || item.created, - cid: item.cid || item.CID, - likeCount: item.likeCount || 0, - replyCount: item.replyCount || 0, - viewerHasLiked: item.viewerHasLiked || false, - }; - } - - return { - uri: item.uri || item.id, - author: item.author || item.creator, - url: item.url || item.source || item.target?.source, - title: item.title || item.target?.title, - text: item.text || item.body?.value, - description: item.description, - selector: item.selector || item.target?.selector, - color: item.color, - tags: item.tags || [], - createdAt: item.createdAt || item.created, - cid: item.cid || item.CID, - likeCount: item.likeCount || 0, - replyCount: item.replyCount || 0, - viewerHasLiked: item.viewerHasLiked || false, - }; -} - -export function normalizeHighlight(highlight) { - return { - uri: highlight.uri || highlight.id, - author: highlight.author || highlight.creator, - url: highlight.url || highlight.target?.source, - title: highlight.title || highlight.target?.title, - selector: highlight.selector || highlight.target?.selector, - color: highlight.color, - tags: highlight.tags || [], - createdAt: highlight.createdAt || highlight.created, - likeCount: highlight.likeCount || 0, - replyCount: highlight.replyCount || 0, - viewerHasLiked: highlight.viewerHasLiked || false, - }; -} - -export function normalizeBookmark(bookmark) { - return { - uri: bookmark.uri || bookmark.id, - author: bookmark.author || bookmark.creator, - url: bookmark.url || bookmark.source, - title: bookmark.title, - description: bookmark.description, - tags: bookmark.tags || [], - createdAt: bookmark.createdAt || bookmark.created, - likeCount: bookmark.likeCount || 0, - replyCount: bookmark.replyCount || 0, - viewerHasLiked: bookmark.viewerHasLiked || false, - }; -} - -export async function searchActors(query) { - const res = await fetch( - `https://public.api.bsky.app/xrpc/app.bsky.actor.searchActorsTypeahead?q=${encodeURIComponent(query)}&limit=5`, - ); - if (!res.ok) throw new Error("Search failed"); - return res.json(); -} - -export async function resolveHandle(handle) { - const res = await fetch( - `https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`, - ); - if (!res.ok) throw new Error("Failed to resolve handle"); - const data = await res.json(); - return data.did; -} - -export async function startLogin(handle) { - return request(`${AUTH_BASE}/start`, { - method: "POST", - body: JSON.stringify({ handle }), - }); -} - -export async function startSignup(pdsUrl) { - return request(`${AUTH_BASE}/signup`, { - method: "POST", - body: JSON.stringify({ pds_url: pdsUrl }), - }); -} -export async function getTrendingTags(limit = 10) { - return request(`${API_BASE}/tags/trending?limit=${limit}`); -} - -export async function getAPIKeys() { - return request(`${API_BASE}/keys`); -} - -export async function createAPIKey(name) { - return request(`${API_BASE}/keys`, { - method: "POST", - body: JSON.stringify({ name }), - }); -} - -export async function deleteAPIKey(id) { - return request(`${API_BASE}/keys/${id}`, { method: "DELETE" }); -} diff --git a/web/src/components/AddToCollectionModal.jsx b/web/src/components/AddToCollectionModal.jsx deleted file mode 100644 index 3e0ed83..0000000 --- a/web/src/components/AddToCollectionModal.jsx +++ /dev/null @@ -1,262 +0,0 @@ -import { useState, useEffect, useCallback } from "react"; -import { X, Plus, Check, Folder } from "lucide-react"; -import { - getCollections, - addItemToCollection, - getCollectionsContaining, -} from "../api/client"; -import { useAuth } from "../context/AuthContext"; -import CollectionModal from "./CollectionModal"; - -export default function AddToCollectionModal({ - isOpen, - onClose, - annotationUri, -}) { - const { user } = useAuth(); - const [collections, setCollections] = useState([]); - const [loading, setLoading] = useState(true); - const [addingTo, setAddingTo] = useState(null); - const [addedTo, setAddedTo] = useState(new Set()); - const [createModalOpen, setCreateModalOpen] = useState(false); - const [error, setError] = useState(null); - - const loadCollections = useCallback(async () => { - try { - setLoading(true); - const [data, existingURIs] = await Promise.all([ - getCollections(user?.did), - annotationUri ? getCollectionsContaining(annotationUri) : [], - ]); - - const items = Array.isArray(data) ? data : data.items || []; - setCollections(items); - setAddedTo(new Set(existingURIs || [])); - } catch (err) { - console.error(err); - setError("Failed to load collections"); - } finally { - setLoading(false); - } - }, [user?.did, annotationUri]); - - useEffect(() => { - if (isOpen && user) { - if (!annotationUri) { - setLoading(false); - return; - } - loadCollections(); - setError(null); - } - }, [isOpen, user, annotationUri, loadCollections]); - - const handleAdd = async (collectionUri) => { - if (addedTo.has(collectionUri)) return; - - try { - setAddingTo(collectionUri); - await addItemToCollection(collectionUri, annotationUri); - setAddedTo((prev) => new Set([...prev, collectionUri])); - } catch (err) { - console.error(err); - alert("Failed to add to collection"); - } finally { - setAddingTo(null); - } - }; - - if (!isOpen) return null; - - return ( - <> -
-
e.stopPropagation()} - > -
-

- - Add to Collection -

- -
- -
- {loading ? ( -
-
- - Loading collections... - -
- ) : error ? ( -
-

- {error} -

- -
- ) : collections.length === 0 ? ( -
-
- -
-

- No collections found -

-

- Create a collection to start organizing your items. -

-
- ) : ( -
- {collections.map((col) => { - const isAdded = addedTo.has(col.uri); - const isAdding = addingTo === col.uri; - - return ( - - ); - })} -
- )} -
- -
- - -
-
-
- - setCreateModalOpen(false)} - onSuccess={() => { - loadCollections(); - }} - /> - - ); -} diff --git a/web/src/components/AnnotationCard.jsx b/web/src/components/AnnotationCard.jsx deleted file mode 100644 index c325d60..0000000 --- a/web/src/components/AnnotationCard.jsx +++ /dev/null @@ -1,674 +0,0 @@ -import { useState, useEffect } from "react"; -import { useAuth } from "../context/AuthContext"; -import ReplyList from "./ReplyList"; -import { Link } from "react-router-dom"; -import RichText from "./RichText"; -import { - normalizeAnnotation, - normalizeHighlight, - likeAnnotation, - unlikeAnnotation, - getReplies, - createReply, - deleteReply, - updateAnnotation, - updateHighlight, - getEditHistory, - deleteAnnotation, -} from "../api/client"; -import { - MessageSquare, - Heart, - Trash2, - Folder, - Edit2, - Save, - X, - Clock, -} from "lucide-react"; -import { HighlightIcon, TrashIcon } from "./Icons"; -import ShareMenu from "./ShareMenu"; -import UserMeta from "./UserMeta"; - -function buildTextFragmentUrl(baseUrl, selector) { - if (!selector || selector.type !== "TextQuoteSelector" || !selector.exact) { - return baseUrl; - } - let fragment = ":~:text="; - if (selector.prefix) { - fragment += encodeURIComponent(selector.prefix) + "-,"; - } - fragment += encodeURIComponent(selector.exact); - if (selector.suffix) { - fragment += ",-" + encodeURIComponent(selector.suffix); - } - return baseUrl + "#" + fragment; -} - -const truncateUrl = (url, maxLength = 50) => { - if (!url) return ""; - try { - const parsed = new URL(url); - const fullPath = parsed.host + parsed.pathname; - if (fullPath.length > maxLength) - return fullPath.substring(0, maxLength) + "..."; - return fullPath; - } catch { - return url.length > maxLength ? url.substring(0, maxLength) + "..." : url; - } -}; - -function SembleBadge() { - return ( -
- via Semble - Semble -
- ); -} - -export default function AnnotationCard({ - annotation, - onDelete, - onAddToCollection, -}) { - const { user, login } = useAuth(); - const data = normalizeAnnotation(annotation); - - const [likeCount, setLikeCount] = useState(data.likeCount || 0); - const [isLiked, setIsLiked] = useState(data.viewerHasLiked || false); - const [deleting, setDeleting] = useState(false); - const [isEditing, setIsEditing] = useState(false); - const [editText, setEditText] = useState(data.text || ""); - const [editTags, setEditTags] = useState(data.tags?.join(", ") || ""); - const [saving, setSaving] = useState(false); - const [showHistory, setShowHistory] = useState(false); - const [editHistory, setEditHistory] = useState([]); - const [loadingHistory, setLoadingHistory] = useState(false); - const [replies, setReplies] = useState([]); - const [replyCount, setReplyCount] = useState(data.replyCount || 0); - const [showReplies, setShowReplies] = useState(false); - const [replyingTo, setReplyingTo] = useState(null); - const [replyText, setReplyText] = useState(""); - const [posting, setPosting] = useState(false); - const [hasEditHistory, setHasEditHistory] = useState(false); - - const isOwner = user?.did && data.author?.did === user.did; - const isSemble = data.uri?.includes("network.cosmik"); - const highlightedText = - data.selector?.type === "TextQuoteSelector" ? data.selector.exact : null; - const fragmentUrl = buildTextFragmentUrl(data.url, data.selector); - - useEffect(() => { - if (data.uri && !data.color && !data.description) { - getEditHistory(data.uri) - .then((history) => { - if (history?.length > 0) setHasEditHistory(true); - }) - .catch(() => {}); - } - }, [data.uri, data.color, data.description]); - - const fetchHistory = async () => { - if (showHistory) { - setShowHistory(false); - return; - } - try { - setLoadingHistory(true); - setShowHistory(true); - const history = await getEditHistory(data.uri); - setEditHistory(history); - } catch (err) { - console.error("Failed to fetch history:", err); - } finally { - setLoadingHistory(false); - } - }; - - const handlePostReply = async (parentReply) => { - if (!replyText.trim()) return; - try { - setPosting(true); - const parentUri = parentReply - ? parentReply.id || parentReply.uri - : data.uri; - const parentCid = parentReply - ? parentReply.cid - : annotation.cid || data.cid; - - await createReply({ - parentUri, - parentCid: parentCid || "", - rootUri: data.uri, - rootCid: annotation.cid || data.cid || "", - text: replyText, - }); - - setReplyText(""); - setReplyingTo(null); - - const res = await getReplies(data.uri); - if (res.items) { - setReplies(res.items); - setReplyCount(res.items.length); - } - } catch (err) { - alert("Failed to post reply: " + err.message); - } finally { - setPosting(false); - } - }; - - const handleSaveEdit = async () => { - try { - setSaving(true); - const tagList = editTags - .split(",") - .map((t) => t.trim()) - .filter(Boolean); - await updateAnnotation(data.uri, editText, tagList); - setIsEditing(false); - if (annotation.body) annotation.body.value = editText; - else if (annotation.text) annotation.text = editText; - if (annotation.tags) annotation.tags = tagList; - data.tags = tagList; - } catch (err) { - alert("Failed to update: " + err.message); - } finally { - setSaving(false); - } - }; - - const handleLike = async () => { - if (!user) { - login(); - return; - } - try { - if (isLiked) { - setIsLiked(false); - setLikeCount((prev) => Math.max(0, prev - 1)); - await unlikeAnnotation(data.uri); - } else { - setIsLiked(true); - setLikeCount((prev) => prev + 1); - const cid = annotation.cid || data.cid || ""; - if (data.uri && cid) await likeAnnotation(data.uri, cid); - } - } catch { - setIsLiked(!isLiked); - setLikeCount((prev) => (isLiked ? prev + 1 : prev - 1)); - } - }; - - const handleDelete = async () => { - if (!confirm("Delete this annotation? This cannot be undone.")) return; - try { - setDeleting(true); - const parts = data.uri.split("/"); - const rkey = parts[parts.length - 1]; - await deleteAnnotation(rkey); - if (onDelete) onDelete(data.uri); - else window.location.reload(); - } catch (err) { - alert("Failed to delete: " + err.message); - } finally { - setDeleting(false); - } - }; - - const loadReplies = async () => { - if (!showReplies && replies.length === 0) { - try { - const res = await getReplies(data.uri); - if (res.items) setReplies(res.items); - } catch (err) { - console.error("Failed to load replies:", err); - } - } - setShowReplies(!showReplies); - }; - - const handleCollect = () => { - if (!user) { - login(); - return; - } - if (onAddToCollection) onAddToCollection(); - }; - - return ( -
-
-
- -
-
- {isSemble && } - {hasEditHistory && !data.color && !data.description && ( - - )} - {isOwner && !isSemble && ( - <> - {!data.color && !data.description && ( - - )} - - - )} -
-
- - {showHistory && ( -
-
-

Edit History

- -
- {loadingHistory ? ( -
Loading history...
- ) : editHistory.length === 0 ? ( -
No edit history found.
- ) : ( -
    - {editHistory.map((edit) => ( -
  • -
    - {new Date(edit.editedAt).toLocaleString()} -
    -
    {edit.previousContent}
    -
  • - ))} -
- )} -
- )} - -
- - {truncateUrl(data.url)} - {data.title && ( - · {data.title} - )} - - - {highlightedText && ( - - “{highlightedText}” - - )} - - {isEditing ? ( -
-