import React, { useState, useEffect } from "react"; import { createAnnotation, createHighlight, sessionAtom, getUserTags, getTrendingTags, } from "../../api/client"; import type { Selector, ContentLabelValue } from "../../types"; import { X, ShieldAlert } from "lucide-react"; import TagInput from "../ui/TagInput"; const SELF_LABEL_OPTIONS: { value: ContentLabelValue; label: string }[] = [ { value: "sexual", label: "Sexual" }, { value: "nudity", label: "Nudity" }, { value: "violence", label: "Violence" }, { value: "gore", label: "Gore" }, { value: "spam", label: "Spam" }, { value: "misleading", label: "Misleading" }, ]; interface ComposerProps { url: string; selector?: Selector | null; onSuccess?: () => void; onCancel?: () => void; } export default function Composer({ url, selector: initialSelector, onSuccess, onCancel, }: ComposerProps) { const [text, setText] = useState(""); const [quoteText, setQuoteText] = useState(""); const [tags, setTags] = useState([]); const [tagSuggestions, setTagSuggestions] = useState([]); const [selector, setSelector] = useState(initialSelector); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [showQuoteInput, setShowQuoteInput] = useState(false); const [selfLabels, setSelfLabels] = useState([]); const [showLabelPicker, setShowLabelPicker] = useState(false); useEffect(() => { const session = sessionAtom.get(); if (session?.did) { Promise.all([ getUserTags(session.did).catch(() => [] as string[]), getTrendingTags(50) .then((tags) => tags.map((t) => t.tag)) .catch(() => [] as string[]), ]).then(([userTags, trendingTags]) => { const seen = new Set(userTags); const merged = [...userTags]; for (const t of trendingTags) { if (!seen.has(t)) { merged.push(t); seen.add(t); } } setTagSuggestions(merged); }); } }, []); const highlightedText = selector?.type === "TextQuoteSelector" ? selector.exact : null; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!text.trim() && !highlightedText && !quoteText.trim()) return; try { setLoading(true); setError(null); let finalSelector = selector; if (!finalSelector && quoteText.trim()) { finalSelector = { type: "TextQuoteSelector", exact: quoteText.trim(), }; } const tagList = tags.filter(Boolean); if (!text.trim()) { if (!finalSelector) throw new Error("No text selected"); await createHighlight({ url, selector: finalSelector as { exact: string; prefix?: string; suffix?: string; }, color: "yellow", tags: tagList, labels: selfLabels.length > 0 ? selfLabels : undefined, }); } else { await createAnnotation({ url, text: text.trim(), selector: finalSelector || undefined, tags: tagList, labels: selfLabels.length > 0 ? selfLabels : undefined, }); } setText(""); setQuoteText(""); setTags([]); setSelector(null); if (onSuccess) onSuccess(); } catch (err) { setError( (err instanceof Error ? err.message : "Unknown error") || "Failed to post", ); } finally { setLoading(false); } }; const handleRemoveSelector = () => { setSelector(null); setQuoteText(""); setShowQuoteInput(false); }; return (

New Annotation

{url && (
{url}
)}
{highlightedText && (
"{highlightedText}"
)} {!highlightedText && ( <> {!showQuoteInput ? ( ) : (