mirror of https://github.com/usememos/memos
				
				
				
			feat(web): enhance code blocks with copy button and fix link navigation
Add custom code block renderer with language display and copy functionality. Links now open in new windows, and clicking image links no longer triggers both link navigation and image preview. - Add CodeBlock component with copy-to-clipboard button and language label - Configure all markdown links to open in new windows with target="_blank" - Fix image link behavior to prevent duplicate actions when clicked 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>pull/5203/head
							parent
							
								
									b00df8a9d1
								
							
						
					
					
						commit
						d693142dd4
					
				@ -0,0 +1,55 @@
 | 
			
		||||
import { CheckIcon, CopyIcon } from "lucide-react";
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
import { cn } from "@/lib/utils";
 | 
			
		||||
 | 
			
		||||
interface PreProps {
 | 
			
		||||
  children?: React.ReactNode;
 | 
			
		||||
  className?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const CodeBlock = ({ children, className, ...props }: PreProps) => {
 | 
			
		||||
  const [copied, setCopied] = useState(false);
 | 
			
		||||
 | 
			
		||||
  // Extract the code element and its props
 | 
			
		||||
  const codeElement = children as React.ReactElement;
 | 
			
		||||
  const codeClassName = codeElement?.props?.className || "";
 | 
			
		||||
  const codeContent = String(codeElement?.props?.children || "").replace(/\n$/, "");
 | 
			
		||||
 | 
			
		||||
  // Extract language from className (format: language-xxx)
 | 
			
		||||
  const match = /language-(\w+)/.exec(codeClassName);
 | 
			
		||||
  const language = match ? match[1] : "";
 | 
			
		||||
 | 
			
		||||
  const handleCopy = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
      await navigator.clipboard.writeText(codeContent);
 | 
			
		||||
      setCopied(true);
 | 
			
		||||
      setTimeout(() => setCopied(false), 2000);
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      console.error("Failed to copy code:", err);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <pre className="relative group">
 | 
			
		||||
      <div className="w-full flex flex-row justify-between items-center">
 | 
			
		||||
        <span className="text-[10px] font-medium text-muted-foreground/60 uppercase tracking-wider select-none">{language}</span>
 | 
			
		||||
        <button
 | 
			
		||||
          onClick={handleCopy}
 | 
			
		||||
          className={cn(
 | 
			
		||||
            "p-1.5 rounded-md transition-all",
 | 
			
		||||
            "hover:bg-accent/50",
 | 
			
		||||
            "focus:outline-none focus:ring-1 focus:ring-ring",
 | 
			
		||||
            copied ? "text-primary" : "text-muted-foreground",
 | 
			
		||||
          )}
 | 
			
		||||
          aria-label={copied ? "Copied" : "Copy code"}
 | 
			
		||||
          title={copied ? "Copied!" : "Copy code"}
 | 
			
		||||
        >
 | 
			
		||||
          {copied ? <CheckIcon className="w-3.5 h-3.5" /> : <CopyIcon className="w-3.5 h-3.5" />}
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div className={className} {...props}>
 | 
			
		||||
        {children}
 | 
			
		||||
      </div>
 | 
			
		||||
    </pre>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue