Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/chakra-ui_chakra-ui.json
T

1 line
107 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "chakra-ui/chakra-ui", "files": {"apps/www/public/r/examples/rich-text-editor.json": {"f6747f9c4ef6cbf117bfd55761304369cf404120": {"t": 1785674683.9763095, "n": 101326, "h": "63cf9e7e7bd3bf5f", "c": "{\n \"name\": \"rich-text-editor\",\n \"file\": \"compositions/ui/rich-text-editor\",\n \"snippet\": \"\\\"use client\\\"\\n\\nimport type { BoxProps, StackProps, SystemStyleObject } from \\\"@chakra-ui/react\\\"\\nimport { Box, HStack, StackSeparator, defineStyle } from \\\"@chakra-ui/react\\\"\\nimport { Editor, EditorContent } from \\\"@tiptap/react\\\"\\nimport {\\n RichTextEditorContext,\\n useRichTextEditorContext,\\n} from \\\"@/components/ui/rich-text-editor-context\\\"\\nimport * as React from \\\"react\\\"\\n\\nconst proseMirrorBaseCss = defineStyle({\\n display: \\\"flex\\\",\\n flexDirection: \\\"column\\\",\\n borderWidth: \\\"1px\\\",\\n rounded: \\\"l2\\\",\\n lineHeight: \\\"1.5\\\",\\n\\n \\\"--content-padding-x\\\": \\\"spacing.5\\\",\\n \\\"--content-padding-y\\\": \\\"spacing.5\\\",\\n\\n \\\"& img.ProseMirror-selectednode\\\": {\\n outlineWidth: \\\"2px\\\",\\n outlineStyle: \\\"solid\\\",\\n outlineColor: \\\"blue.focusRing\\\",\\n },\\n\\n \\\"& .ProseMirror\\\": {\\n outline: \\\"none\\\",\\n minHeight: \\\"var(--content-min-height)\\\",\\n px: \\\"var(--content-padding-x)\\\",\\n py: \\\"var(--content-padding-y)\\\",\\n \\\"& > * + *\\\": { marginTop: \\\"0.75em\\\" },\\n \\\"& h1\\\": {\\n fontSize: \\\"2.15em\\\",\\n letterSpacing: \\\"-0.02em\\\",\\n lineHeight: \\\"1.2em\\\",\\n },\\n \\\"& h2\\\": {\\n fontSize: \\\"1.65em\\\",\\n letterSpacing: \\\"-0.02em\\\",\\n lineHeight: \\\"1.3em\\\",\\n },\\n \\\"& h3\\\": {\\n fontSize: \\\"1.35em\\\",\\n letterSpacing: \\\"-0.01em\\\",\\n lineHeight: \\\"1.4em\\\",\\n },\\n \\\"& h4\\\": {\\n fontSize: \\\"1.15em\\\",\\n letterSpacing: \\\"-0.01em\\\",\\n lineHeight: \\\"1.5em\\\",\\n },\\n \\\"& h5\\\": {\\n fontSize: \\\"1em\\\",\\n letterSpacing: \\\"-0.01em\\\",\\n lineHeight: \\\"1.5em\\\",\\n },\\n \\\"& h6\\\": {\\n fontSize: \\\"0.875em\\\",\\n letterSpacing: \\\"-0.01em\\\",\\n lineHeight: \\\"1.5em\\\",\\n },\\n \\\"& h1, h2, h3, h4, h5, h6\\\": {\\n color: \\\"fg\\\",\\n fontWeight: \\\"600\\\",\\n },\\n \\\"& code\\\": {\\n bg: \\\"bg.muted\\\",\\n paddingInline: \\\"0.25em\\\",\\n rounded: \\\"sm\\\",\\n fontFamily: \\\"mono\\\",\\n fontSize: \\\"0.9em\\\",\\n borderWidth: \\\"1px\\\",\\n },\\n \\\"& pre\\\": {\\n bg: \\\"gray.900\\\",\\n color: \\\"gray.100\\\",\\n padding: \\\"4\\\",\\n rounded: \\\"lg\\\",\\n overflowX: \\\"auto\\\",\\n fontSize: \\\"sm\\\",\\n lineHeight: \\\"1.6\\\",\\n borderWidth: \\\"1px\\\",\\n borderColor: \\\"gray.700\\\",\\n boxShadow: \\\"inset 0 0 0 1px rgba(255,255,255,0.03)\\\",\\n },\\n \\\"& pre code\\\": {\\n bg: \\\"transparent\\\",\\n padding: \\\"0\\\",\\n fontFamily: \\\"mono\\\",\\n color: \\\"inherit\\\",\\n borderWidth: \\\"0\\\",\\n },\\n \\\"& blockquote\\\": {\\n borderStartWidth: \\\"4px\\\",\\n borderStartColor: \\\"border\\\",\\n paddingStart: \\\"4\\\",\\n },\\n \\\"& ul:not([data-type='taskList'])\\\": {\\n paddingInlineStart: \\\"1.25rem\\\",\\n listStyleType: \\\"disc\\\",\\n },\\n \\\"& ol:not([data-type='taskList'])\\\": {\\n paddingInlineStart: \\\"1.25rem\\\",\\n listStyleType: \\\"decimal\\\",\\n },\\n \\\"& ul ul\\\": {\\n listStyleType: \\\"circle\\\",\\n },\\n \\\"& ul ul ul\\\": {\\n listStyleType: \\\"square\\\",\\n },\\n \\\"& ul[data-type='taskList'] li\\\": {\\n listStyle: \\\"none\\\",\\n display: \\\"flex\\\",\\n alignItems: \\\"flex-start\\\",\\n gap: \\\"2\\\",\\n \\\"& input[type='checkbox']\\\": {\\n accentColor: \\\"colorPalette.solid\\\",\\n marginTop: \\\"1\\\",\\n },\\n },\\n \\\"& hr\\\": { my: \\\"4\\\" },\\n \\\"& a\\\": { color: \\\"blue.fg\\\", textDecoration: \\\"underline\\\" },\\n \\\"& em\\\": { fontStyle: \\\"italic\\\" },\\n \\\"& strong\\\": { fontWeight: \\\"bold\\\" },\\n \\\"& p.is-editor-empty:first-of-type::before\\\": {\\n content: \\\"attr(data-placeholder)\\\",\\n color: \\\"fg.muted\\\",\\n pointerEvents: \\\"none\\\",\\n float: \\\"left\\\",\\n height: \\\"0\\\",\\n },\\n\\n \\\"& .node-hashtag\\\": {\\n layerStyle: \\\"fill.surface\\\",\\n px: \\\"0.25em\\\",\\n py: \\\"2px\\\",\\n rounded: \\\"l1\\\",\\n },\\n },\\n\\n \\\"&[data-disabled] .ProseMirror\\\": {\\n pointerEvents: \\\"none\\\",\\n opacity: 0.5,\\n cursor: \\\"not-allowed\\\",\\n },\\n})\\n\\nexport interface RichTextEditorProps extends BoxProps {\\n editor: Editor | null\\n disabled?: boolean\\n}\\n\\nexport const RichTextEditorRoot = React.forwardRef<\\n HTMLDivElement,\\n RichTextEditorProps\\n>(function RichTextEditorRoot(props, ref) {\\n const { editor, children, css, disabled, ...rest } = props\\n const contextValue = React.useMemo(() => ({ editor }), [editor])\\n return (\\n <RichTextEditorContext.Provider value={contextValue}>\\n <Box\\n ref={ref}\\n data-disabled={disabled || undefined}\\n css={[proseMirrorBaseCss, css]}\\n {...rest}\\n >\\n {children}\\n </Box>\\n </RichTextEditorContext.Provider>\\n )\\n})\\n\\ntype RichTextEditorToolbarVariant = \\\"sticky\\\" | \\\"floating\\\" | \\\"fixed\\\"\\n\\nexport interface RichTextEditorToolbarProps extends StackProps {\\n variant?: RichTextEditorToolbarVariant\\n stickyOffset?: string\\n}\\n\\nconst toolbarStylesMap: Record<\\n RichTextEditorToolbarVariant,\\n SystemStyleObject\\n> = {\\n sticky: {\\n bg: \\\"bg\\\",\\n position: \\\"sticky\\\",\\n top: \\\"var(--sticky-offset, 0px)\\\",\\n zIndex: \\\"1\\\",\\n py: \\\"1.5\\\",\\n px: \\\"3\\\",\\n },\\n fixed: {\\n bg: \\\"bg\\\",\\n roundedTop: \\\"l2\\\",\\n borderBottomWidth: \\\"1px\\\",\\n py: \\\"1.5\\\",\\n px: \\\"3\\\",\\n },\\n floating: {\\n shadow: \\\"md\\\",\\n roundedTop: \\\"l2\\\",\\n bg: \\\"bg.panel\\\",\\n px: \\\"1.5\\\",\\n py: \\\"1.5\\\",\\n },\\n}\\n\\nexport const RichTextEditorToolbar = React.forwardRef<\\n HTMLDivElement,\\n RichTextEditorToolbarProps\\n>(function RichTextEditorToolbar(props, ref) {\\n const { variant = \\\"fixed\\\", stickyOffset = \\\"0px\\\", ...rest } = props\\n const variantStyles = toolbarStylesMap[variant]\\n\\n return (\\n <HStack\\n ref={ref}\\n flexWrap=\\\"wrap\\\"\\n separator={<StackSeparator h=\\\"5\\\" alignSelf=\\\"center\\\" />}\\n {...rest}\\n style={{\\n [\\\"--sticky-offset\\\" as string]: stickyOffset,\\n ...rest.style,\\n }}\\n css={[variantStyles, rest.css]}\\n />\\n )\\n})\\n\\nexport const RichTextEditorFooter = React.forwardRef<\\n HTMLDivElement,\\n StackProps\\n>(function RichTextEditorFooter(props, ref) {\\n return <HStack ref={ref} gap=\\\"1\\\" borderTopWidth=\\\"1px\\\" p=\\\"3\\\" {...props} />\\n})\\n\\nexport interface RichTextEditorContentProps\\n extends Omit<React.ComponentProps<typeof EditorContent>, \\\"editor\\\"> {}\\n\\nexport const RichTextEditorContent = React.forwardRef<\\n HTMLDivElement,\\n RichTextEditorContentProps\\n>(function RichTextEditorContent(props, ref) {\\n const { editor } = useRichTextEditorContext()\\n if (!editor) return null\\n return <EditorContent editor={editor} {...props} innerRef={ref} />\\n})\\n\\nexport interface RichTextEditorControlGroupProps extends StackProps {}\\n\\nexport const RichTextEditorControlGroup = React.forwardRef<\\n HTMLDivElement,\\n RichTextEditorControlGroupProps\\n>(function RichTextEditorButtonGroup(props, ref) {\\n return <HStack ref={ref} gap=\\\"1\\\" {...props} />\\n})\\n\\nexport const RichTextEditor = {\\n Root: RichTextEditorRoot,\\n Toolbar: RichTextEditorToolbar,\\n Content: RichTextEditorContent,\\n ControlGroup: RichTextEditorControlGroup,\\n Footer: RichTextEditorFooter,\\n} as const\\n\\nexport * as Control from \\\"@/components/ui/rich-text-editor-control\\\"\\n\\nexport {\\n createBooleanControl,\\n createSelectControl,\\n createSwatchControl,\\n} from \\\"@/components/ui/rich-text-editor-control\\\"\\n\\nexport { useRichTextEditorContext } from \\\"@/components/ui/rich-text-editor-context\\\"\\n\",\n \"examples\": [\n {\n \"name\": \"rich-text-editor-basic\",\n \"content\": \"\\\"use client\\\"\\nexport const RichTextEditorBasic = () => {\\n const editor = useEditor({\\n extensions: [\\n StarterKit.configure({ link: { openOnClick: false } }),\\n Subscript,\\n Superscript,\\n TextAlign.configure({ types: [\\\"paragraph\\\", \\\"heading\\\"] }),\\n TextStyleKit,\\n ],\\n content: `<h1>Welcome to Chakra UI + Tiptap!</h1><p>Edit using the toolbar below...</p>`,\\n shouldRerenderOnTransaction: true,\\n immediatelyRender: false,\\n })\\n\\n if (!editor) return null\\n\\n return (\\n <RichTextEditor.Root editor={editor}>\\n <RichTextEditor.Toolbar>\\n <RichTextEditor.ControlGroup>\\n <Control.FontFamily />\\n <Control.FontSize />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.Bold />\\n <Control.Italic />\\n <Control.Underline />\\n <Control.Strikethrough />\\n <Control.Code />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.H1 />\\n <Control.H2 />\\n <Control.H3 />\\n <Control.H4 />\\n </RichTextEditor.ControlGroup>\\n </RichTextEditor.Toolbar>\\n\\n <RichTextEditor.Content />\\n </RichTextEditor.Root>\\n )\\n}\\n\",\n \"hasSnippet\": true,\n \"importPaths\": [\n \"import Subscript from \\\"@tiptap/extension-subscript\\\"\",\n \"import Superscript from \\\"@tiptap/extension-superscript\\\"\",\n \"import TextAlign from \\\"@tiptap/extension-text-align\\\"\",\n \"import { TextStyleKit } from \\\"@tiptap/extension-text-style\\\"\",\n \"import { useEditor } from \\\"@tiptap/react\\\"\",\n \"import StarterKit from \\\"@tiptap/starter-kit\\\"\",\n \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\"\n ],\n \"importPath\": \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"npmDependencies\": [\n \"@tiptap/extension-subscript\",\n \"@tiptap/extension-superscript\",\n \"@tiptap/extension-text-align\",\n \"@tiptap/extension-text-style\",\n \"@tiptap/react\",\n \"@tiptap/starter-kit\"\n ],\n \"fileDependencies\": [\n \"compositions/ui/rich-text-editor\"\n ]\n },\n {\n \"name\": \"rich-text-editor-composition\",\n \"content\": \"\\\"use client\\\"\\nexport const RichTextEditorComposition = () => {\\n const [linkBubblePosition, setLinkBubblePosition] = useState<{\\n top: number\\n left: number\\n } | null>(null)\\n\\n const editor = useEditor({\\n extensions: [\\n StarterKit.configure({ heading: false }),\\n HeadingWithSlug.configure({ levels: [1, 2, 3] }),\\n TextStyleKit,\\n Color,\\n Highlight.configure({ multicolor: true }),\\n Subscript,\\n Superscript,\\n TextAlign.configure({ types: [\\\"heading\\\", \\\"paragraph\\\"] }),\\n Link.configure({ openOnClick: false }),\\n Image,\\n TaskList,\\n TaskItem.configure({ nested: true }),\\n ],\\n content: editorContent,\\n onSelectionUpdate: ({ editor }) => {\\n if (editor.isActive(\\\"link\\\")) {\\n const { from } = editor.state.selection\\n const domAtPos = editor.view.domAtPos(from)\\n const node = domAtPos.node as HTMLElement\\n const linkElement =\\n node.nodeType === Node.TEXT_NODE ? node.parentElement : node\\n\\n if (linkElement && linkElement.tagName === \\\"A\\\") {\\n const rect = linkElement.getBoundingClientRect()\\n setLinkBubblePosition({\\n top: rect.bottom + window.scrollY + 8,\\n left: rect.left + window.scrollX + rect.width / 2,\\n })\\n }\\n } else {\\n setLinkBubblePosition(null)\\n }\\n },\\n shouldRerenderOnTransaction: true,\\n immediatelyRender: false,\\n })\\n\\n if (!editor) return null\\n\\n return (\\n <RichTextEditor.Root\\n editor={editor}\\n shadow=\\\"sm\\\"\\n h=\\\"100vh\\\"\\n display=\\\"flex\\\"\\n flexDirection=\\\"column\\\"\\n css={{\\n \\\"--content-padding-x\\\": \\\"spacing.16\\\",\\n \\\"--content-padding-y\\\": \\\"spacing.12\\\",\\n }}\\n >\\n <GoogleDocsHeader />\\n <Toolbar />\\n <HStack\\n borderTop=\\\"1px solid\\\"\\n borderColor=\\\"border\\\"\\n flex=\\\"1\\\"\\n mt=\\\"4\\\"\\n alignItems=\\\"stretch\\\"\\n gap={0}\\n overflow=\\\"hidden\\\"\\n >\\n <Box\\n w=\\\"280px\\\"\\n borderRight=\\\"1px solid\\\"\\n borderColor=\\\"border\\\"\\n display=\\\"flex\\\"\\n flexDirection=\\\"column\\\"\\n overflow=\\\"hidden\\\"\\n >\\n <SidebarOutline editor={editor} />\\n </Box>\\n <Flex\\n flex=\\\"1\\\"\\n justifyContent=\\\"center\\\"\\n overflowY=\\\"auto\\\"\\n position=\\\"relative\\\"\\n >\\n <RichTextEditor.Content />\\n {linkBubblePosition && (\\n <LinkBubbleMenu\\n editor={editor}\\n position={linkBubblePosition}\\n onClose={() => setLinkBubblePosition(null)}\\n />\\n )}\\n </Flex>\\n </HStack>\\n </RichTextEditor.Root>\\n )\\n}\\n\\nconst LinkBubbleMenu = ({\\n editor,\\n position,\\n onClose,\\n}: {\\n editor: Editor\\n position: { top: number; left: number }\\n onClose: () => void\\n}) => {\\n const [url, setUrl] = useState(\\\"\\\")\\n const [isEditing, setIsEditing] = useState(false)\\n\\n useEffect(() => {\\n const attrs = editor.getAttributes(\\\"link\\\")\\n setUrl(attrs.href || \\\"\\\")\\n }, [editor])\\n\\n const handleSave = () => {\\n if (url.trim()) {\\n const isValid = /^https?:\\\\/\\\\//i.test(url.trim())\\n const finalUrl = isValid ? url.trim() : `https://${url.trim()}`\\n editor\\n .chain()\\n .focus()\\n .extendMarkRange(\\\"link\\\")\\n .setLink({ href: finalUrl })\\n .run()\\n }\\n setIsEditing(false)\\n }\\n\\n const handleRemove = () => {\\n editor.chain().focus().unsetLink().run()\\n onClose()\\n }\\n\\n return (\\n <Box\\n position=\\\"fixed\\\"\\n top={`${position.top}px`}\\n left={`${position.left}px`}\\n transform=\\\"translateX(-50%)\\\"\\n bg=\\\"white\\\"\\n boxShadow=\\\"lg\\\"\\n borderRadius=\\\"md\\\"\\n borderWidth=\\\"1px\\\"\\n p={3}\\n zIndex={1000}\\n minW=\\\"280px\\\"\\n >\\n {isEditing ? (\\n <VStack gap={2} align=\\\"stretch\\\">\\n <Input\\n size=\\\"sm\\\"\\n value={url}\\n onChange={(e) => setUrl(e.target.value)}\\n placeholder=\\\"Enter URL\\\"\\n autoFocus\\n onKeyDown={(e) => {\\n if (e.key === \\\"Enter\\\") handleSave()\\n if (e.key === \\\"Escape\\\") setIsEditing(false)\\n }}\\n />\\n <HStack justify=\\\"flex-end\\\" gap={2}>\\n <Button\\n size=\\\"xs\\\"\\n variant=\\\"ghost\\\"\\n onClick={() => setIsEditing(false)}\\n >\\n Cancel\\n </Button>\\n <Button size=\\\"xs\\\" colorPalette=\\\"blue\\\" onClick={handleSave}>\\n Save\\n </Button>\\n </HStack>\\n </VStack>\\n ) : (\\n <VStack gap={2} align=\\\"stretch\\\">\\n <HStack justify=\\\"space-between\\\">\\n <Text\\n fontSize=\\\"sm\\\"\\n lineClamp={1}\\n truncate\\n flex=\\\"1\\\"\\n color=\\\"blue.600\\\"\\n >\\n {url}\\n </Text>\\n </HStack>\\n <HStack gap={2}>\\n <Button\\n size=\\\"xs\\\"\\n variant=\\\"outline\\\"\\n onClick={() => setIsEditing(true)}\\n flex=\\\"1\\\"\\n >\\n Edit\\n </Button>\\n <Button\\n size=\\\"xs\\\"\\n variant=\\\"outline\\\"\\n colorPalette=\\\"red\\\"\\n onClick={handleRemove}\\n flex=\\\"1\\\"\\n >\\n Remove\\n </Button>\\n </HStack>\\n </VStack>\\n )}\\n </Box>\\n )\\n}\\n\\nconst GoogleDocsHeader = () => {\\n return (\\n <Flex px={4} py={2} alignItems=\\\"center\\\" justifyContent=\\\"space-between\\\">\\n <HStack gap={3} align=\\\"flex-start\\\">\\n <Icon as={LuFileText} color=\\\"blue.500\\\" boxSize={8} mt={1} />\\n\\n <VStack align=\\\"flex-start\\\" gap={0}>\\n <HStack gap={2}>\\n <Text fontSize=\\\"lg\\\" fontWeight=\\\"semibold\\\">\\n Legend Of X: The Complete Saga\\n </Text>\\n <IconButton variant=\\\"ghost\\\" size=\\\"xs\\\" color=\\\"gray.500\\\">\\n <LuStar size={16} />\\n </IconButton>\\n </HStack>\\n\\n <HStack gap={3}>\\n {menuItems.map((menu) => (\\n <MenuRoot key={menu.label}>\\n <MenuTrigger>\\n <Button fontSize=\\\"sm\\\" px={2} py={1} variant=\\\"ghost\\\" size=\\\"xs\\\">\\n {menu.label}\\n </Button>\\n </MenuTrigger>\\n\\n <MenuContent minW=\\\"200px\\\" py={1}>\\n {menu.items.map((item) => (\\n <MenuItem\\n value={item.label}\\n key={item.label}\\n gap={3}\\n cursor=\\\"button\\\"\\n >\\n <HStack gap={3} align=\\\"center\\\">\\n <Icon as={() => item.icon} boxSize={4} />\\n <Text fontSize=\\\"sm\\\">{item.label}</Text>\\n </HStack>\\n </MenuItem>\\n ))}\\n </MenuContent>\\n </MenuRoot>\\n ))}\\n </HStack>\\n </VStack>\\n </HStack>\\n\\n <HStack gap={4}>\\n <IconButton variant=\\\"ghost\\\">\\n <LuMessageSquare size={20} />\\n </IconButton>\\n\\n <HStack gap={0}>\\n <IconButton variant=\\\"ghost\\\">\\n <LuVideo size={20} />\\n </IconButton>\\n <IconButton variant=\\\"ghost\\\" size=\\\"xs\\\">\\n <LuChevronDown size={14} />\\n </IconButton>\\n </HStack>\\n\\n <Button borderRadius=\\\"full\\\" px={6} gap={2} colorPalette=\\\"blue\\\">\\n <LuLock size={14} />\\n Share\\n </Button>\\n\\n <Avatar\\n fallback={<Text fontSize=\\\"sm\\\">SA</Text>}\\n name=\\\"Segun Adebayo\\\"\\n src=\\\"https://bit.ly/sage-adebayo\\\"\\n />\\n </HStack>\\n </Flex>\\n )\\n}\\nconst Toolbar = () => {\\n return (\\n <Box px={4}>\\n <HStack\\n bg=\\\"bg.muted\\\"\\n p={2}\\n gap={1}\\n rounded=\\\"50px\\\"\\n mt=\\\"4\\\"\\n overflowX=\\\"auto\\\"\\n >\\n <IconButton variant=\\\"ghost\\\" size=\\\"sm\\\">\\n <LuSearch />\\n </IconButton>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.Undo />\\n <Control.Redo />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.FontFamily width=\\\"140px\\\" />\\n <Control.FontSize width=\\\"80px\\\" />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.Bold />\\n <Control.Italic />\\n <Control.Underline />\\n <Control.Strikethrough />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.H1 />\\n <Control.H2 />\\n <Control.H3 />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.AlignLeft />\\n <Control.AlignCenter />\\n <Control.AlignRight />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.BulletList />\\n <Control.OrderedList />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <LinkControl />\\n <InsertImageControl />\\n </RichTextEditor.ControlGroup>\\n </HStack>\\n </Box>\\n )\\n}\\n\\nconst SidebarOutline = ({ editor }: { editor: Editor }) => {\\n if (!editor) return null\\n\\n const headings: { level: number; text: string; id: string }[] = []\\n\\n if (editor.getJSON().content) {\\n editor.getJSON().content.forEach((node, i) => {\\n if (node.type === \\\"heading\\\") {\\n const { attrs = {}, content = [] } = node\\n const level = attrs.level ?? 1\\n const id = attrs.id ?? `heading-${i}`\\n const text = content.map((c: any) => c.text).join(\\\"\\\") ?? \\\"\\\"\\n headings.push({ level, text, id })\\n }\\n })\\n }\\n\\n const getPaddingLeft = (level: number = 1) => {\\n return (level - 1) * 16 + 4\\n }\\n\\n const scrollToHeading = (id: string) => {\\n const el = document.getElementById(id)\\n if (el) el.scrollIntoView({ behavior: \\\"smooth\\\", block: \\\"start\\\" })\\n }\\n\\n return (\\n <VStack align=\\\"stretch\\\" gap={0} h=\\\"full\\\">\\n <HStack\\n justify=\\\"space-between\\\"\\n align=\\\"center\\\"\\n p={4}\\n borderBottom=\\\"1px solid\\\"\\n borderColor=\\\"border\\\"\\n flexShrink={0}\\n >\\n <Text fontWeight=\\\"medium\\\" fontSize=\\\"sm\\\">\\n Document outline\\n </Text>\\n <IconButton variant=\\\"ghost\\\" size=\\\"xs\\\" aria-label=\\\"Options\\\">\\n <Icon as={LuChevronDown} />\\n </IconButton>\\n </HStack>\\n\\n <VStack align=\\\"stretch\\\" gap={1} p={2} overflowY=\\\"auto\\\" flex=\\\"1\\\">\\n {headings.length === 0 ? (\\n <Text fontSize=\\\"sm\\\" textAlign=\\\"center\\\" p={4}>\\n Headings you add to the document will appear here\\n </Text>\\n ) : (\\n headings.map((h) => (\\n <Button\\n key={h.id}\\n variant=\\\"ghost\\\"\\n size=\\\"sm\\\"\\n pl={`${getPaddingLeft(h.level)}px`}\\n py={2}\\n onClick={() => scrollToHeading(h.id)}\\n lineClamp=\\\"1\\\"\\n truncate\\n >\\n <Icon\\n as={LuFileText}\\n mr={2}\\n flexShrink={0}\\n color=\\\"gray.500\\\"\\n boxSize={4}\\n />\\n {h.text}\\n </Button>\\n ))\\n )}\\n </VStack>\\n </VStack>\\n )\\n}\\n\\nconst LinkControl = forwardRef<\\n HTMLButtonElement,\\n Omit<Control.ButtonControlProps, \\\"icon\\\" | \\\"label\\\">\\n>(function LinkControl(props, ref) {\\n const { editor } = useRichTextEditorContext()\\n const [open, setOpen] = useState(false)\\n const [url, setUrl] = useState(\\\"\\\")\\n const [external, setExternal] = useState(false)\\n const [position, setPosition] = useState<{\\n top: number\\n left: number\\n } | null>(null)\\n\\n const triggerId = useId()\\n\\n if (!editor) return null\\n\\n const handleOpen = () => {\\n const markAttrs = editor.getAttributes(\\\"link\\\")\\n setUrl(markAttrs.href ?? \\\"\\\")\\n setExternal(markAttrs.target === \\\"_blank\\\")\\n\\n // Get cursor position\\n const { from } = editor.state.selection\\n const coords = editor.view.coordsAtPos(from)\\n\\n console.log(\\\"coords\\\", coords)\\n setPosition({\\n top: coords.bottom,\\n left: coords.left,\\n })\\n\\n setOpen(true)\\n }\\n\\n const handleApply = () => {\\n const trimmed = url.trim()\\n if (!trimmed) {\\n editor.chain().focus().unsetLink().run()\\n setOpen(false)\\n return\\n }\\n\\n const isValid = /^https?:\\\\/\\\\//i.test(trimmed)\\n const finalUrl = isValid ? trimmed : `https://${trimmed}`\\n\\n editor\\n .chain()\\n .focus()\\n .extendMarkRange(\\\"link\\\")\\n .setLink({ href: finalUrl, ...(external ? { target: \\\"_blank\\\" } : {}) })\\n .run()\\n\\n setOpen(false)\\n }\\n\\n const positioning = position\\n ? {\\n strategy: \\\"fixed\\\" as const,\\n placement: \\\"bottom-start\\\" as const,\\n gutter: 8,\\n getAnchorRect: () => ({\\n x: position.left,\\n y: position.top,\\n height: 0,\\n }),\\n }\\n : undefined\\n\\n return (\\n <PopoverRoot\\n open={open}\\n onOpenChange={(e) => setOpen(e.open)}\\n positioning={positioning}\\n ids={{ trigger: triggerId }}\\n >\\n <Tooltip content=\\\"Insert Link\\\" ids={{ trigger: triggerId }}>\\n <PopoverTrigger asChild>\\n <IconButton\\n ref={ref}\\n size=\\\"2xs\\\"\\n aria-label=\\\"Insert Link\\\"\\n onClick={handleOpen}\\n variant={editor.isActive(\\\"link\\\") ? \\\"subtle\\\" : \\\"ghost\\\"}\\n {...props}\\n >\\n <LuLink />\\n </IconButton>\\n </PopoverTrigger>\\n </Tooltip>\\n <Portal>\\n <PopoverContent p=\\\"3\\\" minW=\\\"280px\\\">\\n <PopoverBody>\\n <Text fontWeight=\\\"medium\\\" mb=\\\"2\\\">\\n Insert Link\\n </Text>\\n <Input\\n placeholder=\\\"Enter URL\\\"\\n value={url}\\n onChange={(e) => setUrl(e.target.value)}\\n size=\\\"sm\\\"\\n mb=\\\"3\\\"\\n onKeyDown={(e) => {\\n if (e.key === \\\"Enter\\\") handleApply()\\n }}\\n autoFocus\\n />\\n <HStack mb=\\\"4\\\" align=\\\"center\\\">\\n <Switch.Root\\n checked={external}\\n onCheckedChange={(e) => setExternal(e.checked)}\\n size=\\\"sm\\\"\\n >\\n <Switch.HiddenInput />\\n <Switch.Control>\\n <Switch.Thumb />\\n </Switch.Control>\\n <Switch.Label>Open in new tab</Switch.Label>\\n </Switch.Root>\\n </HStack>\\n <HStack justify=\\\"flex-end\\\" gap=\\\"2\\\">\\n <Button size=\\\"sm\\\" variant=\\\"ghost\\\" onClick={() => setOpen(false)}>\\n Cancel\\n </Button>\\n <Button size=\\\"sm\\\" onClick={handleApply}>\\n Apply\\n </Button>\\n </HStack>\\n </PopoverBody>\\n </PopoverContent>\\n </Portal>\\n </PopoverRoot>\\n )\\n})\\n\\nfunction InsertImageControl() {\\n const { editor } = useRichTextEditorContext()\\n const [open, setOpen] = useState(false)\\n const [files, setFiles] = useState<File[]>([])\\n\\n if (!editor) return null\\n\\n return (\\n <>\\n <Control.ButtonControl\\n icon={<LuImage />}\\n label=\\\"Insert Image\\\"\\n onClick={() => setOpen(true)}\\n variant=\\\"ghost\\\"\\n />\\n\\n <Dialog.Root open={open} onOpenChange={(e) => setOpen(e.open)}>\\n <Dialog.Trigger asChild />\\n <Portal>\\n <Dialog.Backdrop />\\n <Dialog.Positioner>\\n <Dialog.Content maxW=\\\"lg\\\">\\n <Dialog.Header>\\n <Dialog.Title>Insert Image</Dialog.Title>\\n </Dialog.Header>\\n\\n <Dialog.Body>\\n <Tabs.Root defaultValue=\\\"url\\\">\\n <Tabs.List>\\n <Tabs.Trigger value=\\\"url\\\">\\n <LuLink /> Embed URL\\n </Tabs.Trigger>\\n <Tabs.Trigger value=\\\"upload\\\">\\n <LuUpload /> Upload File\\n </Tabs.Trigger>\\n </Tabs.List>\\n\\n <Tabs.Content value=\\\"url\\\">\\n <Box display=\\\"flex\\\" gap=\\\"2\\\" mt=\\\"4\\\">\\n <Input\\n placeholder=\\\"Enter image URL\\\"\\n id=\\\"image-url-input\\\"\\n />\\n <Button\\n onClick={() => {\\n const url = (\\n document.getElementById(\\n \\\"image-url-input\\\",\\n ) as HTMLInputElement\\n ).value\\n if (url) {\\n editor.chain().focus().setImage({ src: url }).run()\\n setOpen(false)\\n }\\n }}\\n >\\n Insert\\n </Button>\\n </Box>\\n </Tabs.Content>\\n\\n <Tabs.Content value=\\\"upload\\\">\\n <FileUpload.Root\\n maxW=\\\"xl\\\"\\n alignItems=\\\"stretch\\\"\\n maxFiles={1}\\n accept=\\\"image/*\\\"\\n onFileAccept={(accepted) => {\\n const uploaded = accepted.files ?? []\\n setFiles(uploaded)\\n\\n if (uploaded[0]) {\\n const url = URL.createObjectURL(uploaded[0])\\n editor.chain().focus().setImage({ src: url }).run()\\n setOpen(false)\\n }\\n }}\\n >\\n <FileUpload.HiddenInput />\\n <FileUpload.Dropzone>\\n <Icon size=\\\"md\\\" color=\\\"fg.muted\\\">\\n <LuUpload />\\n </Icon>\\n <FileUpload.DropzoneContent>\\n <Box>Drag and drop a file here</Box>\\n <Box color=\\\"fg.muted\\\">.png, .jpg up to 5MB</Box>\\n </FileUpload.DropzoneContent>\\n </FileUpload.Dropzone>\\n\\n <FileUpload.List files={files} />\\n </FileUpload.Root>\\n </Tabs.Content>\\n </Tabs.Root>\\n </Dialog.Body>\\n\\n <Dialog.Footer mt=\\\"4\\\">\\n <Button variant=\\\"outline\\\" onClick={() => setOpen(false)}>\\n Cancel\\n </Button>\\n </Dialog.Footer>\\n </Dialog.Content>\\n </Dialog.Positioner>\\n </Portal>\\n </Dialog.Root>\\n </>\\n )\\n}\\n\\nfunction slugify(text: string) {\\n return text\\n .toLowerCase()\\n .trim()\\n .replace(/[^a-z0-9]+/g, \\\"-\\\") // replace non-alphanumeric with dash\\n .replace(/^-+|-+$/g, \\\"\\\") // remove leading/trailing dashes\\n}\\n\\nconst HeadingWithSlug = Heading.extend({\\n addAttributes() {\\n return {\\n ...this.parent?.(),\\n id: {\\n default: null,\\n parseHTML: (element) => element.getAttribute(\\\"id\\\"),\\n renderHTML: (attributes) => ({\\n id: attributes.id,\\n }),\\n },\\n }\\n },\\n addProseMirrorPlugins() {\\n return [\\n new Plugin({\\n appendTransaction: (_transactions, _oldState, newState) => {\\n const tr = newState.tr\\n let modified = false\\n\\n newState.doc.descendants((node, pos) => {\\n if (node.type.name === \\\"heading\\\") {\\n const text = node.textContent\\n const slug = slugify(text)\\n if (node.attrs.id !== slug) {\\n tr.setNodeMarkup(pos, undefined, {\\n ...node.attrs,\\n id: slug,\\n })\\n modified = true\\n }\\n }\\n })\\n\\n return modified ? tr : null\\n },\\n }),\\n ]\\n },\\n})\\n\\nconst editorContent = `\\n <h1 id=\\\"heading-0\\\">Legend Of X: The Complete Saga</h1>\\n <p>In a world where technology and humanity collide, the fate of civilization hangs in the balance. This is the story of those who dared to question everything they knew.</p>\\n\\n <h2 id=\\\"heading-1\\\">Chapter 1: Awakening</h2>\\n <p>The city of <a href=\\\"https://example.com\\\">Neo-Tokyo</a> stretched endlessly beneath the artificial sky. Maya Tanaka stood at the edge of the observation deck, watching the streams of data flow through the neural network that powered the megacity. She had always believed the System was infallible, that the Architects who built it had created a perfect world.</p>\\n <p>But something was wrong. The anomalies in the code were becoming more frequent, more deliberate. Someone—or something—was trying to break through.</p>\\n\\n <h3 id=\\\"heading-2\\\">Part 1: The First Glitch</h3>\\n <p>It started with small things. A flicker in the holographic displays. A delay in the transportation grid. Messages that appeared and disappeared before anyone could read them. Maya had noticed these irregularities for weeks, but she was afraid to report them. In Neo-Tokyo, questioning the System was considered treason.</p>\\n <p>One evening, as she worked late in the Neural Operations Center, the main screen went black. Then, slowly, text began to appear: \\\"They are watching. They have always been watching. Find the Archive before it's too late.\\\"</p>\\n <h3 id=\\\"heading-3\\\">Part 2: The Underground</h3>\\n <p>The next day, Maya received an encrypted message directing her to an abandoned sector of the city. She knew it was dangerous, but curiosity overwhelmed her caution. The meeting place was a decrepit building, its walls covered in graffiti that depicted symbols she didn't recognize.</p>\\n <p>Inside, she found a group of people huddled around old terminals. They called themselves the Disconnected—those who had rejected the neural implants that connected everyone to the System. Their leader, a man named Kenzo, explained that the glitches were intentional.</p>\\n <p>\\\"We're trying to wake people up,\\\" he said. \\\"The System isn't what you think it is. The Architects didn't save humanity—they enslaved it.\\\"</p>\\n <h2 id=\\\"heading-4\\\">Chapter 2: The Archive</h2>\\n <p>Maya's decision to join the Disconnected changed everything. Kenzo taught her how to navigate the hidden layers of the System, the forgotten protocols and backdoors that the Architects thought they had sealed. Together, they began their search for the Archive.</p>\\n <p>The journey took them through the darkest corners of Neo-Tokyo. They encountered other groups of rebels, each with their own theories about what the Archive contained. Some believed it held the key to shutting down the System entirely. Others thought it was a weapon that could be used to take control.</p>\\n <h3 id=\\\"heading-5\\\">Part 3: Revelations</h3>\\n <p>After months of searching, they found it. The Archive wasn't a physical location—it was a fragment of code hidden in the deepest layer of the System, protected by encryption so complex that even the Architects had lost access to it.</p>\\n <p>When Maya finally broke through the encryption, what she found shocked her. The Archive contained memories—thousands of them, uploaded from the minds of people who had lived before the Great Collapse. They revealed a truth that the Architects had hidden: the Collapse had been engineered.</p>\\n <p>The Architects had created the disaster that destroyed the old world so they could rebuild it in their image. And now, they were planning to do it again.</p>\\n <h2 id=\\\"heading-6\\\">Chapter 3: Resistance</h2>\\n <p>Armed with the truth, Maya and the Disconnected began spreading the Archive's contents throughout the city. The response was immediate. Some people refused to believe it, clinging to their faith in the System. Others joined the resistance, ready to fight for their freedom.</p>\\n <p>The Architects responded with force. Security drones filled the streets, hunting down anyone suspected of accessing the Archive. The city descended into chaos as the battle between the Disconnected and the System's defenders intensified.</p>\\n <h3 id=\\\"heading-7\\\">Part 4: The Final Stand</h3>\\n <p>Maya knew they couldn't win through violence alone. The System was too powerful, too entrenched. Instead, she devised a plan to use the Archive itself as a weapon. If they could upload its contents directly into the neural network, everyone connected to the System would see the truth simultaneously.</p>\\n <p>The operation was risky. It required infiltrating the Central Node, the heart of the System's infrastructure. Many of the Disconnected would have to sacrifice themselves to create a distraction. But it was their only chance.</p>\\n <p>As Maya stood before the Central Node's interface, her fingers trembling over the controls, she thought about all the lives that had been lost, all the lies that had been told. With one final command, she initiated the upload.</p>\\n <h2 id=\\\"heading-8\\\">Epilogue: A New Beginning</h2>\\n <p>The System didn't collapse overnight. But once people knew the truth, they began to question, to resist, to rebuild. Maya watched from a rooftop as the artificial sky flickered and went dark for the first time in decades, revealing the stars above.</p>\\n <p>The world would never be perfect. But it would be real. And that, she thought, was worth fighting for.</p>\\n `\\n\\nconst menuItems = [\\n {\\n label: \\\"File\\\",\\n items: [\\n { label: \\\"New\\\", icon: <LuPlus /> },\\n { label: \\\"Open\\\", icon: <LuFolder /> },\\n { label: \\\"Make a copy\\\", icon: <LuCopy /> },\\n { label: \\\"Download\\\", icon: <LuDownload /> },\\n ],\\n },\\n {\\n label: \\\"Edit\\\",\\n items: [\\n { label: \\\"Undo\\\", icon: <LuArrowRight /> },\\n { label: \\\"Redo\\\", icon: <LuArrowRight /> },\\n { label: \\\"Cut\\\", icon: <LuSettings /> },\\n { label: \\\"Copy\\\", icon: <LuCopy /> },\\n { label: \\\"Paste\\\", icon: <LuArrowRight /> },\\n ],\\n },\\n {\\n label: \\\"View\\\",\\n items: [\\n { label: \\\"Zoom in\\\", icon: <LuArrowRight /> },\\n { label: \\\"Zoom out\\\", icon: <LuArrowRight /> },\\n { label: \\\"Full screen\\\", icon: <LuSettings /> },\\n ],\\n },\\n {\\n label: \\\"Insert\\\",\\n items: [\\n { label: \\\"Image\\\", icon: <LuPlus /> },\\n { label: \\\"Table\\\", icon: <LuSettings /> },\\n { label: \\\"Drawing\\\", icon: <LuFolder /> },\\n ],\\n },\\n {\\n label: \\\"Format\\\",\\n items: [\\n { label: \\\"Bold\\\", icon: <LuSettings /> },\\n { label: \\\"Italic\\\", icon: <LuSettings /> },\\n { label: \\\"Underline\\\", icon: <LuSettings /> },\\n ],\\n },\\n {\\n label: \\\"Tools\\\",\\n items: [\\n { label: \\\"Spelling\\\", icon: <LuSettings /> },\\n { label: \\\"Word count\\\", icon: <LuSettings /> },\\n ],\\n },\\n {\\n label: \\\"Extensions\\\",\\n items: [\\n { label: \\\"Add-ons\\\", icon: <LuSettings /> },\\n { label: \\\"Apps Script\\\", icon: <LuSettings /> },\\n ],\\n },\\n {\\n label: \\\"Help\\\",\\n items: [\\n { label: \\\"Docs Help\\\", icon: <LuCircleHelp /> },\\n { label: \\\"Keyboard shortcuts\\\", icon: <LuSettings /> },\\n ],\\n },\\n]\\n\",\n \"hasSnippet\": true,\n \"importPaths\": [\n \"import {\\n Box,\\n Button,\\n Dialog,\\n FileUpload,\\n Flex,\\n HStack,\\n Icon,\\n IconButton,\\n Input,\\n Portal,\\n Switch,\\n Tabs,\\n Text,\\n VStack,\\n} from \\\"@chakra-ui/react\\\"\",\n \"import Color from \\\"@tiptap/extension-color\\\"\",\n \"import Heading from \\\"@tiptap/extension-heading\\\"\",\n \"import Highlight from \\\"@tiptap/extension-highlight\\\"\",\n \"import Image from \\\"@tiptap/extension-image\\\"\",\n \"import Link from \\\"@tiptap/extension-link\\\"\",\n \"import Subscript from \\\"@tiptap/extension-subscript\\\"\",\n \"import Superscript from \\\"@tiptap/extension-superscript\\\"\",\n \"import TaskItem from \\\"@tiptap/extension-task-item\\\"\",\n \"import TaskList from \\\"@tiptap/extension-task-list\\\"\",\n \"import TextAlign from \\\"@tiptap/extension-text-align\\\"\",\n \"import { TextStyleKit } from \\\"@tiptap/extension-text-style\\\"\",\n \"import { Plugin } from \\\"@tiptap/pm/state\\\"\",\n \"import { Editor, useEditor } from \\\"@tiptap/react\\\"\",\n \"import StarterKit from \\\"@tiptap/starter-kit\\\"\",\n \"import { Avatar } from \\\"@/components/ui/avatar\\\"\",\n \"import {\\n MenuContent,\\n MenuItem,\\n MenuRoot,\\n MenuTrigger,\\n} from \\\"@/components/ui/menu\\\"\",\n \"import {\\n PopoverBody,\\n PopoverContent,\\n PopoverRoot,\\n PopoverTrigger,\\n} from \\\"@/components/ui/popover\\\"\",\n \"import {\\n Control,\\n RichTextEditor,\\n useRichTextEditorContext,\\n} from \\\"@/components/ui/rich-text-editor\\\"\",\n \"import { Tooltip } from \\\"@/components/ui/tooltip\\\"\",\n \"import { forwardRef, useEffect, useId, useState } from \\\"react\\\"\",\n \"import {\\n LuChevronDown,\\n LuCircleHelp,\\n LuFileText,\\n LuLock,\\n LuMessageSquare,\\n LuSearch,\\n LuStar,\\n LuUpload,\\n LuVideo,\\n} from \\\"react-icons/lu\\\"\",\n \"import { LuImage, LuLink } from \\\"react-icons/lu\\\"\",\n \"import {\\n LuArrowRight,\\n LuCopy,\\n LuDownload,\\n LuFolder,\\n LuPlus,\\n LuSettings,\\n} from \\\"react-icons/lu\\\"\"\n ],\n \"importPath\": \"import {\\n Control,\\n RichTextEditor,\\n useRichTextEditorContext,\\n} from \\\"@/components/ui/rich-text-editor\\\"\",\n \"npmDependencies\": [\n \"@tiptap/extension-color\",\n \"@tiptap/extension-heading\",\n \"@tiptap/extension-highlight\",\n \"@tiptap/extension-image\",\n \"@tiptap/extension-link\",\n \"@tiptap/extension-subscript\",\n \"@tiptap/extension-superscript\",\n \"@tiptap/extension-task-item\",\n \"@tiptap/extension-task-list\",\n \"@tiptap/extension-text-align\",\n \"@tiptap/extension-text-style\",\n \"@tiptap/pm\",\n \"@tiptap/react\",\n \"@tiptap/starter-kit\",\n \"react-icons\"\n ],\n \"fileDependencies\": [\n \"compositions/ui/avatar\",\n \"compositions/ui/menu\",\n \"compositions/ui/popover\",\n \"compositions/ui/rich-text-editor\",\n \"compositions/ui/tooltip\"\n ]\n },\n {\n \"name\": \"rich-text-editor-controlled\",\n \"content\": \"\\\"use client\\\"\\nexport const RichTextEditorControlled = () => {\\n const [content, setContent] = useState<string>(\\\"<p>Edit here...</p>\\\")\\n\\n const editor = useEditor({\\n shouldRerenderOnTransaction: true,\\n immediatelyRender: false,\\n extensions: [\\n StarterKit.configure({ link: { openOnClick: false } }),\\n Subscript,\\n Superscript,\\n TextAlign.configure({ types: [\\\"paragraph\\\", \\\"heading\\\"] }),\\n ],\\n content,\\n onUpdate({ editor }) {\\n setContent(editor.getHTML())\\n },\\n })\\n\\n if (!editor) return null\\n\\n return (\\n <Stack maxW=\\\"3xl\\\">\\n <RichTextEditor.Root editor={editor} maxHeight=\\\"2xl\\\">\\n <RichTextEditor.Toolbar>\\n <RichTextEditor.ControlGroup>\\n <Control.Bold />\\n <Control.Italic />\\n <Control.Underline />\\n <Control.Strikethrough />\\n <Control.Code />\\n </RichTextEditor.ControlGroup>\\n </RichTextEditor.Toolbar>\\n\\n <RichTextEditor.Content />\\n </RichTextEditor.Root>\\n\\n <Box p=\\\"4\\\" bg=\\\"bg.muted\\\" flex=\\\"1\\\">\\n <Box\\n as=\\\"pre\\\"\\n textStyle=\\\"sm\\\"\\n wordWrap=\\\"break-word\\\"\\n whiteSpace=\\\"pre-wrap\\\"\\n >\\n {content}\\n </Box>\\n </Box>\\n </Stack>\\n )\\n}\\n\",\n \"hasSnippet\": true,\n \"importPaths\": [\n \"import { Box, Stack } from \\\"@chakra-ui/react\\\"\",\n \"import Subscript from \\\"@tiptap/extension-subscript\\\"\",\n \"import Superscript from \\\"@tiptap/extension-superscript\\\"\",\n \"import TextAlign from \\\"@tiptap/extension-text-align\\\"\",\n \"import { useEditor } from \\\"@tiptap/react\\\"\",\n \"import StarterKit from \\\"@tiptap/starter-kit\\\"\",\n \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"import { useState } from \\\"react\\\"\"\n ],\n \"importPath\": \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"npmDependencies\": [\n \"@tiptap/extension-subscript\",\n \"@tiptap/extension-superscript\",\n \"@tiptap/extension-text-align\",\n \"@tiptap/react\",\n \"@tiptap/starter-kit\"\n ],\n \"fileDependencies\": [\n \"compositions/ui/rich-text-editor\"\n ]\n },\n {\n \"name\": \"rich-text-editor-with-autosave\",\n \"content\": \"\\\"use client\\\"\\ntype SaveStatus = \\\"idle\\\" | \\\"saving\\\" | \\\"saved\\\" | \\\"error\\\"\\n\\nexport const RichTextEditorWithAutosave = () => {\\n const [saveStatus, setSaveStatus] = useState<SaveStatus>(\\\"idle\\\")\\n const [lastSaved, setLastSaved] = useState<Date | null>(null)\\n const saveTimeoutRef = useRef<NodeJS.Timeout | null>(null)\\n\\n // Simulate saving to server\\n const saveContent = useCallback(async (content: string) => {\\n setSaveStatus(\\\"saving\\\")\\n\\n // Simulate API call\\n await new Promise((resolve) => setTimeout(resolve, 800))\\n\\n // Save to localStorage as demo\\n localStorage.setItem(\\\"autosave-content\\\", content)\\n\\n setSaveStatus(\\\"saved\\\")\\n setLastSaved(new Date())\\n\\n // Reset status after 2 seconds\\n setTimeout(() => setSaveStatus(\\\"idle\\\"), 2000)\\n }, [])\\n\\n // Load saved content on mount\\n const getSavedContent = () => {\\n if (typeof window === \\\"undefined\\\") return null\\n return localStorage.getItem(\\\"autosave-content\\\")\\n }\\n\\n const editor = useEditor({\\n extensions: [StarterKit],\\n content:\\n getSavedContent() ||\\n `<p>Start typing... your content will be automatically saved.</p>\\n <p>Try making some changes and watch the save indicator.</p>`,\\n shouldRerenderOnTransaction: true,\\n immediatelyRender: false,\\n onUpdate: ({ editor }) => {\\n // Debounce autosave\\n if (saveTimeoutRef.current) {\\n clearTimeout(saveTimeoutRef.current)\\n }\\n\\n saveTimeoutRef.current = setTimeout(() => {\\n saveContent(editor.getHTML())\\n }, 1000) // Save after 1 second of inactivity\\n },\\n })\\n\\n // Cleanup timeout on unmount\\n useEffect(() => {\\n return () => {\\n if (saveTimeoutRef.current) {\\n clearTimeout(saveTimeoutRef.current)\\n }\\n }\\n }, [])\\n\\n if (!editor) return null\\n\\n const formatLastSaved = (date: Date) => {\\n const now = new Date()\\n const diff = now.getTime() - date.getTime()\\n const seconds = Math.floor(diff / 1000)\\n\\n if (seconds < 60) return \\\"just now\\\"\\n if (seconds < 120) return \\\"1 minute ago\\\"\\n return `${Math.floor(seconds / 60)} minutes ago`\\n }\\n\\n return (\\n <Box>\\n <RichTextEditor.Root editor={editor} borderWidth=\\\"1px\\\" rounded=\\\"l2\\\">\\n <RichTextEditor.Toolbar>\\n <RichTextEditor.ControlGroup>\\n <Control.Bold />\\n <Control.Italic />\\n <Control.Underline />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.BulletList />\\n <Control.OrderedList />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.Undo />\\n <Control.Redo />\\n </RichTextEditor.ControlGroup>\\n\\n <HStack flex=\\\"1\\\" justify=\\\"flex-end\\\" gap=\\\"2\\\">\\n <Badge\\n variant=\\\"subtle\\\"\\n colorPalette={\\n saveStatus === \\\"saving\\\"\\n ? \\\"yellow\\\"\\n : saveStatus === \\\"saved\\\"\\n ? \\\"green\\\"\\n : \\\"gray\\\"\\n }\\n >\\n <HStack gap=\\\"1\\\">\\n {saveStatus === \\\"saving\\\" && (\\n <LuLoader className=\\\"animate-spin\\\" />\\n )}\\n {saveStatus === \\\"saved\\\" && <LuCheck />}\\n {saveStatus === \\\"idle\\\" && <LuCloud />}\\n <Text>\\n {saveStatus === \\\"saving\\\" && \\\"Saving...\\\"}\\n {saveStatus === \\\"saved\\\" && \\\"Saved\\\"}\\n {saveStatus === \\\"idle\\\" &&\\n (lastSaved\\n ? `Saved ${formatLastSaved(lastSaved)}`\\n : \\\"Draft\\\")}\\n </Text>\\n </HStack>\\n </Badge>\\n </HStack>\\n </RichTextEditor.Toolbar>\\n\\n <RichTextEditor.Content />\\n </RichTextEditor.Root>\\n </Box>\\n )\\n}\\n\",\n \"hasSnippet\": true,\n \"importPaths\": [\n \"import { Badge, Box, HStack, Text } from \\\"@chakra-ui/react\\\"\",\n \"import { useEditor } from \\\"@tiptap/react\\\"\",\n \"import StarterKit from \\\"@tiptap/starter-kit\\\"\",\n \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"import { useCallback, useEffect, useRef, useState } from \\\"react\\\"\",\n \"import { LuCheck, LuCloud, LuLoader } from \\\"react-icons/lu\\\"\"\n ],\n \"importPath\": \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"npmDependencies\": [\n \"@tiptap/react\",\n \"@tiptap/starter-kit\",\n \"react-icons\"\n ],\n \"fileDependencies\": [\n \"compositions/ui/rich-text-editor\"\n ]\n },\n {\n \"name\": \"rich-text-editor-with-bubble-menu\",\n \"content\": \"\\\"use client\\\"\\nexport const RichTextEditorWithBubbleMenu = () => {\\n const editor = useEditor({\\n extensions: [StarterKit],\\n content: sampleContent,\\n shouldRerenderOnTransaction: true,\\n immediatelyRender: false,\\n })\\n\\n if (!editor) return null\\n\\n return (\\n <RichTextEditor.Root editor={editor} borderWidth=\\\"1px\\\" rounded=\\\"lg\\\">\\n {editor && (\\n <BubbleMenu editor={editor}>\\n <RichTextEditor.Toolbar variant=\\\"floating\\\">\\n <RichTextEditor.ControlGroup>\\n <Control.Bold />\\n <Control.Italic />\\n <Control.Underline />\\n <Control.Strikethrough />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.Hr />\\n <Control.Code />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.BulletList />\\n <Control.OrderedList />\\n </RichTextEditor.ControlGroup>\\n </RichTextEditor.Toolbar>\\n </BubbleMenu>\\n )}\\n <RichTextEditor.Content />\\n </RichTextEditor.Root>\\n )\\n}\\n\\nconst sampleContent = `\\n <h2>Select some text in this paragraph to see the bubble menu!</h2>\\n <p>The <strong>Bold</strong>, <em>Italic</em>, <u>Underline</u>, and <strike>Strikethrough</strike> controls will appear. You can also change the block type here.</p>\\n <p>\\n Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\\n Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\\n Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\\n </p>\\n <ul>\\n <li>Try selecting text within this list item.</li>\\n <li>Use the list buttons to switch between bullet and ordered lists.</li>\\n </ul>\\n`\\n\",\n \"hasSnippet\": true,\n \"importPaths\": [\n \"import { useEditor } from \\\"@tiptap/react\\\"\",\n \"import { BubbleMenu } from \\\"@tiptap/react/menus\\\"\",\n \"import StarterKit from \\\"@tiptap/starter-kit\\\"\",\n \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\"\n ],\n \"importPath\": \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"npmDependencies\": [\n \"@tiptap/react\",\n \"@tiptap/starter-kit\"\n ],\n \"fileDependencies\": [\n \"compositions/ui/rich-text-editor\"\n ]\n },\n {\n \"name\": \"rich-text-editor-with-character-count\",\n \"content\": \"\\\"use client\\\"\\nexport const RichTextEditorWithCharacterCount = () => {\\n const editor = useEditor({\\n content: `\\n <h2>Dr. Stone</h2>\\n <p><strong>Dr. Stone</strong> is a Japanese manga and anime series that follows the story of Senku Ishigami, a scientific genius who awakens thousands of years after humanity has been petrified.</p>\\n <p>The world is in ruins, and Senku aims to rebuild civilization using the power of science.</p>\\n `,\\n extensions: [\\n StarterKit,\\n Image,\\n CharacterCount.configure({\\n limit: 1000,\\n mode: \\\"textSize\\\",\\n }),\\n ],\\n shouldRerenderOnTransaction: true,\\n immediatelyRender: false,\\n })\\n\\n if (!editor) return null\\n\\n const charCount = editor.storage.characterCount.characters()\\n const wordCount = editor.storage.characterCount.words()\\n\\n return (\\n <RichTextEditor.Root editor={editor}>\\n <RichTextEditor.Toolbar>\\n <RichTextEditor.ControlGroup>\\n <Control.Bold />\\n <Control.Italic />\\n <Control.Strikethrough />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.BulletList />\\n <Control.OrderedList />\\n </RichTextEditor.ControlGroup>\\n </RichTextEditor.Toolbar>\\n\\n <RichTextEditor.Content />\\n\\n <RichTextEditor.Footer justify=\\\"flex-end\\\" textStyle=\\\"xs\\\">\\n <Box fontVariantNumeric=\\\"tabular-nums\\\">Characters: {charCount}</Box>\\n <Box fontVariantNumeric=\\\"tabular-nums\\\">Words: {wordCount}</Box>\\n </RichTextEditor.Footer>\\n </RichTextEditor.Root>\\n )\\n}\\n\",\n \"hasSnippet\": true,\n \"importPaths\": [\n \"import { Box } from \\\"@chakra-ui/react\\\"\",\n \"import Image from \\\"@tiptap/extension-image\\\"\",\n \"import { CharacterCount } from \\\"@tiptap/extensions/character-count\\\"\",\n \"import { useEditor } from \\\"@tiptap/react\\\"\",\n \"import StarterKit from \\\"@tiptap/starter-kit\\\"\",\n \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\"\n ],\n \"importPath\": \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"npmDependencies\": [\n \"@tiptap/extension-image\",\n \"@tiptap/extensions\",\n \"@tiptap/react\",\n \"@tiptap/starter-kit\"\n ],\n \"fileDependencies\": [\n \"compositions/ui/rich-text-editor\"\n ]\n },\n {\n \"name\": \"rich-text-editor-with-code\",\n \"content\": \"\\\"use client\\\"\\nconst lowlight = createLowlight(all)\\nlowlight.register(\\\"html\\\", html)\\nlowlight.register(\\\"css\\\", css)\\nlowlight.register(\\\"js\\\", js)\\nlowlight.register(\\\"ts\\\", ts)\\n\\nexport const RichTextEditorWithCode = () => {\\n const editor = useEditor({\\n extensions: [StarterKit, CodeBlockLowlight.configure({ lowlight })],\\n content: `<p>That’s a boring paragraph followed by a fenced code block:</p>\\n<pre><code class=\\\"language-javascript\\\">${code}</code></pre>\\n<p>Press Command/Ctrl + Enter to leave the fenced code block and continue typing in boring paragraphs.</p>`,\\n shouldRerenderOnTransaction: true,\\n immediatelyRender: false,\\n })\\n\\n if (!editor) return null\\n\\n return (\\n <RichTextEditor.Root\\n editor={editor}\\n border=\\\"1px solid\\\"\\n borderColor=\\\"border\\\"\\n rounded=\\\"md\\\"\\n >\\n <HStack gap=\\\"2\\\" p=\\\"2\\\" borderBottom=\\\"1px solid\\\" borderColor=\\\"border\\\">\\n <RichTextEditor.ControlGroup>\\n <Control.Bold />\\n <Control.Italic />\\n <Control.Code />\\n </RichTextEditor.ControlGroup>\\n </HStack>\\n <RichTextEditor.Content />\\n </RichTextEditor.Root>\\n )\\n}\\n\\n// Escape HTML so it can be safely injected\\nfunction escapeHtml(unsafe: string) {\\n return unsafe\\n .replace(/&/g, \\\"&amp;\\\")\\n .replace(/</g, \\\"&lt;\\\")\\n .replace(/>/g, \\\"&gt;\\\")\\n .replace(/\\\"/g, \\\"&quot;\\\")\\n .replace(/'/g, \\\"&#039;\\\")\\n}\\n\\nconst code = escapeHtml(`\\nasync function fetchTodos() {\\n const response = await fetch('https://jsonplaceholder.typicode.com/todos?_limit=5')\\n const data = await response.json()\\n return data\\n}\\n\\nasync function showTodos() {\\n const todos = await fetchTodos()\\n todos.forEach(todo => console.log(\\\\`\\\\${todo.id}: \\\\${todo.title} [\\\\${todo.completed ? '✅' : '❌'}]\\\\`))\\n}\\n\\nshowTodos()\\n`)\\n\",\n \"hasSnippet\": true,\n \"importPaths\": [\n \"import { HStack } from \\\"@chakra-ui/react\\\"\",\n \"import CodeBlockLowlight from \\\"@tiptap/extension-code-block-lowlight\\\"\",\n \"import { useEditor } from \\\"@tiptap/react\\\"\",\n \"import StarterKit from \\\"@tiptap/starter-kit\\\"\",\n \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"import css from \\\"highlight.js/lib/languages/css\\\"\",\n \"import js from \\\"highlight.js/lib/languages/javascript\\\"\",\n \"import ts from \\\"highlight.js/lib/languages/typescript\\\"\",\n \"import html from \\\"highlight.js/lib/languages/xml\\\"\",\n \"import { all, createLowlight } from \\\"lowlight\\\"\"\n ],\n \"importPath\": \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"npmDependencies\": [\n \"@tiptap/extension-code-block-lowlight\",\n \"@tiptap/react\",\n \"@tiptap/starter-kit\",\n \"highlight.js\",\n \"lowlight\"\n ],\n \"fileDependencies\": [\n \"compositions/ui/rich-text-editor\"\n ]\n },\n {\n \"name\": \"rich-text-editor-with-drag-handle\",\n \"content\": \"\\\"use client\\\"\\nexport const RichTextEditorWithDragHandle = () => {\\n const { token } = useChakraContext()\\n\\n const editor = useEditor({\\n extensions: [\\n StarterKit.configure({\\n dropcursor: { width: 2, color: token(\\\"colors.teal.solid\\\") },\\n }),\\n ],\\n content: `\\n <p>Hover over any paragraph to see the drag handle appear on the left.</p>\\n <p>This is another paragraph. You can drag blocks to reorder them.</p>\\n <p>Try adding more content and rearranging it!</p>\\n <ul>\\n <li>List items can also be dragged</li>\\n <li>Each block has its own handle</li>\\n </ul>\\n <blockquote>Blockquotes work too!</blockquote>\\n `,\\n shouldRerenderOnTransaction: true,\\n immediatelyRender: false,\\n })\\n\\n if (!editor) return null\\n\\n return (\\n <RichTextEditor.Root editor={editor}>\\n <RichTextEditor.Toolbar>\\n <RichTextEditor.ControlGroup>\\n <Control.Bold />\\n <Control.Italic />\\n <Control.Underline />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.BulletList />\\n <Control.OrderedList />\\n <Control.Blockquote />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.Undo />\\n <Control.Redo />\\n </RichTextEditor.ControlGroup>\\n </RichTextEditor.Toolbar>\\n\\n <Box position=\\\"relative\\\">\\n <DragHandle editor={editor}>\\n <Box\\n pos=\\\"relative\\\"\\n top=\\\"-0.5\\\"\\n insetStart=\\\"-1\\\"\\n cursor=\\\"grab\\\"\\n color=\\\"fg.muted\\\"\\n opacity=\\\"0.6\\\"\\n _hover={{ opacity: 1, color: \\\"fg\\\" }}\\n _active={{ cursor: \\\"grabbing\\\" }}\\n >\\n <Icon asChild boxSize=\\\"4\\\">\\n <LuGripVertical />\\n </Icon>\\n </Box>\\n </DragHandle>\\n <RichTextEditor.Content />\\n </Box>\\n </RichTextEditor.Root>\\n )\\n}\\n\",\n \"hasSnippet\": true,\n \"importPaths\": [\n \"import { Box, Icon, useChakraContext } from \\\"@chakra-ui/react\\\"\",\n \"import { DragHandle } from \\\"@tiptap/extension-drag-handle-react\\\"\",\n \"import { useEditor } from \\\"@tiptap/react\\\"\",\n \"import StarterKit from \\\"@tiptap/starter-kit\\\"\",\n \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"import { LuGripVertical } from \\\"react-icons/lu\\\"\"\n ],\n \"importPath\": \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"npmDependencies\": [\n \"@tiptap/extension-drag-handle-react\",\n \"@tiptap/react\",\n \"@tiptap/starter-kit\",\n \"react-icons\"\n ],\n \"fileDependencies\": [\n \"compositions/ui/rich-text-editor\"\n ]\n },\n {\n \"name\": \"rich-text-editor-with-emoji\",\n \"content\": \"\\\"use client\\\"\\nexport const RichTextEditorWithEmoji = () => {\\n const editor = useEditor({\\n extensions: [\\n StarterKit,\\n Emoji.configure({\\n emojis,\\n enableEmoticons: true,\\n suggestion: createEmojiSuggestionConfig(emojis),\\n }),\\n ],\\n content: `<p>Type <strong>:</strong> to insert an emoji, like :smile: or :heart:</p><p>You can also use emoticons like :) or &lt;3</p>`,\\n shouldRerenderOnTransaction: true,\\n immediatelyRender: false,\\n })\\n\\n if (!editor) return null\\n\\n return (\\n <RichTextEditor.Root editor={editor} rounded=\\\"md\\\">\\n <RichTextEditor.Content />\\n </RichTextEditor.Root>\\n )\\n}\\n\",\n \"hasSnippet\": true,\n \"importPaths\": [\n \"import Emoji, { emojis } from \\\"@tiptap/extension-emoji\\\"\",\n \"import { useEditor } from \\\"@tiptap/react\\\"\",\n \"import StarterKit from \\\"@tiptap/starter-kit\\\"\",\n \"import { RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"import { createEmojiSuggestionConfig } from \\\"@/components/ui/rich-text-editor-menu\\\"\"\n ],\n \"importPath\": \"import { RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"npmDependencies\": [\n \"@tiptap/extension-emoji\",\n \"@tiptap/react\",\n \"@tiptap/starter-kit\"\n ],\n \"fileDependencies\": [\n \"compositions/ui/rich-text-editor\",\n \"compositions/ui/rich-text-editor-menu\"\n ]\n },\n {\n \"name\": \"rich-text-editor-with-hashtags\",\n \"content\": \"\\\"use client\\\"\\nexport const RichTextEditorWithHashtags = () => {\\n const initialContent = `<p>Type #chakra or #react and press space, it becomes a tag. Try pasting: #tiptap #awesome</p>`\\n const preprocessedContent = preprocessContent(initialContent, \\\"#\\\")\\n\\n const editor = useEditor({\\n extensions: [StarterKit, Hashtag],\\n content: preprocessedContent,\\n shouldRerenderOnTransaction: true,\\n immediatelyRender: false,\\n })\\n\\n if (!editor) return null\\n return (\\n <RichTextEditor.Root\\n editor={editor}\\n border=\\\"1px solid\\\"\\n borderColor=\\\"border\\\"\\n rounded=\\\"md\\\"\\n >\\n <RichTextEditor.Content />\\n </RichTextEditor.Root>\\n )\\n}\\n\\ninterface HashtagOptions {\\n trigger: string\\n}\\ninterface HashtagAttributes {\\n tag: string\\n}\\n\\nconst Hashtag = Node.create<HashtagOptions>({\\n name: \\\"hashtag\\\",\\n inline: true,\\n group: \\\"inline\\\",\\n atom: true,\\n\\n addOptions() {\\n return { trigger: \\\"#\\\" }\\n },\\n\\n addAttributes() {\\n return {\\n tag: {\\n default: \\\"\\\",\\n parseHTML: (element) => element.getAttribute(\\\"data-tag\\\"),\\n renderHTML: (attributes) => {\\n return { \\\"data-tag\\\": attributes.tag }\\n },\\n },\\n }\\n },\\n\\n parseHTML() {\\n return [\\n {\\n tag: \\\"span[data-type='hashtag']\\\",\\n getAttrs: (element) => {\\n if (typeof element === \\\"string\\\") return false\\n return {\\n tag: element.getAttribute(\\\"data-tag\\\") || \\\"\\\",\\n }\\n },\\n },\\n ]\\n },\\n\\n renderHTML({ HTMLAttributes }) {\\n return [\\n \\\"span\\\",\\n mergeAttributes(HTMLAttributes, {\\n \\\"data-type\\\": \\\"hashtag\\\",\\n \\\"data-tag\\\": HTMLAttributes.tag,\\n }),\\n `${this.options.trigger}${HTMLAttributes.tag}`,\\n ]\\n },\\n\\n addInputRules() {\\n const trigger = this.options.trigger\\n return [\\n nodeInputRule({\\n find: new RegExp(`(${trigger}[a-zA-Z0-9_]+)\\\\\\\\s$`),\\n type: this.type,\\n getAttributes: (match) => ({\\n tag: match[1].substring(trigger.length),\\n }),\\n }),\\n ]\\n },\\n\\n addPasteRules() {\\n const trigger = this.options.trigger\\n return [\\n nodePasteRule({\\n find: new RegExp(`${trigger}([a-zA-Z0-9_]+)`, \\\"g\\\"),\\n type: this.type,\\n getAttributes: (match) => ({ tag: match[1] }),\\n }),\\n ]\\n },\\n\\n addNodeView() {\\n return ReactNodeViewRenderer(HashtagComponent)\\n },\\n})\\n\\nfunction HashtagComponent({ node }: NodeViewProps) {\\n const { tag } = node.attrs as HashtagAttributes\\n return `#${tag}`\\n}\\n\\nfunction preprocessContent(content: string, trigger: string = \\\"#\\\"): string {\\n const regex = new RegExp(`${trigger}([a-zA-Z0-9_]+)`, \\\"g\\\")\\n\\n return content.replace(regex, (_match, tag) => {\\n return `<span data-type=\\\"hashtag\\\" data-tag=\\\"${tag}\\\">${trigger}${tag}</span>`\\n })\\n}\\n\",\n \"hasSnippet\": true,\n \"importPaths\": [\n \"import {\\n Node,\\n mergeAttributes,\\n nodeInputRule,\\n nodePasteRule,\\n} from \\\"@tiptap/core\\\"\",\n \"import {\\n type NodeViewProps,\\n ReactNodeViewRenderer,\\n useEditor,\\n} from \\\"@tiptap/react\\\"\",\n \"import StarterKit from \\\"@tiptap/starter-kit\\\"\",\n \"import { RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\"\n ],\n \"importPath\": \"import { RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"npmDependencies\": [\n \"@tiptap/core\",\n \"@tiptap/react\",\n \"@tiptap/starter-kit\"\n ],\n \"fileDependencies\": [\n \"compositions/ui/rich-text-editor\"\n ]\n },\n {\n \"name\": \"rich-text-editor-with-highlight\",\n \"content\": \"\\\"use client\\\"\\nexport const RichTextEditorWithHighlight = () => {\\n const editor = useEditor({\\n extensions: [\\n StarterKit,\\n Highlight.configure({\\n multicolor: true,\\n }),\\n ],\\n content: `\\n <p>This is a basic example of implementing text <mark data-color=\\\"#FFFF00\\\" style=\\\"background-color: #FFFF00\\\">highlighting</mark> using the Tiptap editor.</p>\\n <p>Select some text and click the highlight button to <mark data-color=\\\"#00FFFF\\\" style=\\\"background-color: #00FFFF\\\">apply a highlight color</mark>.</p>\\n `,\\n shouldRerenderOnTransaction: true,\\n immediatelyRender: false,\\n })\\n\\n if (!editor) return null\\n\\n return (\\n <RichTextEditor.Root editor={editor} borderWidth=\\\"1px\\\" rounded=\\\"l2\\\">\\n <RichTextEditor.Toolbar>\\n <RichTextEditor.ControlGroup>\\n <Control.Bold />\\n <Control.Italic />\\n <Control.Underline />\\n <Control.Strikethrough />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.Highlight />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.Undo />\\n <Control.Redo />\\n </RichTextEditor.ControlGroup>\\n </RichTextEditor.Toolbar>\\n\\n <RichTextEditor.Content />\\n </RichTextEditor.Root>\\n )\\n}\\n\",\n \"hasSnippet\": true,\n \"importPaths\": [\n \"import Highlight from \\\"@tiptap/extension-highlight\\\"\",\n \"import { useEditor } from \\\"@tiptap/react\\\"\",\n \"import StarterKit from \\\"@tiptap/starter-kit\\\"\",\n \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\"\n ],\n \"importPath\": \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"npmDependencies\": [\n \"@tiptap/extension-highlight\",\n \"@tiptap/react\",\n \"@tiptap/starter-kit\"\n ],\n \"fileDependencies\": [\n \"compositions/ui/rich-text-editor\"\n ]\n },\n {\n \"name\": \"rich-text-editor-with-image\",\n \"content\": \"\\\"use client\\\"\\nexport const RichTextEditorWithImage = () => {\\n const editor = useEditor({\\n content: `\\n <h2>Jiraiya Sensei</h2>\\n <img src=\\\"https://preview.redd.it/was-jiraiya-good-looking-back-in-the-day-or-does-it-just-v0-7lcmj7gpf4we1.jpg?width=640&crop=smart&auto=webp&s=cbece8f347da1b9326d1958dbb46284d4bceb828\\\" alt=\\\"Jiraiya Sensei\\\" />\\n <p><strong>Jiraiya</strong> is a legendary ninja from the Naruto series, known for his wisdom, humor, and mentorship of Naruto Uzumaki.</p>\\n <p>Famed as one of the \\\"Legendary Sannin,\\\" Jiraiya travels the world gathering knowledge and inspiring future generations.</p>\\n `,\\n extensions: [StarterKit, Image],\\n immediatelyRender: false,\\n })\\n\\n if (!editor) return null\\n\\n return (\\n <RichTextEditor.Root editor={editor}>\\n <RichTextEditor.Toolbar>\\n <RichTextEditor.ControlGroup>\\n <Control.Bold />\\n <Control.Italic />\\n <Control.Strikethrough />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.BulletList />\\n <Control.OrderedList />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <InsertImageControl />\\n </RichTextEditor.ControlGroup>\\n </RichTextEditor.Toolbar>\\n\\n <RichTextEditor.Content />\\n </RichTextEditor.Root>\\n )\\n}\\n\\nfunction InsertImageControl() {\\n const { editor } = useRichTextEditorContext()\\n const [open, setOpen] = useState(false)\\n const [files, setFiles] = useState<File[]>([])\\n\\n if (!editor) return null\\n\\n return (\\n <>\\n <Control.ButtonControl\\n icon={<LuImage />}\\n label=\\\"Insert Image\\\"\\n onClick={() => setOpen(true)}\\n variant=\\\"ghost\\\"\\n />\\n\\n <Dialog.Root open={open} onOpenChange={(e) => setOpen(e.open)}>\\n <Portal>\\n <Dialog.Backdrop />\\n <Dialog.Positioner>\\n <Dialog.Content maxW=\\\"lg\\\">\\n <Dialog.Header>\\n <Dialog.Title>Insert Image</Dialog.Title>\\n </Dialog.Header>\\n\\n <Dialog.Body>\\n <Tabs.Root defaultValue=\\\"url\\\">\\n <Tabs.List>\\n <Tabs.Trigger value=\\\"url\\\">\\n <LuLink /> Embed URL\\n </Tabs.Trigger>\\n <Tabs.Trigger value=\\\"upload\\\">\\n <LuUpload /> Upload File\\n </Tabs.Trigger>\\n </Tabs.List>\\n\\n <Tabs.Content value=\\\"url\\\">\\n <Box display=\\\"flex\\\" gap=\\\"2\\\" mt=\\\"4\\\">\\n <Input\\n placeholder=\\\"Enter image URL\\\"\\n id=\\\"image-url-input\\\"\\n />\\n <Button\\n onClick={() => {\\n const url = (\\n document.getElementById(\\n \\\"image-url-input\\\",\\n ) as HTMLInputElement\\n ).value\\n if (url)\\n editor.chain().focus().setImage({ src: url }).run()\\n setOpen(false)\\n }}\\n >\\n Insert\\n </Button>\\n </Box>\\n </Tabs.Content>\\n\\n <Tabs.Content value=\\\"upload\\\">\\n <FileUpload.Root\\n maxW=\\\"xl\\\"\\n alignItems=\\\"stretch\\\"\\n maxFiles={1}\\n accept=\\\"image/*\\\"\\n onFileAccept={(accepted) => {\\n const uploaded = accepted.files ?? []\\n setFiles(uploaded)\\n\\n if (uploaded[0]) {\\n const url = URL.createObjectURL(uploaded[0])\\n editor.chain().focus().setImage({ src: url }).run()\\n setOpen(false)\\n }\\n }}\\n >\\n <FileUpload.HiddenInput />\\n <FileUpload.Dropzone>\\n <Icon size=\\\"md\\\" color=\\\"fg.muted\\\">\\n <LuUpload />\\n </Icon>\\n <FileUpload.DropzoneContent>\\n <Box>Drag and drop a file here</Box>\\n <Box color=\\\"fg.muted\\\">.png, .jpg up to 5MB</Box>\\n </FileUpload.DropzoneContent>\\n </FileUpload.Dropzone>\\n\\n <FileUpload.List files={files} />\\n </FileUpload.Root>\\n </Tabs.Content>\\n </Tabs.Root>\\n </Dialog.Body>\\n\\n <Dialog.Footer mt=\\\"4\\\">\\n <Button variant=\\\"outline\\\" onClick={() => setOpen(false)}>\\n Cancel\\n </Button>\\n </Dialog.Footer>\\n </Dialog.Content>\\n </Dialog.Positioner>\\n </Portal>\\n </Dialog.Root>\\n </>\\n )\\n}\\n\",\n \"hasSnippet\": true,\n \"importPaths\": [\n \"import {\\n Box,\\n Button,\\n Dialog,\\n FileUpload,\\n Icon,\\n Input,\\n Portal,\\n Tabs,\\n} from \\\"@chakra-ui/react\\\"\",\n \"import Image from \\\"@tiptap/extension-image\\\"\",\n \"import { useEditor } from \\\"@tiptap/react\\\"\",\n \"import StarterKit from \\\"@tiptap/starter-kit\\\"\",\n \"import {\\n Control,\\n RichTextEditor,\\n useRichTextEditorContext,\\n} from \\\"@/components/ui/rich-text-editor\\\"\",\n \"import { useState } from \\\"react\\\"\",\n \"import { LuImage, LuLink, LuUpload } from \\\"react-icons/lu\\\"\"\n ],\n \"importPath\": \"import {\\n Control,\\n RichTextEditor,\\n useRichTextEditorContext,\\n} from \\\"@/components/ui/rich-text-editor\\\"\",\n \"npmDependencies\": [\n \"@tiptap/extension-image\",\n \"@tiptap/react\",\n \"@tiptap/starter-kit\",\n \"react-icons\"\n ],\n \"fileDependencies\": [\n \"compositions/ui/rich-text-editor\"\n ]\n },\n {\n \"name\": \"rich-text-editor-with-mentions\",\n \"content\": \"\\\"use client\\\"\\nexport const RichTextEditorWithMentions = () => {\\n const editor = useEditor({\\n extensions: [\\n StarterKit.configure({\\n heading: {\\n levels: [1, 2, 3],\\n },\\n }),\\n Subscript,\\n Superscript,\\n TextAlign.configure({ types: [\\\"paragraph\\\", \\\"heading\\\"] }),\\n TextStyle,\\n CustomMention.configure({\\n HTMLAttributes: {\\n class: \\\"mention\\\",\\n },\\n suggestion: createMentionConfig(MENTION_USERS),\\n }),\\n HashtagMention.configure({\\n HTMLAttributes: {\\n class: \\\"hashtag\\\",\\n },\\n suggestion: createSuggestionConfig(\\\"#\\\", (query) =>\\n HASHTAGS.filter((hashtag) =>\\n hashtag.label.toLowerCase().includes(query.toLowerCase()),\\n ),\\n ),\\n }),\\n ],\\n content: `<h1>Rich Text Editor with Mentions</h1><p>Type <strong>@</strong> for mentions or <strong>#</strong> for hashtags</p>`,\\n shouldRerenderOnTransaction: true,\\n immediatelyRender: false,\\n })\\n\\n if (!editor) return null\\n\\n return (\\n <RichTextEditor.Root editor={editor} borderWidth=\\\"1px\\\" rounded=\\\"sm\\\">\\n <RichTextEditor.Toolbar>\\n <RichTextEditor.ControlGroup>\\n <Control.Bold />\\n <Control.Italic />\\n <Control.Strikethrough />\\n <Control.Code />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.H1 />\\n <Control.H2 />\\n <Control.H3 />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.BulletList />\\n <Control.OrderedList />\\n <Control.Blockquote />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.Undo />\\n <Control.Redo />\\n </RichTextEditor.ControlGroup>\\n </RichTextEditor.Toolbar>\\n\\n <RichTextEditor.Content />\\n </RichTextEditor.Root>\\n )\\n}\\n\\nconst MentionComponent = (props: ReactNodeViewProps) => {\\n return (\\n <NodeViewWrapper as=\\\"span\\\">\\n <Tag size=\\\"lg\\\" colorPalette=\\\"orange\\\" mr=\\\"1\\\">\\n @{props.node.attrs.label ?? props.node.attrs.id}\\n </Tag>\\n </NodeViewWrapper>\\n )\\n}\\n\\nconst CustomMention = Mention.extend({\\n addNodeView() {\\n return ReactNodeViewRenderer(MentionComponent)\\n },\\n})\\n\\nconst HashtagMention = Mention.extend({\\n name: \\\"hashtag\\\",\\n addNodeView() {\\n return ReactNodeViewRenderer((props) => (\\n <NodeViewWrapper as=\\\"span\\\">\\n #{props.node.attrs.label ?? props.node.attrs.id}\\n </NodeViewWrapper>\\n ))\\n },\\n})\\n\\nconst MENTION_USERS: MentionItem[] = [\\n { id: \\\"1\\\", label: \\\"Alice Johnson\\\", email: \\\"alice@example.com\\\" },\\n { id: \\\"2\\\", label: \\\"Bob Smith\\\", email: \\\"bob@example.com\\\" },\\n { id: \\\"3\\\", label: \\\"Charlie Davis\\\", email: \\\"charlie@example.com\\\" },\\n { id: \\\"4\\\", label: \\\"Diana Wilson\\\", email: \\\"diana@example.com\\\" },\\n { id: \\\"5\\\", label: \\\"Ethan Brown\\\", email: \\\"ethan@example.com\\\" },\\n { id: \\\"6\\\", label: \\\"Fiona Martinez\\\", email: \\\"fiona@example.com\\\" },\\n { id: \\\"7\\\", label: \\\"George Anderson\\\", email: \\\"george@example.com\\\" },\\n { id: \\\"8\\\", label: \\\"Hannah Taylor\\\", email: \\\"hannah@example.com\\\" },\\n]\\n\\nconst HASHTAGS: HashtagItem[] = [\\n { id: \\\"react\\\", label: \\\"react\\\", description: \\\"React.js framework\\\" },\\n { id: \\\"typescript\\\", label: \\\"typescript\\\", description: \\\"TypeScript language\\\" },\\n { id: \\\"nextjs\\\", label: \\\"nextjs\\\", description: \\\"Next.js framework\\\" },\\n { id: \\\"chakra\\\", label: \\\"chakra\\\", description: \\\"Chakra UI library\\\" },\\n { id: \\\"javascript\\\", label: \\\"javascript\\\", description: \\\"JavaScript language\\\" },\\n { id: \\\"css\\\", label: \\\"css\\\", description: \\\"CSS styling\\\" },\\n]\\n\",\n \"hasSnippet\": true,\n \"importPaths\": [\n \"import Mention from \\\"@tiptap/extension-mention\\\"\",\n \"import Subscript from \\\"@tiptap/extension-subscript\\\"\",\n \"import Superscript from \\\"@tiptap/extension-superscript\\\"\",\n \"import TextAlign from \\\"@tiptap/extension-text-align\\\"\",\n \"import { TextStyle } from \\\"@tiptap/extension-text-style\\\"\",\n \"import {\\n NodeViewWrapper,\\n type ReactNodeViewProps,\\n ReactNodeViewRenderer,\\n useEditor,\\n} from \\\"@tiptap/react\\\"\",\n \"import StarterKit from \\\"@tiptap/starter-kit\\\"\",\n \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"import {\\n type HashtagItem,\\n type MentionItem,\\n createMentionConfig,\\n createSuggestionConfig,\\n} from \\\"@/components/ui/rich-text-editor-menu\\\"\",\n \"import { Tag } from \\\"@/components/ui/tag\\\"\"\n ],\n \"importPath\": \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"npmDependencies\": [\n \"@tiptap/extension-mention\",\n \"@tiptap/extension-subscript\",\n \"@tiptap/extension-superscript\",\n \"@tiptap/extension-text-align\",\n \"@tiptap/extension-text-style\",\n \"@tiptap/react\",\n \"@tiptap/starter-kit\"\n ],\n \"fileDependencies\": [\n \"compositions/ui/rich-text-editor\",\n \"compositions/ui/rich-text-editor-menu\",\n \"compositions/ui/tag\"\n ]\n },\n {\n \"name\": \"rich-text-editor-with-mode\",\n \"content\": \"\\\"use client\\\"\\nexport const RichTextEditorWithMode = () => {\\n const [editable, setEditable] = useState(true)\\n\\n const editor = useEditor({\\n extensions: [StarterKit],\\n content: `<p>Edit this text...</p>`,\\n editable,\\n shouldRerenderOnTransaction: true,\\n immediatelyRender: false,\\n })\\n\\n if (!editor) return null\\n\\n const handleModeChange = (newMode: string) => {\\n setEditable(newMode === \\\"edit\\\")\\n editor.setEditable(newMode === \\\"edit\\\")\\n }\\n\\n return (\\n <RichTextEditor.Root editor={editor} borderWidth=\\\"1px\\\" rounded=\\\"md\\\">\\n <HStack p=\\\"2\\\" borderBottomWidth=\\\"1px\\\" justify=\\\"space-between\\\">\\n <RichTextEditor.ControlGroup\\n inert={!editable}\\n opacity={!editable ? 0.5 : 1}\\n >\\n <Control.Bold />\\n <Control.Italic />\\n <Control.Underline />\\n <Control.Strikethrough />\\n <Control.Code />\\n </RichTextEditor.ControlGroup>\\n <RichTextEditor.ControlGroup>\\n <ModePicker\\n width=\\\"120px\\\"\\n currentMode={editable ? \\\"edit\\\" : \\\"view\\\"}\\n onModeChange={handleModeChange}\\n />\\n </RichTextEditor.ControlGroup>\\n </HStack>\\n\\n <RichTextEditor.Content />\\n </RichTextEditor.Root>\\n )\\n}\\n\\ninterface ModePickerProps {\\n currentMode: string\\n onModeChange: (mode: string) => void\\n width?: string\\n}\\n\\nconst ModePicker = (props: ModePickerProps) => {\\n const { currentMode, onModeChange, ...rest } = props\\n\\n const SelectControl = createSelectControl({\\n label: \\\"Mode\\\",\\n options: [\\n { value: \\\"edit\\\", label: \\\"Editing\\\" },\\n { value: \\\"view\\\", label: \\\"Viewing\\\" },\\n ],\\n getValue: () => currentMode,\\n command: (_editor, value) => {\\n onModeChange(value)\\n },\\n })\\n\\n return <SelectControl {...rest} />\\n}\\n\",\n \"hasSnippet\": true,\n \"importPaths\": [\n \"import { HStack } from \\\"@chakra-ui/react\\\"\",\n \"import { useEditor } from \\\"@tiptap/react\\\"\",\n \"import StarterKit from \\\"@tiptap/starter-kit\\\"\",\n \"import {\\n Control,\\n RichTextEditor,\\n createSelectControl,\\n} from \\\"@/components/ui/rich-text-editor\\\"\",\n \"import { useState } from \\\"react\\\"\"\n ],\n \"importPath\": \"import {\\n Control,\\n RichTextEditor,\\n createSelectControl,\\n} from \\\"@/components/ui/rich-text-editor\\\"\",\n \"npmDependencies\": [\n \"@tiptap/react\",\n \"@tiptap/starter-kit\"\n ],\n \"fileDependencies\": [\n \"compositions/ui/rich-text-editor\"\n ]\n },\n {\n \"name\": \"rich-text-editor-with-paste-handling\",\n \"content\": \"\\\"use client\\\"\\nexport const RichTextEditorWithPasteHandling = () => {\\n const editor = useEditor({\\n extensions: [\\n StarterKit.configure({\\n heading: {\\n levels: [1, 2, 3],\\n },\\n }),\\n ],\\n content: `\\n <p>Try pasting content from external sources like Word, Google Docs, or web pages.</p>\\n <p>The editor will clean up the formatting while preserving the essential structure.</p>\\n `,\\n shouldRerenderOnTransaction: true,\\n immediatelyRender: false,\\n editorProps: {\\n transformPastedText,\\n transformPastedHTML,\\n },\\n })\\n\\n if (!editor) return null\\n\\n return (\\n <Box>\\n <Text fontSize=\\\"sm\\\" color=\\\"fg.muted\\\" mb=\\\"2\\\">\\n Paste content from Word, Google Docs, or web pages - formatting will be\\n cleaned automatically.\\n </Text>\\n <RichTextEditor.Root editor={editor} borderWidth=\\\"1px\\\" rounded=\\\"l2\\\">\\n <RichTextEditor.Toolbar>\\n <RichTextEditor.ControlGroup>\\n <Control.Bold />\\n <Control.Italic />\\n <Control.Underline />\\n <Control.Strikethrough />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.H1 />\\n <Control.H2 />\\n <Control.H3 />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.BulletList />\\n <Control.OrderedList />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.Undo />\\n <Control.Redo />\\n </RichTextEditor.ControlGroup>\\n </RichTextEditor.Toolbar>\\n\\n <RichTextEditor.Content />\\n </RichTextEditor.Root>\\n </Box>\\n )\\n}\\n\\nconst transformPastedText = (text: string) => {\\n return text\\n .replace(/\\\\r\\\\n/g, \\\"\\\\n\\\")\\n .replace(/\\\\n{2,}/g, \\\"\\\\n\\\\n\\\")\\n .trim()\\n}\\n\\nconst transformPastedHTML = (html: string) => {\\n return html\\n .replace(/<meta[^>]*>/gi, \\\"\\\")\\n .replace(/<b[^>]*>/gi, \\\"\\\")\\n .replace(/<\\\\/b>/gi, \\\"\\\")\\n .replace(/style=\\\"[^\\\"]*\\\"/gi, \\\"\\\")\\n .replace(/class=\\\"[^\\\"]*\\\"/gi, \\\"\\\")\\n .replace(/\\\\s*dir=\\\"[^\\\"]*\\\"/gi, \\\"\\\")\\n .replace(/\\\\s*id=\\\"[^\\\"]*\\\"/gi, \\\"\\\")\\n .replace(/<p>\\\\s*<\\\\/p>/gi, \\\"\\\")\\n .replace(/<\\\\/p>\\\\s*<br\\\\s*\\\\/?>\\\\s*/gi, \\\"</p>\\\")\\n .replace(/<br\\\\s*\\\\/?>\\\\s*<p/gi, \\\"<p\\\")\\n .replace(/^(\\\\s*<br\\\\s*\\\\/?>)+/gi, \\\"\\\")\\n .replace(/(<br\\\\s*\\\\/?>)+\\\\s*$/gi, \\\"\\\")\\n .replace(/<div>/gi, \\\"<p>\\\")\\n .replace(/<\\\\/div>/gi, \\\"</p>\\\")\\n .replace(/<font[^>]*>/gi, \\\"\\\")\\n .replace(/<\\\\/font>/gi, \\\"\\\")\\n .replace(/<span[^>]*>/gi, \\\"\\\")\\n .replace(/<\\\\/span>/gi, \\\"\\\")\\n .replace(/<p\\\\s*>/gi, \\\"<p>\\\")\\n .replace(/<p>(\\\\s|&nbsp;)*<\\\\/p>/gi, \\\"\\\")\\n .replace(/(<\\\\/p>\\\\s*)+(<p>)/gi, \\\"</p><p>\\\")\\n}\\n\",\n \"hasSnippet\": true,\n \"importPaths\": [\n \"import { Box, Text } from \\\"@chakra-ui/react\\\"\",\n \"import { useEditor } from \\\"@tiptap/react\\\"\",\n \"import StarterKit from \\\"@tiptap/starter-kit\\\"\",\n \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\"\n ],\n \"importPath\": \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"npmDependencies\": [\n \"@tiptap/react\",\n \"@tiptap/starter-kit\"\n ],\n \"fileDependencies\": [\n \"compositions/ui/rich-text-editor\"\n ]\n },\n {\n \"name\": \"rich-text-editor-with-placeholder\",\n \"content\": \"\\\"use client\\\"\\nexport const RichTextEditorWithPlaceholder = () => {\\n const editor = useEditor({\\n extensions: [\\n StarterKit,\\n Placeholder.configure({\\n placeholder: \\\"Start typing your content here...\\\",\\n }),\\n ],\\n content: \\\"\\\",\\n shouldRerenderOnTransaction: true,\\n immediatelyRender: false,\\n })\\n\\n if (!editor) return null\\n\\n return (\\n <RichTextEditor.Root editor={editor} borderWidth=\\\"1px\\\" rounded=\\\"l2\\\">\\n <RichTextEditor.Toolbar>\\n <RichTextEditor.ControlGroup>\\n <Control.Bold />\\n <Control.Italic />\\n <Control.Underline />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.BulletList />\\n <Control.OrderedList />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.Undo />\\n <Control.Redo />\\n </RichTextEditor.ControlGroup>\\n </RichTextEditor.Toolbar>\\n\\n <RichTextEditor.Content />\\n </RichTextEditor.Root>\\n )\\n}\\n\",\n \"hasSnippet\": true,\n \"importPaths\": [\n \"import Placeholder from \\\"@tiptap/extension-placeholder\\\"\",\n \"import { useEditor } from \\\"@tiptap/react\\\"\",\n \"import StarterKit from \\\"@tiptap/starter-kit\\\"\",\n \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\"\n ],\n \"importPath\": \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"npmDependencies\": [\n \"@tiptap/extension-placeholder\",\n \"@tiptap/react\",\n \"@tiptap/starter-kit\"\n ],\n \"fileDependencies\": [\n \"compositions/ui/rich-text-editor\"\n ]\n },\n {\n \"name\": \"rich-text-editor-with-preview\",\n \"content\": \"\\\"use client\\\"\\nexport const RichTextEditorWithPreview = () => {\\n const editor = useEditor({\\n extensions: [\\n StarterKit.configure({ link: { openOnClick: false } }),\\n Subscript,\\n Superscript,\\n TextAlign.configure({ types: [\\\"paragraph\\\", \\\"heading\\\"] }),\\n ],\\n content: `\\n <p>Edit here...</p>\\n <p><strong>Tip:</strong> Try selecting this sentence.</p>\\n <h2>Example Subheading</h2>\\n <p>Here's a paragraph with <em>italic</em>, <u>underline</u>, and <strong>bold</strong> text.</p>\\n <p><code>Code snippets can be inline or block-level.</code></p>\\n <ul>\\n <li>Item one</li>\\n <li>Item two</li>\\n <li>Item three</li>\\n </ul>\\n <ol>\\n <li>First numbered item</li>\\n <li>Second numbered item</li>\\n </ol>\\n <blockquote>This is a blockquote example.</blockquote>\\n `,\\n shouldRerenderOnTransaction: true,\\n immediatelyRender: false,\\n })\\n\\n if (!editor) return null\\n\\n return (\\n <Splitter.Root panels={[{ id: \\\"editor\\\" }, { id: \\\"preview\\\" }]} minH=\\\"60\\\">\\n <Splitter.Panel id=\\\"editor\\\">\\n <RichTextEditor.Root\\n editor={editor}\\n css={{ \\\"--content-min-height\\\": \\\"520px\\\" }}\\n >\\n <RichTextEditor.Toolbar>\\n <RichTextEditor.ControlGroup>\\n <Control.Bold />\\n <Control.Italic />\\n <Control.Underline />\\n <Control.Strikethrough />\\n <Control.Code />\\n </RichTextEditor.ControlGroup>\\n <RichTextEditor.ControlGroup>\\n <Control.H1 />\\n <Control.H2 />\\n <Control.H3 />\\n <Control.H4 />\\n </RichTextEditor.ControlGroup>\\n <RichTextEditor.ControlGroup>\\n <Control.Undo />\\n <Control.Redo />\\n </RichTextEditor.ControlGroup>\\n </RichTextEditor.Toolbar>\\n\\n <RichTextEditor.Content />\\n </RichTextEditor.Root>\\n </Splitter.Panel>\\n\\n <Splitter.ResizeTrigger id=\\\"editor:preview\\\" />\\n <Splitter.Panel id=\\\"preview\\\" px=\\\"8\\\" py=\\\"2\\\">\\n <Prose\\n width=\\\"full\\\"\\n size=\\\"lg\\\"\\n color=\\\"fg\\\"\\n dangerouslySetInnerHTML={{ __html: editor.getHTML() }}\\n />\\n </Splitter.Panel>\\n </Splitter.Root>\\n )\\n}\\n\",\n \"hasSnippet\": true,\n \"importPaths\": [\n \"import { Splitter } from \\\"@chakra-ui/react\\\"\",\n \"import Subscript from \\\"@tiptap/extension-subscript\\\"\",\n \"import Superscript from \\\"@tiptap/extension-superscript\\\"\",\n \"import TextAlign from \\\"@tiptap/extension-text-align\\\"\",\n \"import { useEditor } from \\\"@tiptap/react\\\"\",\n \"import StarterKit from \\\"@tiptap/starter-kit\\\"\",\n \"import { Prose } from \\\"@/components/ui/prose\\\"\",\n \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\"\n ],\n \"importPath\": \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"npmDependencies\": [\n \"@tiptap/extension-subscript\",\n \"@tiptap/extension-superscript\",\n \"@tiptap/extension-text-align\",\n \"@tiptap/react\",\n \"@tiptap/starter-kit\"\n ],\n \"fileDependencies\": [\n \"compositions/ui/prose\",\n \"compositions/ui/rich-text-editor\"\n ]\n },\n {\n \"name\": \"rich-text-editor-with-slash-commands\",\n \"content\": \"\\\"use client\\\"\\nexport const RichTextEditorWithSlashCommands = () => {\\n const editor = useEditor({\\n extensions: [\\n StarterKit.configure({\\n heading: {\\n levels: [1, 2, 3],\\n },\\n }),\\n Subscript,\\n Superscript,\\n TextAlign.configure({ types: [\\\"paragraph\\\", \\\"heading\\\"] }),\\n TextStyle,\\n SlashCommandsExtension,\\n ],\\n content: `<h1>Slash Commands Editor</h1><p>Type <strong>/</strong> to see commands</p>`,\\n shouldRerenderOnTransaction: true,\\n immediatelyRender: false,\\n })\\n\\n if (!editor) return null\\n\\n return (\\n <RichTextEditor.Root editor={editor} borderWidth=\\\"1px\\\" rounded=\\\"sm\\\">\\n <RichTextEditor.Toolbar>\\n <RichTextEditor.ControlGroup>\\n <Control.Bold />\\n <Control.Italic />\\n <Control.Strikethrough />\\n <Control.Code />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.H1 />\\n <Control.H2 />\\n <Control.H3 />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.BulletList />\\n <Control.OrderedList />\\n <Control.Blockquote />\\n </RichTextEditor.ControlGroup>\\n\\n <RichTextEditor.ControlGroup>\\n <Control.Undo />\\n <Control.Redo />\\n </RichTextEditor.ControlGroup>\\n </RichTextEditor.Toolbar>\\n\\n <RichTextEditor.Content />\\n </RichTextEditor.Root>\\n )\\n}\\n\\ninterface SlashCommand {\\n id: string\\n label: string\\n description: string\\n icon: any\\n command: (props: { editor: any; range: any }) => void\\n}\\n\\nconst SLASH_COMMANDS: SlashCommand[] = [\\n {\\n id: \\\"heading1\\\",\\n label: \\\"Heading 1\\\",\\n description: \\\"Large section heading\\\",\\n icon: LuHash,\\n command: ({ editor, range }) => {\\n editor\\n .chain()\\n .focus()\\n .deleteRange(range)\\n .setNode(\\\"heading\\\", { level: 1 })\\n .run()\\n },\\n },\\n {\\n id: \\\"heading2\\\",\\n label: \\\"Heading 2\\\",\\n description: \\\"Medium section heading\\\",\\n icon: LuHash,\\n command: ({ editor, range }) => {\\n editor\\n .chain()\\n .focus()\\n .deleteRange(range)\\n .setNode(\\\"heading\\\", { level: 2 })\\n .run()\\n },\\n },\\n {\\n id: \\\"heading3\\\",\\n label: \\\"Heading 3\\\",\\n description: \\\"Small section heading\\\",\\n icon: LuHash,\\n command: ({ editor, range }) => {\\n editor\\n .chain()\\n .focus()\\n .deleteRange(range)\\n .setNode(\\\"heading\\\", { level: 3 })\\n .run()\\n },\\n },\\n {\\n id: \\\"bullet\\\",\\n label: \\\"Bullet List\\\",\\n description: \\\"Create a bullet list\\\",\\n icon: LuList,\\n command: ({ editor, range }) => {\\n editor.chain().focus().deleteRange(range).toggleBulletList().run()\\n },\\n },\\n {\\n id: \\\"numbered\\\",\\n label: \\\"Numbered List\\\",\\n description: \\\"Create a numbered list\\\",\\n icon: LuListOrdered,\\n command: ({ editor, range }) => {\\n editor.chain().focus().deleteRange(range).toggleOrderedList().run()\\n },\\n },\\n {\\n id: \\\"quote\\\",\\n label: \\\"Quote\\\",\\n description: \\\"Add a blockquote\\\",\\n icon: LuQuote,\\n command: ({ editor, range }) => {\\n editor.chain().focus().deleteRange(range).toggleBlockquote().run()\\n },\\n },\\n {\\n id: \\\"code\\\",\\n label: \\\"Code Block\\\",\\n description: \\\"Add a code block\\\",\\n icon: LuCode,\\n command: ({ editor, range }) => {\\n editor.chain().focus().deleteRange(range).toggleCodeBlock().run()\\n },\\n },\\n]\\n\\nconst SlashCommandsExtension = Extension.create({\\n name: \\\"slashCommands\\\",\\n\\n addProseMirrorPlugins() {\\n return [\\n Suggestion({\\n editor: this.editor,\\n char: \\\"/\\\",\\n pluginKey: new PluginKey(\\\"slashCommands\\\"),\\n\\n command: ({\\n editor,\\n range,\\n props,\\n }: {\\n editor: any\\n range: any\\n props: SlashCommand\\n }) => {\\n props.command({ editor, range })\\n },\\n\\n items: ({ query }: { query: string }) =>\\n SLASH_COMMANDS.filter((command) =>\\n command.label.toLowerCase().includes(query.toLowerCase()),\\n ),\\n\\n render: () => {\\n let component: ReactRenderer<\\n HTMLDivElement,\\n FloatingMenuProps\\n > | null = null\\n let container: HTMLDivElement | null = null\\n let selectedIndex = 0\\n\\n return {\\n onStart(props) {\\n selectedIndex = 0\\n container = document.createElement(\\\"div\\\")\\n document.body.appendChild(container)\\n\\n component = new ReactRenderer(SuggestionMenu, {\\n props: {\\n items: props.items,\\n selectedIndex,\\n onSelect: (item: SlashCommand) => props.command(item),\\n clientRect: props.clientRect,\\n },\\n editor: props.editor,\\n })\\n\\n container.appendChild(component.element)\\n },\\n\\n onUpdate(props) {\\n if (!component) return\\n component.updateProps({\\n items: props.items,\\n selectedIndex,\\n onSelect: (item: SlashCommand) => props.command(item),\\n clientRect: props.clientRect,\\n })\\n },\\n\\n onKeyDown({ event }) {\\n if (!component) return false\\n\\n if (event.key === \\\"ArrowUp\\\") {\\n selectedIndex =\\n (selectedIndex - 1 + component.props.items.length) %\\n component.props.items.length\\n component.updateProps({ ...component.props, selectedIndex })\\n return true\\n }\\n\\n if (event.key === \\\"ArrowDown\\\") {\\n selectedIndex =\\n (selectedIndex + 1) % component.props.items.length\\n component.updateProps({ ...component.props, selectedIndex })\\n return true\\n }\\n\\n if (event.key === \\\"Enter\\\") {\\n const item = component.props.items[selectedIndex]\\n if (item) component.props.onSelect(item)\\n return true\\n }\\n\\n if (event.key === \\\"Escape\\\") return true\\n\\n return false\\n },\\n\\n onExit() {\\n if (container) container.remove()\\n container = null\\n if (component) component.destroy()\\n component = null\\n },\\n }\\n },\\n }),\\n ]\\n },\\n})\\n\",\n \"hasSnippet\": true,\n \"importPaths\": [\n \"import { Extension } from \\\"@tiptap/core\\\"\",\n \"import Subscript from \\\"@tiptap/extension-subscript\\\"\",\n \"import Superscript from \\\"@tiptap/extension-superscript\\\"\",\n \"import TextAlign from \\\"@tiptap/extension-text-align\\\"\",\n \"import { TextStyle } from \\\"@tiptap/extension-text-style\\\"\",\n \"import { PluginKey } from \\\"@tiptap/pm/state\\\"\",\n \"import { ReactRenderer, useEditor } from \\\"@tiptap/react\\\"\",\n \"import StarterKit from \\\"@tiptap/starter-kit\\\"\",\n \"import { Suggestion } from \\\"@tiptap/suggestion\\\"\",\n \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"import {\\n type FloatingMenuProps,\\n SuggestionMenu,\\n} from \\\"@/components/ui/rich-text-editor-menu\\\"\",\n \"import { LuCode, LuHash, LuList, LuListOrdered, LuQuote } from \\\"react-icons/lu\\\"\"\n ],\n \"importPath\": \"import { Control, RichTextEditor } from \\\"@/components/ui/rich-text-editor\\\"\",\n \"npmDependencies\": [\n \"@tiptap/core\",\n \"@tiptap/extension-subscript\",\n \"@tiptap/extension-superscript\",\n \"@tiptap/extension-text-align\",\n \"@tiptap/extension-text-style\",\n \"@tiptap/pm\",\n \"@tiptap/react\",\n \"@tiptap/starter-kit\",\n \"@tiptap/suggestion\",\n \"react-icons\"\n ],\n \"fileDependencies\": [\n \"compositions/ui/rich-text-editor\",\n \"compositions/ui/rich-text-editor-menu\"\n ]\n },\n {\n \"name\": \"rich-text-editor-with-task\",\n \"content\": \"\\\"use client\\\"\\nexport const RichTextEditorWithTask = () => {\\n const editor = useEditor({\\n extensions: [StarterKit, TaskList, TaskItem.configure({ nested: true })],\\n content: `\\n <h2>Project Tasks</h2>\\n <p>Use the toolbar to manage your tasks:</p>\\n <ul data-type=\\\"taskList\\\">\\n <li data-type=\\\"taskItem\\\" data-checked=\\\"false\\\">Write introduction</li>\\n <li data-type=\\\"taskItem\\\" data-checked=\\\"true\\\">Set up editor</li>\\n <li data-type=\\\"taskItem\\\" data-checked=\\\"false\\\">Add toolbar controls</li>\\n </ul>\\n <p>Keep adding tasks to track your progress!</p>\\n `,\\n shouldRerenderOnTransaction: true,\\n immediatelyRender: false,\\n })\\n\\n if (!editor) return null\\n\\n return (\\n <RichTextEditor.Root editor={editor} borderWidth=\\\"1px\\\" rounded=\\\"md\\\">\\n <HStack gap=\\\"2\\\" p=\\\"2\\\" borderBottomWidth=\\\"1px\\\">\\n <RichTextEditor.ControlGroup>\\n <ToggleTaskList />\\n <IndentTask />\\n <OutdentTask />\\n <AddTask />\\n </RichTextEditor.ControlGroup>\\n </HStack>\\n <RichTextEditor.Content />\\n </RichTextEditor.Root>\\n )\\n}\\n\\nconst ToggleTaskList = createBooleanControl({\\n label: \\\"Toggle Task List\\\",\\n icon: LuListChecks,\\n command: (editor) => editor.chain().focus().toggleTaskList().run(),\\n getVariant: (editor) => (editor.isActive(\\\"taskList\\\") ? \\\"subtle\\\" : \\\"ghost\\\"),\\n})\\n\\nconst IndentTask = createBooleanControl({\\n label: \\\"Indent Task\\\",\\n icon: LuArrowRight,\\n command: (editor) => editor.chain().focus().sinkListItem(\\\"taskItem\\\").run(),\\n getVariant: (editor) => (editor.isActive(\\\"taskItem\\\") ? \\\"subtle\\\" : \\\"ghost\\\"),\\n})\\n\\nconst OutdentTask = createBooleanControl({\\n label: \\\"Outdent Task\\\",\\n icon: LuArrowLeft,\\n command: (editor) => editor.chain().focus().liftListItem(\\\"taskItem\\\").run(),\\n getVariant: (editor) => (editor.isActive(\\\"taskItem\\\") ? \\\"subtle\\\" : \\\"ghost\\\"),\\n})\\n\\nconst AddTask = createBooleanControl({\\n label: \\\"Add Task\\\",\\n icon: LuPlus,\\n command: (editor) =>\\n editor\\n .chain()\\n .focus()\\n .insertContent(\\n `<li data-type=\\\"taskItem\\\" data-checked=\\\"false\\\">New task</li>`,\\n )\\n .run(),\\n getVariant: (editor) => (editor.isActive(\\\"taskItem\\\") ? \\\"subtle\\\" : \\\"ghost\\\"),\\n})\\n\",\n \"hasSnippet\": true,\n \"importPaths\": [\n \"import { HStack } from \\\"@chakra-ui/react\\\"\",\n \"import TaskItem from \\\"@tiptap/extension-task-item\\\"\",\n \"import TaskList from \\\"@tiptap/extension-task-list\\\"\",\n \"import { useEditor } from \\\"@tiptap/react\\\"\",\n \"import StarterKit from \\\"@tiptap/starter-kit\\\"\",\n \"import {\\n RichTextEditor,\\n createBooleanControl,\\n} from \\\"@/components/ui/rich-text-editor\\\"\",\n \"import { LuArrowLeft, LuArrowRight, LuListChecks, LuPlus } from \\\"react-icons/lu\\\"\"\n ],\n \"importPath\": \"import {\\n RichTextEditor,\\n createBooleanControl,\\n} from \\\"@/components/ui/rich-text-editor\\\"\",\n \"npmDependencies\": [\n \"@tiptap/extension-task-item\",\n \"@tiptap/extension-task-list\",\n \"@tiptap/react\",\n \"@tiptap/starter-kit\",\n \"react-icons\"\n ],\n \"fileDependencies\": [\n \"compositions/ui/rich-text-editor\"\n ]\n }\n ]\n}"}}}}