mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-06 16:59:01 +00:00
299 lines
8.9 KiB
TypeScript
299 lines
8.9 KiB
TypeScript
import { createEffect, createSignal, onMount, Show, splitProps, type JSX } from "solid-js"
|
|
import { Button } from "./button"
|
|
import { Icon } from "./icon"
|
|
import { installLineCommentStyles } from "./line-comment-styles"
|
|
import { useI18n } from "../context/i18n"
|
|
|
|
installLineCommentStyles()
|
|
|
|
export type LineCommentVariant = "default" | "editor" | "add"
|
|
|
|
function InlineGlyph(props: { icon: "comment" | "plus" }) {
|
|
return (
|
|
<svg data-slot="line-comment-icon" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
|
<Show
|
|
when={props.icon === "comment"}
|
|
fallback={
|
|
<path
|
|
d="M10 5.41699V10.0003M10 10.0003V14.5837M10 10.0003H5.4165M10 10.0003H14.5832"
|
|
stroke="currentColor"
|
|
stroke-linecap="square"
|
|
/>
|
|
}
|
|
>
|
|
<path d="M16.25 3.75H3.75V16.25L6.875 14.4643H16.25V3.75Z" stroke="currentColor" stroke-linecap="square" />
|
|
</Show>
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
export type LineCommentAnchorProps = {
|
|
id?: string
|
|
top?: number
|
|
inline?: boolean
|
|
hideButton?: boolean
|
|
open: boolean
|
|
variant?: LineCommentVariant
|
|
icon?: "comment" | "plus"
|
|
buttonLabel?: string
|
|
onClick?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>
|
|
onMouseEnter?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>
|
|
onPopoverFocusOut?: JSX.EventHandlerUnion<HTMLDivElement, FocusEvent>
|
|
class?: string
|
|
popoverClass?: string
|
|
children?: JSX.Element
|
|
}
|
|
|
|
export const LineCommentAnchor = (props: LineCommentAnchorProps) => {
|
|
const hidden = () => !props.inline && props.top === undefined
|
|
const variant = () => props.variant ?? "default"
|
|
const icon = () => props.icon ?? "comment"
|
|
const inlineBody = () => props.inline && props.hideButton
|
|
|
|
return (
|
|
<div
|
|
data-component="line-comment"
|
|
data-prevent-autofocus=""
|
|
data-variant={variant()}
|
|
data-comment-id={props.id}
|
|
data-open={props.open ? "" : undefined}
|
|
data-inline={props.inline ? "" : undefined}
|
|
classList={{
|
|
[props.class ?? ""]: !!props.class,
|
|
}}
|
|
style={
|
|
props.inline
|
|
? undefined
|
|
: {
|
|
top: `${props.top ?? 0}px`,
|
|
opacity: hidden() ? 0 : 1,
|
|
"pointer-events": hidden() ? "none" : "auto",
|
|
}
|
|
}
|
|
>
|
|
<Show
|
|
when={inlineBody()}
|
|
fallback={
|
|
<>
|
|
<button
|
|
type="button"
|
|
aria-label={props.buttonLabel}
|
|
data-slot="line-comment-button"
|
|
on:mousedown={(e) => e.stopPropagation()}
|
|
on:mouseup={(e) => e.stopPropagation()}
|
|
on:click={props.onClick as any}
|
|
on:mouseenter={props.onMouseEnter as any}
|
|
>
|
|
<Show
|
|
when={props.inline}
|
|
fallback={<Icon name={icon() === "plus" ? "plus-small" : "comment"} size="small" />}
|
|
>
|
|
<InlineGlyph icon={icon()} />
|
|
</Show>
|
|
</button>
|
|
<Show when={props.open}>
|
|
<div
|
|
data-slot="line-comment-popover"
|
|
classList={{
|
|
[props.popoverClass ?? ""]: !!props.popoverClass,
|
|
}}
|
|
on:mousedown={(e) => e.stopPropagation()}
|
|
on:focusout={props.onPopoverFocusOut as any}
|
|
>
|
|
{props.children}
|
|
</div>
|
|
</Show>
|
|
</>
|
|
}
|
|
>
|
|
<div
|
|
data-slot="line-comment-popover"
|
|
data-inline-body=""
|
|
classList={{
|
|
[props.popoverClass ?? ""]: !!props.popoverClass,
|
|
}}
|
|
on:mousedown={(e) => e.stopPropagation()}
|
|
on:click={props.onClick as any}
|
|
on:mouseenter={props.onMouseEnter as any}
|
|
on:focusout={props.onPopoverFocusOut as any}
|
|
>
|
|
{props.children}
|
|
</div>
|
|
</Show>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export type LineCommentProps = Omit<LineCommentAnchorProps, "children" | "variant"> & {
|
|
comment: JSX.Element
|
|
selection: JSX.Element
|
|
actions?: JSX.Element
|
|
}
|
|
|
|
export const LineComment = (props: LineCommentProps) => {
|
|
const i18n = useI18n()
|
|
const [split, rest] = splitProps(props, ["comment", "selection", "actions"])
|
|
|
|
return (
|
|
<LineCommentAnchor {...rest} variant="default" hideButton={props.inline}>
|
|
<div data-slot="line-comment-content">
|
|
<div data-slot="line-comment-head">
|
|
<div data-slot="line-comment-text">{split.comment}</div>
|
|
<Show when={split.actions}>
|
|
<div data-slot="line-comment-tools">{split.actions}</div>
|
|
</Show>
|
|
</div>
|
|
<div data-slot="line-comment-label">
|
|
{i18n.t("ui.lineComment.label.prefix")}
|
|
{split.selection}
|
|
{i18n.t("ui.lineComment.label.suffix")}
|
|
</div>
|
|
</div>
|
|
</LineCommentAnchor>
|
|
)
|
|
}
|
|
|
|
export type LineCommentAddProps = Omit<LineCommentAnchorProps, "children" | "variant" | "open" | "icon"> & {
|
|
label?: string
|
|
}
|
|
|
|
export const LineCommentAdd = (props: LineCommentAddProps) => {
|
|
const [split, rest] = splitProps(props, ["label"])
|
|
const i18n = useI18n()
|
|
|
|
return (
|
|
<LineCommentAnchor
|
|
{...rest}
|
|
open={false}
|
|
variant="add"
|
|
icon="plus"
|
|
buttonLabel={split.label ?? i18n.t("ui.lineComment.submit")}
|
|
/>
|
|
)
|
|
}
|
|
|
|
export type LineCommentEditorProps = Omit<LineCommentAnchorProps, "children" | "open" | "variant" | "onClick"> & {
|
|
value: string
|
|
selection: JSX.Element
|
|
onInput: (value: string) => void
|
|
onCancel: VoidFunction
|
|
onSubmit: (value: string) => void
|
|
placeholder?: string
|
|
rows?: number
|
|
autofocus?: boolean
|
|
cancelLabel?: string
|
|
submitLabel?: string
|
|
}
|
|
|
|
export const LineCommentEditor = (props: LineCommentEditorProps) => {
|
|
const i18n = useI18n()
|
|
const [split, rest] = splitProps(props, [
|
|
"value",
|
|
"selection",
|
|
"onInput",
|
|
"onCancel",
|
|
"onSubmit",
|
|
"placeholder",
|
|
"rows",
|
|
"autofocus",
|
|
"cancelLabel",
|
|
"submitLabel",
|
|
])
|
|
|
|
const refs = {
|
|
textarea: undefined as HTMLTextAreaElement | undefined,
|
|
}
|
|
const [text, setText] = createSignal(split.value)
|
|
|
|
const focus = () => refs.textarea?.focus()
|
|
|
|
createEffect(() => {
|
|
setText(split.value)
|
|
})
|
|
|
|
const submit = () => {
|
|
const value = text().trim()
|
|
if (!value) return
|
|
split.onSubmit(value)
|
|
}
|
|
|
|
onMount(() => {
|
|
if (split.autofocus === false) return
|
|
requestAnimationFrame(focus)
|
|
})
|
|
|
|
return (
|
|
<LineCommentAnchor {...rest} open={true} variant="editor" hideButton={props.inline} onClick={() => focus()}>
|
|
<div data-slot="line-comment-editor">
|
|
<textarea
|
|
ref={(el) => {
|
|
refs.textarea = el
|
|
}}
|
|
data-slot="line-comment-textarea"
|
|
rows={split.rows ?? 3}
|
|
placeholder={split.placeholder ?? i18n.t("ui.lineComment.placeholder")}
|
|
value={text()}
|
|
on:input={(e) => {
|
|
const value = (e.currentTarget as HTMLTextAreaElement).value
|
|
setText(value)
|
|
split.onInput(value)
|
|
}}
|
|
on:keydown={(e) => {
|
|
const event = e as KeyboardEvent
|
|
if (event.isComposing || event.keyCode === 229) return
|
|
event.stopPropagation()
|
|
if (e.key === "Escape") {
|
|
event.preventDefault()
|
|
e.currentTarget.blur()
|
|
split.onCancel()
|
|
return
|
|
}
|
|
if (e.key !== "Enter") return
|
|
if (e.shiftKey) return
|
|
event.preventDefault()
|
|
submit()
|
|
}}
|
|
/>
|
|
<div data-slot="line-comment-actions">
|
|
<div data-slot="line-comment-editor-label">
|
|
{i18n.t("ui.lineComment.editorLabel.prefix")}
|
|
{split.selection}
|
|
{i18n.t("ui.lineComment.editorLabel.suffix")}
|
|
</div>
|
|
<Show
|
|
when={!props.inline}
|
|
fallback={
|
|
<>
|
|
<button
|
|
type="button"
|
|
data-slot="line-comment-action"
|
|
data-variant="ghost"
|
|
on:click={split.onCancel as any}
|
|
>
|
|
{split.cancelLabel ?? i18n.t("ui.common.cancel")}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
data-slot="line-comment-action"
|
|
data-variant="primary"
|
|
disabled={text().trim().length === 0}
|
|
on:click={submit as any}
|
|
>
|
|
{split.submitLabel ?? i18n.t("ui.lineComment.submit")}
|
|
</button>
|
|
</>
|
|
}
|
|
>
|
|
<Button size="small" variant="ghost" onClick={split.onCancel}>
|
|
{split.cancelLabel ?? i18n.t("ui.common.cancel")}
|
|
</Button>
|
|
<Button size="small" variant="primary" disabled={text().trim().length === 0} onClick={submit}>
|
|
{split.submitLabel ?? i18n.t("ui.lineComment.submit")}
|
|
</Button>
|
|
</Show>
|
|
</div>
|
|
</div>
|
|
</LineCommentAnchor>
|
|
)
|
|
}
|