import { For, Show, createSignal, onMount, splitProps, type ComponentProps, type JSX } from "solid-js" import { FileIcon } from "../../components/file-icon" import { useI18n } from "../../context/i18n" import { useFilteredList } from "../../hooks" import { ButtonV2 } from "./button-v2" import "./line-comment-v2.css" /** Horizontal “more” glyph for the display-card overflow control (Figma outline-dots). */ export function LineCommentV2OverflowIcon(props: ComponentProps<"svg">) { return ( ) } export interface LineCommentV2Props extends ComponentProps<"div"> { /** Main comment body (text or rich content). */ comment: JSX.Element /** Line / selection context (e.g. “Comment on line 40”). */ selection: JSX.Element /** Typically an overflow menu trigger; use `LineCommentV2OverflowIcon` inside `line-comment-v2-overflow`. */ actions?: JSX.Element } export function LineCommentV2(props: LineCommentV2Props) { const [local, rest] = splitProps(props, ["comment", "selection", "actions", "class", "classList"]) return (
{local.comment}
{local.selection}
{(actions) =>
{actions()}
}
) } export type LineCommentEditorV2Mention = { items: (query: string) => string[] | Promise } export interface LineCommentEditorV2Props extends Omit, "children" | "onInput" | "onSubmit"> { /** Visible field label above the textarea (default: “Comment”). */ heading?: JSX.Element | string value: string onInput: (value: string) => void onCancel: () => void onSubmit: (value: string) => void selection: JSX.Element placeholder?: string rows?: number cancelLabel?: string submitLabel?: string autofocus?: boolean mention?: LineCommentEditorV2Mention } function pathFilename(path: string) { const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")) return index === -1 ? path : path.slice(index + 1) } function pathDirectory(path: string) { const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")) return index === -1 ? "" : path.slice(0, index + 1) } export function LineCommentEditorV2(props: LineCommentEditorV2Props) { const i18n = useI18n() let textareaRef: HTMLTextAreaElement | undefined const [mentionOpen, setMentionOpen] = createSignal(false) const [local, rest] = splitProps(props, [ "heading", "value", "onInput", "onCancel", "onSubmit", "selection", "placeholder", "rows", "cancelLabel", "submitLabel", "autofocus", "mention", "class", "classList", ]) const heading = () => local.heading ?? i18n.t("ui.lineComment.submit") const canSubmit = () => local.value.trim().length > 0 const closeMention = () => { setMentionOpen(false) mention.clear() } const currentMention = () => { const textarea = textareaRef if (!textarea) return if (!local.mention) return if (textarea.selectionStart !== textarea.selectionEnd) return const end = textarea.selectionStart const match = textarea.value.slice(0, end).match(/@(\S*)$/) if (!match) return return { query: match[1] ?? "", start: end - match[0].length, end, } } function selectMention(item: { path: string } | undefined) { if (!item) return const textarea = textareaRef const query = currentMention() if (!textarea || !query) return const value = `${textarea.value.slice(0, query.start)}@${item.path} ${textarea.value.slice(query.end)}` const cursor = query.start + item.path.length + 2 local.onInput(value) closeMention() requestAnimationFrame(() => { textarea.focus() textarea.setSelectionRange(cursor, cursor) }) } const mention = useFilteredList<{ path: string }>({ items: async (query) => { if (!local.mention) return [] if (!query.trim()) return [] const paths = await local.mention.items(query) return paths.map((path) => ({ path })) }, key: (item) => item.path, filterKeys: ["path"], skipFilter: () => true, onSelect: selectMention, }) const syncMention = () => { const item = currentMention() if (!item) { closeMention() return } setMentionOpen(true) mention.onInput(item.query) } const selectActiveMention = () => { const items = mention.flat() if (items.length === 0) return const active = mention.active() selectMention(items.find((item) => item.path === active) ?? items[0]) } const submit = () => { const v = local.value.trim() if (!v) return local.onSubmit(v) } onMount(() => { if (local.autofocus === false) return requestAnimationFrame(() => textareaRef?.focus()) }) return (
{heading()}