- New QuickEmojiPicker: shared SmilePlus + 8 quick emojis + full picker - New FileUploadButton: reusable Paperclip upload trigger - New CodeBlockView: React NodeView with language label + copy button - CodeBlockLowlight: syntax highlighting in editor (replaces plain codeBlock) - ReactionBar: brand-tinted pill styles, hideAddButton prop - Comment header: emoji picker + three-dot menu in top-right - Comment edit: inline editing with brand border, blur-to-save, Escape-to-cancel - RichTextEditor: add onBlur prop, markdown paste extension - Create issue: upload button in footer - Issue detail: upload button next to reaction bar - Comment/reply: use FileUploadButton, loading spinners, no optimistic updates Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
52 lines
1.6 KiB
TypeScript
52 lines
1.6 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { NodeViewWrapper, NodeViewContent } from "@tiptap/react";
|
|
import type { NodeViewProps } from "@tiptap/react";
|
|
import { Copy, Check } from "lucide-react";
|
|
|
|
function CodeBlockView({ node }: NodeViewProps) {
|
|
const [copied, setCopied] = useState(false);
|
|
const language = node.attrs.language || "";
|
|
|
|
const handleCopy = async () => {
|
|
const text = node.textContent;
|
|
if (!text) return;
|
|
await navigator.clipboard.writeText(text);
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 2000);
|
|
};
|
|
|
|
return (
|
|
<NodeViewWrapper className="code-block-wrapper group/code relative my-2">
|
|
<div
|
|
contentEditable={false}
|
|
className="code-block-header absolute top-0 right-0 z-10 flex items-center gap-1.5 px-2 py-1.5 opacity-0 transition-opacity group-hover/code:opacity-100"
|
|
>
|
|
{language && (
|
|
<span className="text-xs text-muted-foreground select-none">
|
|
{language}
|
|
</span>
|
|
)}
|
|
<button
|
|
type="button"
|
|
onClick={handleCopy}
|
|
className="flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
|
|
title="Copy code"
|
|
>
|
|
{copied ? (
|
|
<Check className="h-3.5 w-3.5" />
|
|
) : (
|
|
<Copy className="h-3.5 w-3.5" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
<pre spellCheck={false}>
|
|
{/* @ts-expect-error -- NodeViewContent supports as="code" at runtime */}
|
|
<NodeViewContent as="code" />
|
|
</pre>
|
|
</NodeViewWrapper>
|
|
);
|
|
}
|
|
|
|
export { CodeBlockView };
|