| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { useCallback } from "react"; |
| import { NodeViewWrapper } from "@tiptap/react"; |
| import type { NodeViewProps } from "@tiptap/react"; |
|
|
| function FieldRow({ |
| label, |
| value, |
| placeholder, |
| onChange, |
| }: { |
| label: string; |
| value: string; |
| placeholder?: string; |
| onChange: (val: string) => void; |
| }) { |
| return ( |
| <div style={{ display: "flex", alignItems: "center", gap: 8 }}> |
| <span |
| style={{ |
| fontSize: 12, |
| color: "var(--muted-color)", |
| minWidth: 90, |
| }} |
| > |
| {label} |
| </span> |
| <input |
| type="text" |
| value={value} |
| placeholder={placeholder ?? label} |
| onChange={(e) => onChange(e.target.value)} |
| style={{ |
| flex: 1, |
| minWidth: 0, |
| padding: "4px 8px", |
| background: "var(--page-bg)", |
| border: "1px solid var(--border-color)", |
| borderRadius: 4, |
| color: "var(--text-color)", |
| fontSize: 13, |
| outline: "none", |
| }} |
| /> |
| </div> |
| ); |
| } |
|
|
| export function HfUserNodeView({ |
| node, |
| updateAttributes, |
| selected, |
| }: NodeViewProps) { |
| const username = String(node.attrs.username || "").trim(); |
| const name = String(node.attrs.name || "").trim(); |
| const url = String(node.attrs.url || "").trim(); |
|
|
| const displayName = name || username || "username"; |
| const avatarSrc = username |
| ? `https://huggingface.co/api/users/${encodeURIComponent(username)}/avatar` |
| : ""; |
| const urlPlaceholder = username |
| ? `https://huggingface.co/${username}` |
| : "https://huggingface.co/…"; |
|
|
| const handleAttr = useCallback( |
| (key: string) => (value: string) => updateAttributes({ [key]: value }), |
| [updateAttributes], |
| ); |
|
|
| |
| |
| |
| const showEditor = selected || !username; |
|
|
| return ( |
| <NodeViewWrapper data-component="hfUser"> |
| <div contentEditable={false} style={{ margin: "0.75em 0" }}> |
| <div |
| className="hf-user" |
| style={ |
| selected |
| ? { |
| outline: "2px solid var(--primary-color)", |
| outlineOffset: 2, |
| borderRadius: 12, |
| } |
| : undefined |
| } |
| > |
| <div className="hf-user__left"> |
| {avatarSrc ? ( |
| <img |
| className="hf-user__avatar" |
| src={avatarSrc} |
| alt={`${displayName} avatar`} |
| width={44} |
| height={44} |
| loading="lazy" |
| decoding="async" |
| referrerPolicy="no-referrer" |
| /> |
| ) : ( |
| <div |
| className="hf-user__avatar" |
| aria-hidden="true" |
| style={{ |
| display: "flex", |
| alignItems: "center", |
| justifyContent: "center", |
| background: "var(--surface-bg)", |
| color: "var(--muted-color)", |
| fontSize: 18, |
| fontWeight: 700, |
| }} |
| > |
| ? |
| </div> |
| )} |
| <span className="hf-user__text"> |
| <span className="hf-user__name">{displayName}</span> |
| <span className="hf-user__row"> |
| <span className="hf-user__username"> |
| @{username || "username"} |
| </span> |
| </span> |
| </span> |
| </div> |
| </div> |
| |
| {showEditor && ( |
| <div |
| style={{ |
| display: "flex", |
| flexDirection: "column", |
| gap: 6, |
| marginTop: 8, |
| padding: "10px 12px", |
| border: "1px dashed var(--border-color)", |
| borderRadius: 8, |
| background: "var(--surface-bg)", |
| maxWidth: 360, |
| }} |
| > |
| <FieldRow |
| label="Username" |
| value={username} |
| placeholder="username" |
| onChange={handleAttr("username")} |
| /> |
| <FieldRow |
| label="Display name" |
| value={name} |
| placeholder="Full Name" |
| onChange={handleAttr("name")} |
| /> |
| <FieldRow |
| label="URL" |
| value={url} |
| placeholder={urlPlaceholder} |
| onChange={handleAttr("url")} |
| /> |
| </div> |
| )} |
| </div> |
| </NodeViewWrapper> |
| ); |
| } |
|
|
| HfUserNodeView.displayName = "HfUserView"; |
|
|