{"release_id":"01a1c502-4ad1-0000-ec62-adc54d3c69aa","chunk_id":"01a1c502-4c48-7080-81aa-82d8cef88ca0","file":"SIg_qZ03.mjs","mappings":"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","names":["mentionQueryAtCaret","text","el","caret","match","spliceMention","nickname","upto","after","newUpto","candidatesPromise","loadMentionCandidates","r","j","replyTo","postUrl","$.derived","$$props","signInTarget","body","$.state","submitting","error","inputEl","pickerOpen","viewerNickname","page","viewerAvatarPath","viewerUserId","autosize","$.get","mentionOpen","mentionQuery","mentionCandidates","$.proxy","mentionLoadKicked","refreshMentionState","q","$.set","c","pickMention","tick","onkeydown","submit","insertEmoji","emoji","start","end","optimisticComment","fields","post","payload","optimistic","res","comment","t","goto","trimmed","id","sendGif","gif","canSubmit","div","$.first_child","fragment","div_1","root_1","$0","$1","ReplyQuote","node_1","$$render","consequent","form","$.sibling","node","node_2","$.child","div_2","root_2","getAvatarUrl","DuelAvatar","node_3","textarea","$.remove_textarea_child","$$value","node_4","button","root_3","$.delegated","e","$.append","$$anchor","button_1","$.reset","node_5","p","root_4","consequent_3","EmojiGifPicker","node_6","MentionAutocomplete","node_7","$.set_attribute","$.set_class","$.event"],"sources":["../../../../../../src/lib/mention-typeahead.ts","../../../../../../src/lib/components/post/CommentInput.svelte"],"sourcesContent":["/** Shared @-mention typeahead plumbing for feed surfaces (post composer,\n *  comment input, tier list caption). The popover UI itself is the chat's\n *  MentionAutocomplete component; these helpers cover the parent's side:\n *  caret-anchored query detection + splice, and a cached candidates fetch.\n *\n *  Candidates = the viewer's FOLLOWERS — the only people a feed mention will\n *  actually notify (see post-mentions.ts), so the suggestions are honest.\n *  Chat keeps its own local candidate lists (DM partner / room members). */\n\nexport type MentionCandidate = {\n\tuserId: string;\n\tnickname: string | null;\n\tavatarPath: string | null;\n};\n\ntype CaretEl = HTMLTextAreaElement | HTMLInputElement | null;\n\n/** The in-progress `@query` at the caret, or null when no mention is being\n *  typed. Trigger: `@` at start-of-text or after whitespace (same rule as the\n *  chat composers). */\nexport function mentionQueryAtCaret(text: string, el: CaretEl): string | null {\n\tif (!el) return null;\n\tconst caret = el.selectionStart ?? text.length;\n\tconst upto = text.slice(0, caret);\n\tconst match = upto.match(/(?:^|\\s)@([a-zA-Z0-9_-]*)$/);\n\treturn match ? match[1] : null;\n}\n\n/** Replace the in-progress `@frag` before the caret with `@nickname `.\n *  Returns the new text + where the caret should land. */\nexport function spliceMention(\n\ttext: string,\n\tel: CaretEl,\n\tnickname: string\n): { text: string; caret: number } {\n\tconst caret = el?.selectionStart ?? text.length;\n\tconst upto = text.slice(0, caret);\n\tconst after = text.slice(caret);\n\tconst newUpto = upto.replace(/@([a-zA-Z0-9_-]*)$/, `@${nickname} `);\n\treturn { text: newUpto + after, caret: newUpto.length };\n}\n\n// Fetched once per page lifetime, shared across every composer on the page\n// (feed caption + N comment inputs). Reset on failure so a flaky request\n// retries next time instead of pinning an empty list.\nlet candidatesPromise: Promise<MentionCandidate[]> | null = null;\n\nexport function loadMentionCandidates(): Promise<MentionCandidate[]> {\n\tif (!candidatesPromise) {\n\t\tcandidatesPromise = fetch('/api/mention-candidates')\n\t\t\t.then((r) => (r.ok ? r.json() : { candidates: [] }))\n\t\t\t.then((j: { candidates?: MentionCandidate[] }) => j.candidates ?? [])\n\t\t\t.catch(() => {\n\t\t\t\tcandidatesPromise = null;\n\t\t\t\treturn [];\n\t\t\t});\n\t}\n\treturn candidatesPromise;\n}\n","<script lang=\"ts\">\n\timport { t } from '$lib/i18n';\n\timport { tick } from 'svelte';\n\timport { goto } from '$app/navigation';\n\timport { page } from '$app/state';\n\timport DuelAvatar from '$lib/components/duel/DuelAvatar.svelte';\n\timport EmojiGifPicker from '$lib/components/chat/EmojiGifPicker.svelte';\n\timport MentionAutocomplete from '$lib/components/chat/MentionAutocomplete.svelte';\n\timport ReplyQuote from '$lib/components/chat/ReplyQuote.svelte';\n\timport { getAvatarUrl } from '$lib/avatar';\n\timport {\n\t\tmentionQueryAtCaret,\n\t\tspliceMention,\n\t\tloadMentionCandidates,\n\t\ttype MentionCandidate\n\t} from '$lib/mention-typeahead';\n\timport type { GifSearchResult } from '../../../routes/api/gif/search/+server';\n\n\timport type { CommentView } from '$lib/types/post';\n\n\tinterface Props {\n\t\tpostId: string;\n\t\tviewerLoggedIn: boolean;\n\t\t/** Fired after a comment posts successfully. Receives the optimistic\n\t\t *  comment object so the parent can append it to its list immediately. */\n\t\tonposted?: (comment: CommentView) => void | Promise<void>;\n\t\t/** Comments 2.0: the comment being replied to (chat-style quote). Set by\n\t\t *  the parent when the viewer taps \"reply\"; cleared via onclearreply. */\n\t\treplyTo?: CommentView | null;\n\t\tonclearreply?: () => void;\n\t\t/** Override the POST target — the animation clip page reuses this\n\t\t *  composer against /api/animation/[id]/comments (same payload shape).\n\t\t *  Defaults to the post comment endpoint. */\n\t\tendpoint?: string;\n\t\t/** Sign-in redirect target when a logged-out viewer tries to comment. */\n\t\tsignInNext?: string;\n\t}\n\n\tlet {\n\t\tpostId,\n\t\tviewerLoggedIn,\n\t\tonposted,\n\t\treplyTo = null,\n\t\tonclearreply,\n\t\tendpoint,\n\t\tsignInNext\n\t}: Props = $props();\n\tconst postUrl = $derived(endpoint ?? `/api/post/${postId}/comment`);\n\tconst signInTarget = $derived(signInNext ?? `/post/${postId}`);\n\n\tlet body = $state('');\n\tlet submitting = $state(false);\n\tlet error = $state('');\n\tlet inputEl: HTMLTextAreaElement | null = $state(null);\n\tlet pickerOpen = $state(false);\n\n\tconst viewerNickname = $derived((page.data.viewerNickname as string | null) ?? null);\n\tconst viewerAvatarPath = $derived((page.data.viewerAvatarPath as string | null) ?? null);\n\tconst viewerUserId = $derived((page.data.user as { id: string } | null)?.id ?? null);\n\n\tfunction autosize() {\n\t\tif (!inputEl) return;\n\t\tinputEl.style.height = 'auto';\n\t\tinputEl.style.height = Math.min(inputEl.scrollHeight, 96) + 'px';\n\t}\n\n\t// @-mention typeahead (Telegram-style). Candidates load lazily on the\n\t// first `@` and are cached module-wide for every input on the page.\n\tlet mentionOpen = $state(false);\n\tlet mentionQuery = $state('');\n\tlet mentionCandidates = $state<MentionCandidate[]>([]);\n\tlet mentionLoadKicked = false;\n\n\tfunction refreshMentionState() {\n\t\tconst q = mentionQueryAtCaret(body, inputEl);\n\t\tif (q === null) {\n\t\t\tmentionOpen = false;\n\t\t\treturn;\n\t\t}\n\t\tmentionQuery = q;\n\t\tmentionOpen = true;\n\t\tif (!mentionLoadKicked) {\n\t\t\tmentionLoadKicked = true;\n\t\t\tloadMentionCandidates().then((c) => (mentionCandidates = c));\n\t\t}\n\t}\n\n\tfunction pickMention(nickname: string) {\n\t\tconst r = spliceMention(body, inputEl, nickname);\n\t\tbody = r.text;\n\t\tmentionOpen = false;\n\t\tautosize();\n\t\ttick().then(() => {\n\t\t\tinputEl?.focus();\n\t\t\tinputEl?.setSelectionRange(r.caret, r.caret);\n\t\t});\n\t}\n\n\tfunction onkeydown(e: KeyboardEvent) {\n\t\t// Enter submits, Shift+Enter inserts a newline. While the mention\n\t\t// popover is open, Enter belongs to it (its window-level handler picks\n\t\t// the highlighted row) — just block the submit/newline here.\n\t\tif (e.key === 'Enter' && !e.shiftKey) {\n\t\t\te.preventDefault();\n\t\t\tif (mentionOpen) return;\n\t\t\tsubmit();\n\t\t}\n\t}\n\n\tfunction insertEmoji(emoji: string) {\n\t\tif (!inputEl) {\n\t\t\tbody += emoji;\n\t\t\treturn;\n\t\t}\n\t\tconst start = inputEl.selectionStart ?? body.length;\n\t\tconst end = inputEl.selectionEnd ?? body.length;\n\t\tbody = body.slice(0, start) + emoji + body.slice(end);\n\t\tautosize();\n\t\t// Restore the caret just after the inserted emoji on the next tick.\n\t\tqueueMicrotask(() => {\n\t\t\tinputEl?.focus();\n\t\t\tinputEl?.setSelectionRange(start + emoji.length, start + emoji.length);\n\t\t});\n\t}\n\n\tfunction optimisticComment(fields: Partial<CommentView> & { id: string; body: string }): CommentView {\n\t\treturn {\n\t\t\tkind: 'text',\n\t\t\tmediaUrl: null,\n\t\t\tmediaMime: null,\n\t\t\treplyTo: replyTo\n\t\t\t\t? {\n\t\t\t\t\t\tid: replyTo.id,\n\t\t\t\t\t\tnickname: replyTo.author?.nickname ?? null,\n\t\t\t\t\t\tbody: replyTo.body,\n\t\t\t\t\t\tkind: replyTo.kind,\n\t\t\t\t\t\tdeleted: !!replyTo.deletedAt\n\t\t\t\t\t}\n\t\t\t\t: null,\n\t\t\treactions: [],\n\t\t\tcreatedAt: new Date().toISOString(),\n\t\t\tdeletedAt: null,\n\t\t\tauthor: viewerUserId\n\t\t\t\t? { id: viewerUserId, nickname: viewerNickname, avatarPath: viewerAvatarPath }\n\t\t\t\t: null,\n\t\t\t...fields\n\t\t};\n\t}\n\n\tasync function post(payload: Record<string, unknown>, optimistic: (id: string) => CommentView) {\n\t\tsubmitting = true;\n\t\terror = '';\n\t\ttry {\n\t\t\tconst res = await fetch(postUrl, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'content-type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({ ...payload, replyToCommentId: replyTo?.id })\n\t\t\t});\n\t\t\tif (res.ok) {\n\t\t\t\tconst j = await res.json().catch(() => ({}));\n\t\t\t\tconst comment = optimistic(j.id ?? `temp-${Date.now()}`);\n\t\t\t\tonclearreply?.();\n\t\t\t\tawait onposted?.(comment);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tconst j = await res.json().catch(() => ({}));\n\t\t\terror = j.error ?? t('failed to post');\n\t\t\treturn false;\n\t\t} catch {\n\t\t\terror = t('network error');\n\t\t\treturn false;\n\t\t} finally {\n\t\t\tsubmitting = false;\n\t\t}\n\t}\n\n\tasync function submit() {\n\t\tif (!viewerLoggedIn) {\n\t\t\tgoto(`/auth/sign-in?next=${signInTarget}`);\n\t\t\treturn;\n\t\t}\n\t\tconst trimmed = body.trim();\n\t\tif (!trimmed || submitting) return;\n\t\tconst ok = await post({ body: trimmed }, (id) => optimisticComment({ id, body: trimmed }));\n\t\tif (ok) {\n\t\t\tbody = '';\n\t\t\tautosize();\n\t\t}\n\t}\n\n\tasync function sendGif(gif: GifSearchResult) {\n\t\tpickerOpen = false;\n\t\tif (!viewerLoggedIn) {\n\t\t\tgoto(`/auth/sign-in?next=${signInTarget}`);\n\t\t\treturn;\n\t\t}\n\t\tif (submitting) return;\n\t\tawait post({ gif: { url: gif.url, mime: gif.mime, title: gif.title } }, (id) =>\n\t\t\toptimisticComment({\n\t\t\t\tid,\n\t\t\t\tbody: gif.title || 'gif',\n\t\t\t\tkind: 'gif',\n\t\t\t\tmediaUrl: gif.url,\n\t\t\t\tmediaMime: gif.mime\n\t\t\t})\n\t\t);\n\t}\n\n\tconst canSubmit = $derived(viewerLoggedIn && body.trim().length > 0 && !submitting);\n</script>\n\n<!-- data-comment-input lets /post/[id] find the input on mount and\n\t scroll it into view so a viewer landing from the feed sees \"add a\n\t comment…\" immediately, without having to scroll past the post body\n\t + existing comments first (RAT-294 follow-up). -->\n<div class=\"border-t border-border\" data-comment-input>\n\t{#if replyTo}\n\t\t<div class=\"px-4 pt-2\">\n\t\t\t<ReplyQuote\n\t\t\t\tvariant=\"composer\"\n\t\t\t\tsenderLabel={replyTo.author?.nickname ? `@${replyTo.author.nickname}` : t('a comment')}\n\t\t\t\tkind={replyTo.kind}\n\t\t\t\tbody={replyTo.body}\n\t\t\t\tmediaMime={replyTo.mediaMime}\n\t\t\t\tdeleted={!!replyTo.deletedAt}\n\t\t\t\tonClear={onclearreply}\n\t\t\t/>\n\t\t</div>\n\t{/if}\n\t<form\n\t\tonsubmit={(e) => {\n\t\t\te.preventDefault();\n\t\t\tsubmit();\n\t\t}}\n\t\tclass=\"flex items-center gap-2.5 px-4 py-2.5\"\n\t>\n\t\t{#if viewerLoggedIn}\n\t\t\t<div class=\"shrink-0\">\n\t\t\t\t<DuelAvatar\n\t\t\t\t\tnickname={viewerNickname}\n\t\t\t\t\tavatarUrl={getAvatarUrl(viewerAvatarPath)}\n\t\t\t\t\tsize={28}\n\t\t\t\t\thref=\"\"\n\t\t\t\t\tlink={false}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t{/if}\n\t\t<textarea\n\t\t\tbind:this={inputEl}\n\t\t\tbind:value={body}\n\t\t\toninput={() => {\n\t\t\t\tautosize();\n\t\t\t\trefreshMentionState();\n\t\t\t}}\n\t\t\tonkeyup={refreshMentionState}\n\t\t\t{onkeydown}\n\t\t\tplaceholder={viewerLoggedIn ? t('add a comment…') : t('sign in to comment')}\n\t\t\tdisabled={submitting || !viewerLoggedIn}\n\t\t\trows=\"1\"\n\t\t\tmaxlength=\"1000\"\n\t\t\tonclick={() => {\n\t\t\t\tif (!viewerLoggedIn) goto(`/auth/sign-in?next=/post/${postId}`);\n\t\t\t\trefreshMentionState();\n\t\t\t}}\n\t\t\tclass=\"block min-w-0 flex-1 resize-none border-0 bg-transparent p-0 text-[14px] leading-snug text-text shadow-none ring-0 outline-none placeholder:text-muted/50 focus:border-0 focus:ring-0 focus:outline-none disabled:opacity-60\"\n\t\t></textarea>\n\t\t{#if viewerLoggedIn}\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\taria-label={t('emoji and gif picker')}\n\t\t\t\tonclick={(e) => {\n\t\t\t\t\t// stopPropagation: without it the click bubbles into the freshly\n\t\t\t\t\t// mounted picker scrim in the same tick and insta-closes it.\n\t\t\t\t\te.stopPropagation();\n\t\t\t\t\tpickerOpen = !pickerOpen;\n\t\t\t\t}}\n\t\t\t\tclass=\"shrink-0 text-muted/60 transition-colors hover:text-text\"\n\t\t\t>\n\t\t\t\t<svg class=\"h-5 w-5\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\">\n\t\t\t\t\t<circle cx=\"12\" cy=\"12\" r=\"9\" />\n\t\t\t\t\t<circle cx=\"9\" cy=\"10\" r=\"0.5\" fill=\"currentColor\" />\n\t\t\t\t\t<circle cx=\"15\" cy=\"10\" r=\"0.5\" fill=\"currentColor\" />\n\t\t\t\t\t<path d=\"M8.5 14.5a4.5 4.5 0 0 0 7 0\" />\n\t\t\t\t</svg>\n\t\t\t</button>\n\t\t{/if}\n\t\t<button\n\t\t\ttype=\"submit\"\n\t\t\tdisabled={!canSubmit}\n\t\t\tclass=\"shrink-0 text-[13px] font-semibold transition-colors disabled:cursor-not-allowed {canSubmit ? 'text-accent hover:text-accent/80' : 'text-muted/40'}\"\n\t\t>\n\t\t\t{submitting ? '…' : t('post')}\n\t\t</button>\n\t</form>\n\t{#if error}\n\t\t<p class=\"px-4 pb-2 text-xs text-red-400\">{error}</p>\n\t{/if}\n</div>\n\n<EmojiGifPicker\n\topen={pickerOpen}\n\tonPickEmoji={insertEmoji}\n\tonPickGif={sendGif}\n\tonClose={() => (pickerOpen = false)}\n/>\n\n<MentionAutocomplete\n\topen={mentionOpen}\n\tquery={mentionQuery}\n\tcandidates={mentionCandidates}\n\tanchor={inputEl}\n\tonPick={pickMention}\n\tonClose={() => (mentionOpen = false)}\n/>\n"],"version":3}