diff --git a/src/components/repo.tsx b/src/components/repo.tsx index 6112371..585e14f 100644 --- a/src/components/repo.tsx +++ b/src/components/repo.tsx @@ -17,12 +17,14 @@ import { GitPullRequest, LoaderCircle, MessageSquarePlus, + Reply, + X, } from 'lucide-solid'; import { useAuth } from '../lib/auth'; import { resolveActor } from '../lib/api/identity'; import { marked } from 'marked'; -import { A, useNavigate } from '@solidjs/router'; -import { For, Show, createMemo, createSignal, onCleanup, onMount, type Component, type JSX } from 'solid-js'; +import { A, useNavigate, useLocation } from '@solidjs/router'; +import { For, Show, createMemo, createSignal, createEffect, onCleanup, onMount, type Component, type JSX } from 'solid-js'; import type { RepoContext, TreeEntry } from '../lib/api/repos'; import type { Did } from '@atcute/lexicons/syntax'; import { createQuery } from '@tanstack/solid-query'; @@ -30,9 +32,9 @@ import { getRepo, getRepoLanguages, getRepoForkCount, getRepoByDid } from '../li import { getRepoStarCount } from '../lib/api/stars'; import { getRepoIssueCount } from '../lib/api/issues'; import { getRepoPullCount } from '../lib/api/pulls'; -import { formatRelativeTime, languageColor, encodePath, formatLanguagePercent, blobHref, treeHref } from '../lib/repo-utils'; +import { formatRelativeTime, languageColor, encodePath, formatLanguagePercent, blobHref, treeHref, getErrorMessage } from '../lib/repo-utils'; import { getTangledAppviewService } from '../lib/settings'; -import { Avatar, SkeletonBlock, StateBadge, PlaceholderAvatar, buttonStyles, cardStyles } from './common'; +import { Avatar, SkeletonBlock, StateBadge, PlaceholderAvatar, buttonStyles, cardStyles, textareaStyles } from './common'; import { useRepoPreloader } from '../lib/preloading'; @@ -1625,6 +1627,7 @@ export { CodeView, DiffView, PullDiffView } from './code-view'; export interface GenericComment { uri: string; + cid?: string; rkey: string; author: { did: string; handle: string }; value: { @@ -1643,9 +1646,58 @@ export const CommentThreadsSection: Component<{ threads: GenericCommentThread[]; authorDid?: string; title?: string; + onReplySubmit?: (body: string, parentComment: GenericComment) => Promise; }> = (props) => { + const auth = useAuth(); + const location = useLocation(); + const [replyingTo, setReplyingTo] = createSignal(null); + const [replyBody, setReplyBody] = createSignal(''); + const [replyWorking, setReplyWorking] = createSignal(false); + const [replyError, setReplyError] = createSignal(null); + + createEffect(() => { + const hash = location.hash; + const cleanHash = hash.startsWith('#') ? hash.slice(1) : hash; + if (cleanHash && cleanHash.startsWith('comment-') && props.threads.length > 0) { + setTimeout(() => { + const element = document.getElementById(cleanHash); + if (element) { + // Remove highlight from any other elements first + document.querySelectorAll('.untangled-comment-highlight').forEach((el) => { + el.classList.remove('untangled-comment-highlight'); + }); + element.scrollIntoView({ behavior: 'smooth', block: 'center' }); + element.classList.add('untangled-comment-highlight'); + } + }, 150); + } + }); + + const viewerQuery = createQuery(() => ({ + queryKey: ['viewer', auth.currentDid()], + enabled: Boolean(auth.currentDid()), + queryFn: async () => resolveActor(auth.currentDid()!), + })); + + const handleReplySubmit = async (e: SubmitEvent, parent: GenericComment) => { + e.preventDefault(); + if (!props.onReplySubmit || !replyBody().trim() || replyWorking()) return; + + setReplyWorking(true); + setReplyError(null); + try { + await props.onReplySubmit(replyBody(), parent); + setReplyBody(''); + setReplyingTo(null); + } catch (err) { + setReplyError(getErrorMessage(err)); + } finally { + setReplyWorking(false); + } + }; + return ( -
+

{props.title} @@ -1654,34 +1706,98 @@ export const CommentThreadsSection: Component<{
{(thread) => ( -
+
0}> -
+
{(reply) => ( -
- -
+ )}
+ + + { + setReplyingTo(thread.item); + setReplyBody(''); + setReplyError(null); + }} + > + + {(did) => } + + Leave a reply... + + } + > +
void handleReplySubmit(event, thread.item)} + class="untangled-thread-reply-form" + > + + {(viewer) => ( +
+ + {viewer().handle || viewer().did} +
+ )} +
+