remove playground
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -34,3 +34,6 @@ yarn-error.log*
|
|||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun.lock
|
||||||
|
|||||||
@@ -51,14 +51,15 @@ export default function Home() {
|
|||||||
Get Started
|
Get Started
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/playground"
|
href="https://www.youtube.com/channel/UCWRCKHQCS-LCjd2WfDJCvRg?sub_confirmation=1"
|
||||||
|
target="_blank"
|
||||||
className={buttonVariants({
|
className={buttonVariants({
|
||||||
variant: "secondary",
|
variant: "secondary",
|
||||||
className: "px-6 bg-gray-200 text-gray-900 hover:bg-gray-300 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700",
|
className: "px-6 bg-gray-200 text-gray-900 hover:bg-gray-300 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700",
|
||||||
size: "lg",
|
size: "lg",
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
Playground
|
Subscribe Now
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 py-12">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 py-12">
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import { PropsWithChildren } from "react";
|
|
||||||
import { getMetadata } from "@/app/layout";
|
|
||||||
|
|
||||||
export const metadata = getMetadata({
|
|
||||||
title: "Playground",
|
|
||||||
description: "Test and experiment with DocuBook markdown components in real-time",
|
|
||||||
image: "img-playground.png",
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function PlaygroundLayout({ children }: PropsWithChildren) {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col min-h-screen">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,395 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useEffect, useRef } from "react";
|
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
||||||
import { Separator } from "@/components/ui/separator";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import {
|
|
||||||
List,
|
|
||||||
ListOrdered,
|
|
||||||
Heading2,
|
|
||||||
Heading3,
|
|
||||||
Code,
|
|
||||||
Quote,
|
|
||||||
ImageIcon,
|
|
||||||
Link as LinkIcon,
|
|
||||||
Table,
|
|
||||||
Maximize2,
|
|
||||||
Minimize2,
|
|
||||||
Type,
|
|
||||||
ChevronDown,
|
|
||||||
Notebook,
|
|
||||||
Component,
|
|
||||||
Youtube as YoutubeIcon,
|
|
||||||
HelpCircle,
|
|
||||||
LayoutGrid,
|
|
||||||
MousePointer2,
|
|
||||||
Rows,
|
|
||||||
LayoutPanelTop,
|
|
||||||
Laptop2,
|
|
||||||
Copy,
|
|
||||||
Download,
|
|
||||||
RotateCcw,
|
|
||||||
Calendar
|
|
||||||
} from "lucide-react";
|
|
||||||
import { Button as UIButton } from "@/components/ui/button";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import {
|
|
||||||
handleParagraphClick,
|
|
||||||
handleHeading2Click,
|
|
||||||
handleHeading3Click,
|
|
||||||
handleBulletListClick,
|
|
||||||
handleNumberedListClick,
|
|
||||||
handleLinkClick,
|
|
||||||
handleImageClick,
|
|
||||||
handleBlockquoteClick,
|
|
||||||
handleCodeBlockClick,
|
|
||||||
handleTableClick,
|
|
||||||
handleNoteClick,
|
|
||||||
handleComponentClick,
|
|
||||||
handleMetadataClick,
|
|
||||||
} from "@/components/playground/MarkComponent";
|
|
||||||
|
|
||||||
import "@/styles/editor.css";
|
|
||||||
|
|
||||||
const ToolbarButton = ({ icon: Icon, label, onClick }: { icon: any, label: string, onClick?: () => void }) => (
|
|
||||||
<UIButton
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="h-8 w-8 p-0 hover:bg-muted"
|
|
||||||
title={label}
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
<Icon className="h-4 w-4" />
|
|
||||||
</UIButton>
|
|
||||||
);
|
|
||||||
|
|
||||||
const ToolbarSeparator = () => (
|
|
||||||
<Separator orientation="vertical" className="mx-1 h-6" />
|
|
||||||
);
|
|
||||||
|
|
||||||
const MobileMessage = () => (
|
|
||||||
<div className="min-h-[80vh] flex flex-col items-center justify-center text-center px-4 animate-in fade-in-50 duration-500">
|
|
||||||
<Laptop2 className="w-16 h-16 mb-4 text-muted-foreground animate-bounce" />
|
|
||||||
<h2 className="text-2xl font-bold mb-2">Desktop View Recommended</h2>
|
|
||||||
<p className="text-muted-foreground max-w-md">
|
|
||||||
The Playground works best on larger screens. Please switch to a desktop device for the best experience.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default function PlaygroundPage() {
|
|
||||||
const [markdown, setMarkdown] = useState("");
|
|
||||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
||||||
const [isMobile, setIsMobile] = useState(false);
|
|
||||||
const [lineCount, setLineCount] = useState(1);
|
|
||||||
const editorRef = useRef<HTMLTextAreaElement>(null);
|
|
||||||
const lineNumbersRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const checkMobile = () => {
|
|
||||||
setIsMobile(window.innerWidth < 768);
|
|
||||||
};
|
|
||||||
|
|
||||||
checkMobile();
|
|
||||||
window.addEventListener('resize', checkMobile);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('resize', checkMobile);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Update line count when markdown content changes
|
|
||||||
const lines = markdown.split('\n').length;
|
|
||||||
setLineCount(Math.max(lines, 1));
|
|
||||||
}, [markdown]);
|
|
||||||
|
|
||||||
// Sync scroll position between editor and line numbers
|
|
||||||
useEffect(() => {
|
|
||||||
const textarea = editorRef.current;
|
|
||||||
const lineNumbers = lineNumbersRef.current;
|
|
||||||
|
|
||||||
if (!textarea || !lineNumbers) return;
|
|
||||||
|
|
||||||
const handleScroll = () => {
|
|
||||||
lineNumbers.scrollTop = textarea.scrollTop;
|
|
||||||
};
|
|
||||||
|
|
||||||
textarea.addEventListener('scroll', handleScroll);
|
|
||||||
return () => textarea.removeEventListener('scroll', handleScroll);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const toggleFullscreen = () => {
|
|
||||||
setIsFullscreen(!isFullscreen);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCopy = async () => {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(markdown);
|
|
||||||
toast.success('Content copied to clipboard');
|
|
||||||
} catch (err) {
|
|
||||||
toast.error('Failed to copy content');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDownload = () => {
|
|
||||||
try {
|
|
||||||
const blob = new Blob([markdown], { type: 'text/markdown' });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = 'index.mdx';
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
document.body.removeChild(a);
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
toast.success('Content downloaded successfully');
|
|
||||||
} catch (err) {
|
|
||||||
toast.error('Failed to download content');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleReset = () => {
|
|
||||||
if (markdown.trim()) {
|
|
||||||
toast.custom((t) => (
|
|
||||||
<div className="flex flex-col gap-2 bg-background border rounded-lg p-4 shadow-lg">
|
|
||||||
<h3 className="font-semibold">Clear editor content?</h3>
|
|
||||||
<p className="text-sm text-muted-foreground">This action cannot be undone.</p>
|
|
||||||
<div className="flex gap-2 mt-2">
|
|
||||||
<UIButton
|
|
||||||
size="sm"
|
|
||||||
variant="destructive"
|
|
||||||
onClick={() => {
|
|
||||||
setMarkdown('');
|
|
||||||
toast.success('all content in the editor has been cleaned');
|
|
||||||
toast.dismiss(t);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</UIButton>
|
|
||||||
<UIButton
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => toast.dismiss(t)}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</UIButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
), { duration: 10000 });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const insertAtCursor = (textArea: HTMLTextAreaElement, text: string) => {
|
|
||||||
const start = textArea.selectionStart;
|
|
||||||
const end = textArea.selectionEnd;
|
|
||||||
const before = markdown.substring(0, start);
|
|
||||||
const after = markdown.substring(end);
|
|
||||||
|
|
||||||
const needsLeadingNewline = before && !before.endsWith('\n\n') ? '\n\n' : '';
|
|
||||||
const needsTrailingNewline = after && !after.startsWith('\n\n') ? '\n\n' : '';
|
|
||||||
|
|
||||||
const newText = `${before}${needsLeadingNewline}${text}${needsTrailingNewline}${after}`;
|
|
||||||
setMarkdown(newText);
|
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
textArea.focus();
|
|
||||||
const newPosition = before.length + needsLeadingNewline.length + text.length + 1;
|
|
||||||
textArea.setSelectionRange(newPosition, newPosition);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isMobile) {
|
|
||||||
return <MobileMessage />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={cn(
|
|
||||||
"flex flex-col transition-all duration-200",
|
|
||||||
isFullscreen ? "fixed inset-0 z-50 bg-background" : "min-h-[calc(100vh-4rem)]"
|
|
||||||
)}>
|
|
||||||
<div className="border-b bg-background">
|
|
||||||
<div className="py-8 px-2">
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<h1 className="text-2xl font-extrabold">Docu<span className="text-primary text-lg ml-1">PLAY</span></h1>
|
|
||||||
<p className="text-lg text-muted-foreground mt-2">
|
|
||||||
Test and experiment with DocuBook markdown components in real-time
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-1 py-8 px-2">
|
|
||||||
<div className="flex flex-col h-full pb-12">
|
|
||||||
<ScrollArea className="flex-1 border rounded-lg">
|
|
||||||
<div className="sticky top-0 z-20 bg-background border-b">
|
|
||||||
<div className="flex items-center justify-between p-2 bg-muted/40">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{markdown.trim() && (
|
|
||||||
<>
|
|
||||||
<UIButton
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={handleCopy}
|
|
||||||
className="gap-2 text-xs"
|
|
||||||
>
|
|
||||||
<Copy className="h-3.5 w-3.5" />
|
|
||||||
Copy
|
|
||||||
</UIButton>
|
|
||||||
<UIButton
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={handleDownload}
|
|
||||||
className="gap-2 text-xs"
|
|
||||||
>
|
|
||||||
<Download className="h-3.5 w-3.5" />
|
|
||||||
Download
|
|
||||||
</UIButton>
|
|
||||||
<UIButton
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={handleReset}
|
|
||||||
className="gap-2 text-xs"
|
|
||||||
>
|
|
||||||
<RotateCcw className="h-3.5 w-3.5" />
|
|
||||||
Reset
|
|
||||||
</UIButton>
|
|
||||||
<Separator orientation="vertical" className="h-4" />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<UIButton
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={toggleFullscreen}
|
|
||||||
className="gap-2 text-xs"
|
|
||||||
>
|
|
||||||
{isFullscreen ? (
|
|
||||||
<>
|
|
||||||
<Minimize2 className="h-3.5 w-3.5" />
|
|
||||||
Exit Fullscreen
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Maximize2 className="h-3.5 w-3.5" />
|
|
||||||
Fullscreen
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</UIButton>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center border-b p-1 bg-background">
|
|
||||||
<ToolbarButton icon={Calendar} label="Metadata" onClick={() => handleMetadataClick(insertAtCursor)} />
|
|
||||||
<ToolbarSeparator />
|
|
||||||
<ToolbarButton icon={Type} label="Paragraph" onClick={() => handleParagraphClick(insertAtCursor)} />
|
|
||||||
<ToolbarButton icon={Heading2} label="Heading 2" onClick={() => handleHeading2Click(insertAtCursor)} />
|
|
||||||
<ToolbarButton icon={Heading3} label="Heading 3" onClick={() => handleHeading3Click(insertAtCursor)} />
|
|
||||||
<ToolbarButton icon={List} label="Bullet List" onClick={() => handleBulletListClick(insertAtCursor)} />
|
|
||||||
<ToolbarButton icon={ListOrdered} label="Numbered List" onClick={() => handleNumberedListClick(insertAtCursor)} />
|
|
||||||
<ToolbarSeparator />
|
|
||||||
<ToolbarButton icon={LinkIcon} label="Link" onClick={() => handleLinkClick(insertAtCursor)} />
|
|
||||||
<ToolbarButton icon={ImageIcon} label="Image" onClick={() => handleImageClick(insertAtCursor)} />
|
|
||||||
<ToolbarButton icon={Quote} label="Blockquote" onClick={() => handleBlockquoteClick(insertAtCursor)} />
|
|
||||||
<ToolbarButton icon={Code} label="Code Block" onClick={() => handleCodeBlockClick(insertAtCursor)} />
|
|
||||||
<ToolbarButton icon={Table} label="Table" onClick={() => handleTableClick(insertAtCursor)} />
|
|
||||||
<ToolbarSeparator />
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<UIButton
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="h-8 px-2 flex items-center gap-1 font-normal"
|
|
||||||
>
|
|
||||||
<Notebook className="h-4 w-4" />
|
|
||||||
<ChevronDown className="h-4 w-4" />
|
|
||||||
</UIButton>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="start">
|
|
||||||
<DropdownMenuItem onClick={handleNoteClick(insertAtCursor, 'note')}>
|
|
||||||
Note
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={handleNoteClick(insertAtCursor, 'danger')}>
|
|
||||||
Danger
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={handleNoteClick(insertAtCursor, 'warning')}>
|
|
||||||
Warning
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={handleNoteClick(insertAtCursor, 'success')}>
|
|
||||||
Success
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
<ToolbarSeparator />
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<UIButton
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="h-8 px-2 flex items-center gap-1 font-normal"
|
|
||||||
>
|
|
||||||
<Component className="h-4 w-4" />
|
|
||||||
<ChevronDown className="h-4 w-4" />
|
|
||||||
</UIButton>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="start">
|
|
||||||
<DropdownMenuItem onClick={handleComponentClick(insertAtCursor, 'stepper')}>
|
|
||||||
<Rows className="h-4 w-4 mr-2" />
|
|
||||||
Stepper
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={handleComponentClick(insertAtCursor, 'card')}>
|
|
||||||
<LayoutGrid className="h-4 w-4 mr-2" />
|
|
||||||
Card
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={handleComponentClick(insertAtCursor, 'button')}>
|
|
||||||
<MousePointer2 className="h-4 w-4 mr-2" />
|
|
||||||
Button
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={handleComponentClick(insertAtCursor, 'accordion')}>
|
|
||||||
<ChevronDown className="h-4 w-4 mr-2" />
|
|
||||||
Accordion
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={handleComponentClick(insertAtCursor, 'tabs')}>
|
|
||||||
<LayoutPanelTop className="h-4 w-4 mr-2" />
|
|
||||||
Tabs
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={handleComponentClick(insertAtCursor, 'youtube')}>
|
|
||||||
<YoutubeIcon className="h-4 w-4 mr-2" />
|
|
||||||
Youtube
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={handleComponentClick(insertAtCursor, 'tooltip')}>
|
|
||||||
<HelpCircle className="h-4 w-4 mr-2" />
|
|
||||||
Tooltip
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="editor-container">
|
|
||||||
<div className="editor-line-numbers" ref={lineNumbersRef}>
|
|
||||||
<div className="editor-line-numbers-content">
|
|
||||||
{Array.from({ length: lineCount }).map((_, i) => (
|
|
||||||
<div key={i} data-line-number={i + 1} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<textarea
|
|
||||||
ref={editorRef}
|
|
||||||
value={markdown}
|
|
||||||
onChange={(e) => setMarkdown(e.target.value)}
|
|
||||||
className="editor-textarea"
|
|
||||||
spellCheck={false}
|
|
||||||
placeholder="Type '/' for commands..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ScrollArea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,252 +0,0 @@
|
|||||||
type InsertAtCursor = (textArea: HTMLTextAreaElement, text: string) => void;
|
|
||||||
|
|
||||||
// toolbar handler
|
|
||||||
export const handleMetadataClick = (insertAtCursor: InsertAtCursor) => {
|
|
||||||
const textArea = document.querySelector("textarea");
|
|
||||||
if (textArea) {
|
|
||||||
const metadata = `---
|
|
||||||
title: Post Title
|
|
||||||
description: Your Post Description
|
|
||||||
date: ${new Date().toISOString().split("T")[0]}
|
|
||||||
image: example-img.png
|
|
||||||
---\n\n`;
|
|
||||||
|
|
||||||
insertAtCursor(textArea, metadata);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleParagraphClick = (insertAtCursor: InsertAtCursor) => {
|
|
||||||
const textArea = document.querySelector("textarea");
|
|
||||||
if (textArea) {
|
|
||||||
insertAtCursor(textArea, "this is regular text, **bold text**, *italic text*\n");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleHeading2Click = (insertAtCursor: InsertAtCursor) => {
|
|
||||||
const textArea = document.querySelector("textarea");
|
|
||||||
if (textArea) {
|
|
||||||
insertAtCursor(textArea, "## Heading 2\n");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleHeading3Click = (insertAtCursor: InsertAtCursor) => {
|
|
||||||
const textArea = document.querySelector("textarea");
|
|
||||||
if (textArea) {
|
|
||||||
insertAtCursor(textArea, "### Heading 3\n");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleBulletListClick = (insertAtCursor: InsertAtCursor) => {
|
|
||||||
const textArea = document.querySelector("textarea");
|
|
||||||
if (textArea) {
|
|
||||||
insertAtCursor(textArea, "- List One\n- List Two\n- Other List\n");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleNumberedListClick = (insertAtCursor: InsertAtCursor) => {
|
|
||||||
const textArea = document.querySelector("textarea");
|
|
||||||
if (textArea) {
|
|
||||||
insertAtCursor(textArea, "1. Number One\n2. Number Two\n3. Number Three\n");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleLinkClick = (insertAtCursor: InsertAtCursor) => {
|
|
||||||
const textArea = document.querySelector("textarea");
|
|
||||||
if (textArea) {
|
|
||||||
insertAtCursor(textArea, "[Visit OpenAI](https://www.openai.com)\n");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleImageClick = (insertAtCursor: InsertAtCursor) => {
|
|
||||||
const textArea = document.querySelector("textarea");
|
|
||||||
if (textArea) {
|
|
||||||
insertAtCursor(textArea, "\n");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleBlockquoteClick = (insertAtCursor: InsertAtCursor) => {
|
|
||||||
const textArea = document.querySelector("textarea");
|
|
||||||
if (textArea) {
|
|
||||||
insertAtCursor(textArea, "> The overriding design goal for Markdown's formatting syntax is to make it as readable as possible.\n");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleCodeBlockClick = (insertAtCursor: InsertAtCursor) => {
|
|
||||||
const textArea = document.querySelector("textarea");
|
|
||||||
if (textArea) {
|
|
||||||
insertAtCursor(
|
|
||||||
textArea,
|
|
||||||
"```javascript:main.js showLineNumbers {3-4}\nfunction isRocketAboutToCrash() {\n // Check if the rocket is stable\n if (!isStable()) {\n NoCrash(); // Prevent the crash\n }\n}\n```\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleTableClick = (insertAtCursor: InsertAtCursor) => {
|
|
||||||
const textArea = document.querySelector("textarea");
|
|
||||||
if (textArea) {
|
|
||||||
insertAtCursor(
|
|
||||||
textArea,
|
|
||||||
`| **Feature** | **Description** |
|
|
||||||
| ------------------------------- | ----------------------------------------------------- |
|
|
||||||
| MDX Support | Write interactive documentation with MDX. |
|
|
||||||
| Nested Pages | Organize content in a nested, hierarchical structure. |
|
|
||||||
| Blog Section | Include a dedicated blog section. |
|
|
||||||
| Pagination | Split content across multiple pages. |
|
|
||||||
|
|
||||||
`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleNoteClick = (insertAtCursor: InsertAtCursor, type: string) => {
|
|
||||||
return () => {
|
|
||||||
const textArea = document.querySelector("textarea");
|
|
||||||
if (textArea) {
|
|
||||||
const noteTemplate = `<Note type="${type}" title="${type.charAt(0).toUpperCase() + type.slice(1)}">\n This is a ${type} message.\n</Note>\n`;
|
|
||||||
insertAtCursor(textArea, noteTemplate);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleComponentClick = (insertAtCursor: InsertAtCursor, component: string) => {
|
|
||||||
return () => {
|
|
||||||
const textArea = document.querySelector("textarea");
|
|
||||||
if (!textArea) return;
|
|
||||||
|
|
||||||
const templates: { [key: string]: string } = {
|
|
||||||
stepper: `<Stepper>
|
|
||||||
<StepperItem title="Step 1">
|
|
||||||
Content for step 1
|
|
||||||
</StepperItem>
|
|
||||||
<StepperItem title="Step 2">
|
|
||||||
Content for step 2
|
|
||||||
</StepperItem>
|
|
||||||
</Stepper>\n`,
|
|
||||||
card: `<Card title="Click on me" icon="Link" href="/docs/getting-started/components/button">
|
|
||||||
This is how you use a card with an icon and a link. Clicking on this card brings you to the Card Group page.
|
|
||||||
</Card>\n`,
|
|
||||||
button: `<Button
|
|
||||||
text="Click Me"
|
|
||||||
href="#"
|
|
||||||
icon="ArrowRight"
|
|
||||||
size="md"
|
|
||||||
variation="primary"
|
|
||||||
/>\n`,
|
|
||||||
accordion: `<Accordion title="Markdown">
|
|
||||||
this is an example of plain text content from the accordion component and below is markdown ;
|
|
||||||
1. number one
|
|
||||||
2. number two
|
|
||||||
3. number three
|
|
||||||
</Accordion>\n`,
|
|
||||||
youtube: `<Youtube videoId="your-video-id" />\n`,
|
|
||||||
tooltip: `What do you know about <Tooltip text="DocuBook" tip="npx @docubook/create@latest" /> ? Create interactive nested documentations using MDX.\n`,
|
|
||||||
tabs: `<Tabs defaultValue="tab1" className="pt-5 pb-1">
|
|
||||||
<TabsList>
|
|
||||||
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
|
|
||||||
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
<TabsContent value="tab1">
|
|
||||||
Content for tab 1
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="tab2">
|
|
||||||
Content for tab 2
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>\n`
|
|
||||||
};
|
|
||||||
|
|
||||||
insertAtCursor(textArea, templates[component]);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// slash command handler
|
|
||||||
export const MARK_COMPONENTS = [
|
|
||||||
{ label: "Metadata", value: `---
|
|
||||||
title: Post Title
|
|
||||||
description: Your Post Description
|
|
||||||
date: ${new Date().toISOString().split("T")[0]}
|
|
||||||
image: example-img.png
|
|
||||||
---\n\n` },
|
|
||||||
|
|
||||||
{ label: "Heading 2", value: "## Heading 2\n" },
|
|
||||||
{ label: "Heading 3", value: "### Heading 3\n" },
|
|
||||||
|
|
||||||
{ label: "Paragraph", value: "this is regular text, **bold text**, *italic text*\n" },
|
|
||||||
|
|
||||||
{ label: "Bullet List", value: "- List One\n- List Two\n- Other List\n" },
|
|
||||||
{ label: "Numbered List", value: "1. Number One\n2. Number Two\n3. Number Three\n" },
|
|
||||||
|
|
||||||
{ label: "Blockquote", value: "> The overriding design goal for Markdown's formatting syntax is to make it as readable as possible.\n" },
|
|
||||||
|
|
||||||
{ label: "Code Block", value: "```javascript:main.js showLineNumbers {3-4}\nfunction isRocketAboutToCrash() {\n // Check if the rocket is stable\n if (!isStable()) {\n NoCrash(); // Prevent the crash\n }\n}\n```\n" },
|
|
||||||
|
|
||||||
{ label: "Table", value: `| **Feature** | **Description** |
|
|
||||||
| ------------------------------- | ----------------------------------------------------- |
|
|
||||||
| MDX Support | Write interactive documentation with MDX. |
|
|
||||||
| Nested Pages | Organize content in a nested, hierarchical structure. |
|
|
||||||
| Blog Section | Include a dedicated blog section. |
|
|
||||||
| Pagination | Split content across multiple pages. |
|
|
||||||
|
|
||||||
` },
|
|
||||||
|
|
||||||
{ label: "Link", value: "[Visit OpenAI](https://www.openai.com)\n" },
|
|
||||||
{ label: "Image", value: "\n" },
|
|
||||||
|
|
||||||
// ⭐ Komponen Interaktif DocuBook ⭐
|
|
||||||
{ label: "Stepper", value: `<Stepper>
|
|
||||||
<StepperItem title="Step 1">
|
|
||||||
Content for step 1
|
|
||||||
</StepperItem>
|
|
||||||
<StepperItem title="Step 2">
|
|
||||||
Content for step 2
|
|
||||||
</StepperItem>
|
|
||||||
</Stepper>\n` },
|
|
||||||
|
|
||||||
{ label: "Button", value: `<Button
|
|
||||||
text="Click Me"
|
|
||||||
href="#"
|
|
||||||
icon="ArrowRight"
|
|
||||||
size="md"
|
|
||||||
variation="primary"
|
|
||||||
/>\n` },
|
|
||||||
|
|
||||||
{ label: "Accordion", value: `<Accordion title="Markdown">
|
|
||||||
This is an example of plain text content inside the accordion component.
|
|
||||||
1. Number One
|
|
||||||
2. Number Two
|
|
||||||
3. Number Three
|
|
||||||
</Accordion>\n` },
|
|
||||||
|
|
||||||
{ label: "Youtube", value: `<Youtube videoId="your-video-id" />\n` },
|
|
||||||
|
|
||||||
{ label: "Tooltip", value: `What do you know about <Tooltip text="DocuBook" tip="npx @docubook/create@latest" /> ?\n` },
|
|
||||||
|
|
||||||
{ label: "Tabs", value: `<Tabs defaultValue="tab1" className="pt-5 pb-1">
|
|
||||||
<TabsList>
|
|
||||||
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
|
|
||||||
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
<TabsContent value="tab1">
|
|
||||||
Content for tab 1
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="tab2">
|
|
||||||
Content for tab 2
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>\n` },
|
|
||||||
|
|
||||||
{ label: "Note", value: `<Note type="note" title="Note">
|
|
||||||
This is a note message.
|
|
||||||
</Note>\n` },
|
|
||||||
|
|
||||||
{ label: "Danger", value: `<Note type="danger" title="Danger">
|
|
||||||
This is a danger message.
|
|
||||||
</Note>\n` },
|
|
||||||
|
|
||||||
{ label: "Warning", value: `<Note type="warning" title="Warning">
|
|
||||||
This is a warning message.
|
|
||||||
</Note>\n` },
|
|
||||||
|
|
||||||
{ label: "Success", value: `<Note type="success" title="Success">
|
|
||||||
This is a success message.
|
|
||||||
</Note>\n` }
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -8,8 +8,7 @@
|
|||||||
"menu": [
|
"menu": [
|
||||||
{ "title": "Docs", "href": "/docs/getting-started/introduction" },
|
{ "title": "Docs", "href": "/docs/getting-started/introduction" },
|
||||||
{ "title": "Changelog", "href": "/changelog" },
|
{ "title": "Changelog", "href": "/changelog" },
|
||||||
{ "title": "Blog", "href": "/blog" },
|
{ "title": "Blog", "href": "/blog" }
|
||||||
{ "title": "Playground", "href": "/playground" }
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
|
|||||||
Reference in New Issue
Block a user