Replace three divergent data paths (Marked HTML loading, regex post-processing saving, separate paste parsing) with one symmetric path through @tiptap/markdown. Key changes: - Create features/editor/ module with ContentEditor (unified edit+readonly) and TitleEditor, replacing components/common/ editor files - Load content via contentType: 'markdown' instead of markdownToHtml() hack - Save content via editor.getMarkdown() directly, no post-processing - Merge RichTextEditor + ReadonlyEditor into single ContentEditor with editable prop - Extract extensions into separate modules (mention, file-upload, markdown-paste, submit-shortcut, code-block-view) - Extract shared preprocessMentionShortcodes to components/markdown/mentions.ts - Add copyMarkdown utility for clipboard operations - Upgrade all @tiptap packages from 3.20.5 to 3.22.1 (lexer isolation fix, HTML entity roundtrip fix, table alignment support) - Delete markdownToHtml.ts, readonly-editor.tsx, and 10 old component files 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 };
|