// --------------------------------------------------------------------------- // HfUser NodeView // // WYSIWYG rendition of the Hugging Face user card, mirroring the DOM // emitted by `app/src/components/HfUser.astro` in the upstream // research-article-template so what you see in the editor matches // what the publisher writes. // // Why a dedicated view (vs. the generic AtomicView): // The generic placeholder shows a dashed card with form fields, // which made authors guess what the final block looks like. The // upstream component is small and visual (avatar + name + handle), // so it's worth reproducing inline and revealing the form only // when the node is selected (or when the username is still empty, // right after insertion). // // Anchors: // We use instead of inside the editor so accidental // clicks don't navigate away from the document. The publisher // transformer (publisher/transformers/hf-user.ts) re-renders the // same DOM with real tags for the static HTML output. // --------------------------------------------------------------------------- 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 (
{label} 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", }} />
); } 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], ); // Show the inline editor on selection or while the card is empty // (right after insertion, prompting the author to fill at least // the username). const showEditor = selected || !username; return (
{avatarSrc ? ( {`${displayName} ) : ( )} {displayName} @{username || "username"}
{showEditor && (
)}
); } HfUserNodeView.displayName = "HfUserView";