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 (
{local.selection}
local.onCancel()}>
{local.cancelLabel ?? i18n.t("ui.lineComment.cancel")}
{local.submitLabel ?? i18n.t("ui.lineComment.submit")}
)
}