@open-cloud-initiative/editor-x 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.devcontainer/Dockerfile +13 -0
- package/.devcontainer/devcontainer.json +52 -0
- package/.github/dependabot.yml +10 -0
- package/.github/workflows/pages.yml +42 -0
- package/.github/workflows/publish.yml +41 -0
- package/.vscode/settings.json +7 -0
- package/LICENSE +9 -0
- package/README.md +9 -0
- package/app/_component/editor.tsx +383 -0
- package/app/layout.tsx +46 -0
- package/app/page.tsx +11 -0
- package/app/r/registry.json/route.ts +22 -0
- package/components/editorx/editor.tsx +1794 -0
- package/components/editorx/extensions/floating-menu.tsx +376 -0
- package/components/editorx/extensions/floating-toolbar.tsx +97 -0
- package/components/editorx/extensions/image-placeholder.tsx +316 -0
- package/components/editorx/extensions/image.tsx +462 -0
- package/components/editorx/extensions/search-and-replace.tsx +438 -0
- package/components/editorx/rich-text-editor.tsx +383 -0
- package/components/editorx/tiptap.css +421 -0
- package/components/editorx/toolbars/alignment.tsx +126 -0
- package/components/editorx/toolbars/blockquote.tsx +47 -0
- package/components/editorx/toolbars/bold.tsx +48 -0
- package/components/editorx/toolbars/bullet-list.tsx +48 -0
- package/components/editorx/toolbars/code-block.tsx +47 -0
- package/components/editorx/toolbars/code.tsx +43 -0
- package/components/editorx/toolbars/color-and-highlight.tsx +215 -0
- package/components/editorx/toolbars/editor-toolbar.tsx +77 -0
- package/components/editorx/toolbars/hard-break.tsx +46 -0
- package/components/editorx/toolbars/headings.tsx +97 -0
- package/components/editorx/toolbars/horizontal-rule.tsx +42 -0
- package/components/editorx/toolbars/image-placeholder-toolbar.tsx +47 -0
- package/components/editorx/toolbars/italic.tsx +48 -0
- package/components/editorx/toolbars/link.tsx +130 -0
- package/components/editorx/toolbars/mobile-toolbar-group.tsx +76 -0
- package/components/editorx/toolbars/ordered-list.tsx +47 -0
- package/components/editorx/toolbars/redo.tsx +44 -0
- package/components/editorx/toolbars/strikethrough.tsx +48 -0
- package/components/editorx/toolbars/toolbar-provider.tsx +29 -0
- package/components/editorx/toolbars/underline.tsx +48 -0
- package/components/editorx/toolbars/undo.tsx +43 -0
- package/components/layout/theme-switcher.tsx +26 -0
- package/components/main-nav.tsx +24 -0
- package/components/mobile-nav.tsx +46 -0
- package/components/open-in-v0-button.tsx +38 -0
- package/components/page-header.tsx +30 -0
- package/components/site-footer.tsx +41 -0
- package/components/site-header.tsx +32 -0
- package/components/theme-provider.tsx +8 -0
- package/components/ui/button.tsx +57 -0
- package/components/ui/checkbox.tsx +30 -0
- package/components/ui/collapsible.tsx +11 -0
- package/components/ui/command.tsx +148 -0
- package/components/ui/dialog.tsx +122 -0
- package/components/ui/drawer.tsx +118 -0
- package/components/ui/dropdown-menu.tsx +201 -0
- package/components/ui/input.tsx +22 -0
- package/components/ui/label.tsx +26 -0
- package/components/ui/popover.tsx +33 -0
- package/components/ui/resizable.tsx +40 -0
- package/components/ui/scroll-area.tsx +42 -0
- package/components/ui/separator.tsx +31 -0
- package/components/ui/sheet.tsx +140 -0
- package/components/ui/sidebar.tsx +763 -0
- package/components/ui/skeleton.tsx +15 -0
- package/components/ui/spinner.tsx +29 -0
- package/components/ui/tabs.tsx +55 -0
- package/components/ui/toggle-group.tsx +61 -0
- package/components/ui/toggle.tsx +45 -0
- package/components/ui/tooltip.tsx +32 -0
- package/components.json +21 -0
- package/config/site.ts +15 -0
- package/eslint.config.mjs +20 -0
- package/hooks/use-character-limit.ts +28 -0
- package/hooks/use-copy-to-clipboard.ts +16 -0
- package/hooks/use-debounce.ts +17 -0
- package/hooks/use-image-upload.ts +97 -0
- package/hooks/use-media-querry.ts +18 -0
- package/hooks/use-mobile.tsx +19 -0
- package/images/editor.png +0 -0
- package/lib/content.ts +39 -0
- package/lib/cookie-client.ts +19 -0
- package/lib/localstorage-client.ts +19 -0
- package/lib/package.ts +144 -0
- package/lib/preferences-config.ts +72 -0
- package/lib/preferences-storage.ts +20 -0
- package/lib/theme-utils.ts +12 -0
- package/lib/theme.ts +50 -0
- package/lib/tiptap-utils.ts +45 -0
- package/lib/utils.ts +11 -0
- package/next-env.d.ts +6 -0
- package/next.config.mjs +11 -0
- package/package.json +92 -0
- package/postcss.config.mjs +8 -0
- package/prettier.config.mjs +15 -0
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/og.webp +0 -0
- package/public/r/editor-x.json +85 -0
- package/public/r/registry.json +93 -0
- package/public/site.webmanifest +19 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/registry/editor/components/editor.tsx +1794 -0
- package/registry/editor/components/extensions/floating-menu.tsx +376 -0
- package/registry/editor/components/extensions/floating-toolbar.tsx +97 -0
- package/registry/editor/components/extensions/image-placeholder.tsx +316 -0
- package/registry/editor/components/extensions/image.tsx +462 -0
- package/registry/editor/components/extensions/search-and-replace.tsx +438 -0
- package/registry/editor/components/rich-text-editor.tsx +383 -0
- package/registry/editor/components/tiptap.css +421 -0
- package/registry/editor/components/toolbars/alignment.tsx +126 -0
- package/registry/editor/components/toolbars/blockquote.tsx +47 -0
- package/registry/editor/components/toolbars/bold.tsx +48 -0
- package/registry/editor/components/toolbars/bullet-list.tsx +48 -0
- package/registry/editor/components/toolbars/code-block.tsx +47 -0
- package/registry/editor/components/toolbars/code.tsx +43 -0
- package/registry/editor/components/toolbars/color-and-highlight.tsx +215 -0
- package/registry/editor/components/toolbars/editor-toolbar.tsx +77 -0
- package/registry/editor/components/toolbars/hard-break.tsx +46 -0
- package/registry/editor/components/toolbars/headings.tsx +97 -0
- package/registry/editor/components/toolbars/horizontal-rule.tsx +42 -0
- package/registry/editor/components/toolbars/image-placeholder-toolbar.tsx +47 -0
- package/registry/editor/components/toolbars/italic.tsx +48 -0
- package/registry/editor/components/toolbars/link.tsx +130 -0
- package/registry/editor/components/toolbars/mobile-toolbar-group.tsx +76 -0
- package/registry/editor/components/toolbars/ordered-list.tsx +47 -0
- package/registry/editor/components/toolbars/redo.tsx +44 -0
- package/registry/editor/components/toolbars/strikethrough.tsx +48 -0
- package/registry/editor/components/toolbars/toolbar-provider.tsx +29 -0
- package/registry/editor/components/toolbars/underline.tsx +48 -0
- package/registry/editor/components/toolbars/undo.tsx +43 -0
- package/registry/editor/components/ui/button.tsx +57 -0
- package/registry/editor/components/ui/checkbox.tsx +30 -0
- package/registry/editor/components/ui/collapsible.tsx +11 -0
- package/registry/editor/components/ui/command.tsx +148 -0
- package/registry/editor/components/ui/dialog.tsx +122 -0
- package/registry/editor/components/ui/drawer.tsx +118 -0
- package/registry/editor/components/ui/dropdown-menu.tsx +201 -0
- package/registry/editor/components/ui/input.tsx +22 -0
- package/registry/editor/components/ui/label.tsx +26 -0
- package/registry/editor/components/ui/popover.tsx +33 -0
- package/registry/editor/components/ui/resizable.tsx +40 -0
- package/registry/editor/components/ui/scroll-area.tsx +42 -0
- package/registry/editor/components/ui/separator.tsx +31 -0
- package/registry/editor/components/ui/sheet.tsx +140 -0
- package/registry/editor/components/ui/sidebar.tsx +763 -0
- package/registry/editor/components/ui/skeleton.tsx +15 -0
- package/registry/editor/components/ui/spinner.tsx +29 -0
- package/registry/editor/components/ui/tabs.tsx +55 -0
- package/registry/editor/components/ui/toggle-group.tsx +61 -0
- package/registry/editor/components/ui/toggle.tsx +45 -0
- package/registry/editor/components/ui/tooltip.tsx +32 -0
- package/registry/editor/hooks/use-character-limit.ts +28 -0
- package/registry/editor/hooks/use-copy-to-clipboard.ts +16 -0
- package/registry/editor/hooks/use-debounce.ts +17 -0
- package/registry/editor/hooks/use-image-upload.ts +97 -0
- package/registry/editor/hooks/use-media-querry.ts +18 -0
- package/registry/editor/hooks/use-mobile.tsx +19 -0
- package/registry/editor/lib/content.ts +39 -0
- package/registry/editor/lib/cookie-client.ts +19 -0
- package/registry/editor/lib/localstorage-client.ts +19 -0
- package/registry/editor/lib/package.ts +144 -0
- package/registry/editor/lib/preferences-config.ts +72 -0
- package/registry/editor/lib/preferences-storage.ts +20 -0
- package/registry/editor/lib/theme-utils.ts +12 -0
- package/registry/editor/lib/theme.ts +50 -0
- package/registry/editor/lib/tiptap-utils.ts +45 -0
- package/registry/editor/lib/utils.ts +11 -0
- package/registry/editor/page.tsx +9 -0
- package/registry.json +93 -0
- package/reset.d.ts +1 -0
- package/scripts/generate-theme-presets.ts +128 -0
- package/scripts/postCreateCommand.sh +0 -0
- package/scripts/theme-boot.tsx +105 -0
- package/server/server-actions.ts +27 -0
- package/stores/preferences/preferences-provider.tsx +55 -0
- package/stores/preferences/preferences-store.ts +23 -0
- package/styles/globals.css +288 -0
- package/styles/presets/brutalist.css +89 -0
- package/styles/presets/soft-pop.css +89 -0
- package/styles/presets/tangerine.css +89 -0
- package/tsconfig.json +50 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Code } from 'lucide-react'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
|
|
6
|
+
import { cn } from '@/lib/utils'
|
|
7
|
+
import { Button, type ButtonProps } from 'components/ui/button'
|
|
8
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from 'components/ui/tooltip'
|
|
9
|
+
import { useToolbar } from './toolbar-provider'
|
|
10
|
+
|
|
11
|
+
const CodeBlockToolbar = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
12
|
+
({ className, onClick, children, ...props }, ref) => {
|
|
13
|
+
const { editor } = useToolbar()
|
|
14
|
+
return (
|
|
15
|
+
<Tooltip>
|
|
16
|
+
<TooltipTrigger asChild>
|
|
17
|
+
<Button
|
|
18
|
+
variant="ghost"
|
|
19
|
+
size="icon"
|
|
20
|
+
type="button"
|
|
21
|
+
className={cn(
|
|
22
|
+
'h-8 w-8 p-0 sm:h-9 sm:w-9',
|
|
23
|
+
editor?.isActive('codeBlock') && 'bg-accent',
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
onClick={(e) => {
|
|
27
|
+
editor?.chain().focus().toggleCodeBlock().run()
|
|
28
|
+
onClick?.(e)
|
|
29
|
+
}}
|
|
30
|
+
disabled={!editor?.can().chain().focus().toggleCodeBlock().run()}
|
|
31
|
+
ref={ref}
|
|
32
|
+
{...props}
|
|
33
|
+
>
|
|
34
|
+
{children ?? <Code className="h-4 w-4" />}
|
|
35
|
+
</Button>
|
|
36
|
+
</TooltipTrigger>
|
|
37
|
+
<TooltipContent>
|
|
38
|
+
<span>Code Block</span>
|
|
39
|
+
</TooltipContent>
|
|
40
|
+
</Tooltip>
|
|
41
|
+
)
|
|
42
|
+
},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
CodeBlockToolbar.displayName = 'CodeBlockToolbar'
|
|
46
|
+
|
|
47
|
+
export { CodeBlockToolbar }
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Code2 } from 'lucide-react'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
|
|
6
|
+
import { cn } from '@/lib/utils'
|
|
7
|
+
import { Button, type ButtonProps } from 'components/ui/button'
|
|
8
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from 'components/ui/tooltip'
|
|
9
|
+
import { useToolbar } from './toolbar-provider'
|
|
10
|
+
|
|
11
|
+
const CodeToolbar = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
12
|
+
({ className, onClick, children, ...props }, ref) => {
|
|
13
|
+
const { editor } = useToolbar()
|
|
14
|
+
return (
|
|
15
|
+
<Tooltip>
|
|
16
|
+
<TooltipTrigger asChild>
|
|
17
|
+
<Button
|
|
18
|
+
variant="ghost"
|
|
19
|
+
size="icon"
|
|
20
|
+
type="button"
|
|
21
|
+
className={cn('h-8 w-8 p-0 sm:h-9 sm:w-9', editor?.isActive('code') && 'bg-accent', className)}
|
|
22
|
+
onClick={(e) => {
|
|
23
|
+
editor?.chain().focus().toggleCode().run()
|
|
24
|
+
onClick?.(e)
|
|
25
|
+
}}
|
|
26
|
+
disabled={!editor?.can().chain().focus().toggleCode().run()}
|
|
27
|
+
ref={ref}
|
|
28
|
+
{...props}
|
|
29
|
+
>
|
|
30
|
+
{children ?? <Code2 className="h-4 w-4" />}
|
|
31
|
+
</Button>
|
|
32
|
+
</TooltipTrigger>
|
|
33
|
+
<TooltipContent>
|
|
34
|
+
<span>Code</span>
|
|
35
|
+
</TooltipContent>
|
|
36
|
+
</Tooltip>
|
|
37
|
+
)
|
|
38
|
+
},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
CodeToolbar.displayName = 'CodeToolbar'
|
|
42
|
+
|
|
43
|
+
export { CodeToolbar }
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
// @ts-nocheck
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
import {
|
|
6
|
+
Popover,
|
|
7
|
+
PopoverContent,
|
|
8
|
+
PopoverTrigger,
|
|
9
|
+
} from "@/components/ui/popover";
|
|
10
|
+
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
11
|
+
import { Separator } from "@/components/ui/separator";
|
|
12
|
+
import {
|
|
13
|
+
Tooltip,
|
|
14
|
+
TooltipContent,
|
|
15
|
+
TooltipTrigger,
|
|
16
|
+
} from "@/components/ui/tooltip";
|
|
17
|
+
import { useMediaQuery } from "@/hooks/use-media-querry";
|
|
18
|
+
import { cn } from "@/lib/utils";
|
|
19
|
+
import type { Extension } from "@tiptap/core";
|
|
20
|
+
import type { ColorOptions } from "@tiptap/extension-color";
|
|
21
|
+
import type { HighlightOptions } from "@tiptap/extension-highlight";
|
|
22
|
+
import { CheckIcon, ChevronDownIcon } from "lucide-react";
|
|
23
|
+
import { MobileToolbarGroup, MobileToolbarItem } from "./mobile-toolbar-group";
|
|
24
|
+
import { useToolbar } from "./toolbar-provider";
|
|
25
|
+
|
|
26
|
+
type TextStylingExtensions =
|
|
27
|
+
| Extension<ColorOptions, any>
|
|
28
|
+
| Extension<HighlightOptions, any>;
|
|
29
|
+
|
|
30
|
+
const TEXT_COLORS = [
|
|
31
|
+
{ name: "Default", color: "var(--editor-text-default)" },
|
|
32
|
+
{ name: "Gray", color: "var(--editor-text-gray)" },
|
|
33
|
+
{ name: "Brown", color: "var(--editor-text-brown)" },
|
|
34
|
+
{ name: "Orange", color: "var(--editor-text-orange)" },
|
|
35
|
+
{ name: "Yellow", color: "var(--editor-text-yellow)" },
|
|
36
|
+
{ name: "Green", color: "var(--editor-text-green)" },
|
|
37
|
+
{ name: "Blue", color: "var(--editor-text-blue)" },
|
|
38
|
+
{ name: "Purple", color: "var(--editor-text-purple)" },
|
|
39
|
+
{ name: "Pink", color: "var(--editor-text-pink)" },
|
|
40
|
+
{ name: "Red", color: "var(--editor-text-red)" },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const HIGHLIGHT_COLORS = [
|
|
44
|
+
{ name: "Default", color: "var(--editor-highlight-default)" },
|
|
45
|
+
{ name: "Gray", color: "var(--editor-highlight-gray)" },
|
|
46
|
+
{ name: "Brown", color: "var(--editor-highlight-brown)" },
|
|
47
|
+
{ name: "Orange", color: "var(--editor-highlight-orange)" },
|
|
48
|
+
{ name: "Yellow", color: "var(--editor-highlight-yellow)" },
|
|
49
|
+
{ name: "Green", color: "var(--editor-highlight-green)" },
|
|
50
|
+
{ name: "Blue", color: "var(--editor-highlight-blue)" },
|
|
51
|
+
{ name: "Purple", color: "var(--editor-highlight-purple)" },
|
|
52
|
+
{ name: "Pink", color: "var(--editor-highlight-pink)" },
|
|
53
|
+
{ name: "Red", color: "var(--editor-highlight-red)" },
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
interface ColorHighlightButtonProps {
|
|
57
|
+
name: string;
|
|
58
|
+
color: string;
|
|
59
|
+
isActive: boolean;
|
|
60
|
+
onClick: () => void;
|
|
61
|
+
isHighlight?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const ColorHighlightButton = ({
|
|
65
|
+
name,
|
|
66
|
+
color,
|
|
67
|
+
isActive,
|
|
68
|
+
onClick,
|
|
69
|
+
isHighlight,
|
|
70
|
+
}: ColorHighlightButtonProps) => (
|
|
71
|
+
<button
|
|
72
|
+
onClick={onClick}
|
|
73
|
+
className="flex w-full items-center justify-between rounded-sm px-2 py-1 text-sm hover:bg-gray-3"
|
|
74
|
+
type="button"
|
|
75
|
+
>
|
|
76
|
+
<div className="flex items-center space-x-2">
|
|
77
|
+
<div
|
|
78
|
+
className="rounded-sm border px-1 py-px font-medium"
|
|
79
|
+
style={isHighlight ? { backgroundColor: color } : { color }}
|
|
80
|
+
>
|
|
81
|
+
A
|
|
82
|
+
</div>
|
|
83
|
+
<span>{name}</span>
|
|
84
|
+
</div>
|
|
85
|
+
{isActive && <CheckIcon className="h-4 w-4" />}
|
|
86
|
+
</button>
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
export const ColorHighlightToolbar = () => {
|
|
90
|
+
const { editor } = useToolbar();
|
|
91
|
+
const isMobile = useMediaQuery("(max-width: 640px)");
|
|
92
|
+
|
|
93
|
+
const currentColor = editor?.getAttributes("textStyle").color;
|
|
94
|
+
const currentHighlight = editor?.getAttributes("highlight").color;
|
|
95
|
+
|
|
96
|
+
const handleSetColor = (color: string) => {
|
|
97
|
+
editor
|
|
98
|
+
?.chain()
|
|
99
|
+
.focus()
|
|
100
|
+
.setColor(color === currentColor ? "" : color)
|
|
101
|
+
.run();
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const handleSetHighlight = (color: string) => {
|
|
105
|
+
editor
|
|
106
|
+
?.chain()
|
|
107
|
+
.focus()
|
|
108
|
+
.setHighlight(color === currentHighlight ? { color: "" } : { color })
|
|
109
|
+
.run();
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const isDisabled =
|
|
113
|
+
!editor?.can().chain().setHighlight().run() ||
|
|
114
|
+
!editor?.can().chain().setColor("").run();
|
|
115
|
+
|
|
116
|
+
if (isMobile) {
|
|
117
|
+
return (
|
|
118
|
+
<div className="flex gap-1">
|
|
119
|
+
<MobileToolbarGroup label="Color">
|
|
120
|
+
{TEXT_COLORS.map(({ name, color }) => (
|
|
121
|
+
<MobileToolbarItem
|
|
122
|
+
key={name}
|
|
123
|
+
onClick={() => handleSetColor(color)}
|
|
124
|
+
active={currentColor === color}
|
|
125
|
+
>
|
|
126
|
+
<div className="flex items-center gap-2">
|
|
127
|
+
<div className="rounded-sm border px-2" style={{ color }}>
|
|
128
|
+
A
|
|
129
|
+
</div>
|
|
130
|
+
<span>{name}</span>
|
|
131
|
+
</div>
|
|
132
|
+
</MobileToolbarItem>
|
|
133
|
+
))}
|
|
134
|
+
</MobileToolbarGroup>
|
|
135
|
+
|
|
136
|
+
<MobileToolbarGroup label="Highlight">
|
|
137
|
+
{HIGHLIGHT_COLORS.map(({ name, color }) => (
|
|
138
|
+
<MobileToolbarItem
|
|
139
|
+
key={name}
|
|
140
|
+
onClick={() => handleSetHighlight(color)}
|
|
141
|
+
active={currentHighlight === color}
|
|
142
|
+
>
|
|
143
|
+
<div className="flex items-center gap-2">
|
|
144
|
+
<div
|
|
145
|
+
className="rounded-sm border px-2"
|
|
146
|
+
style={{ backgroundColor: color }}
|
|
147
|
+
>
|
|
148
|
+
A
|
|
149
|
+
</div>
|
|
150
|
+
<span>{name}</span>
|
|
151
|
+
</div>
|
|
152
|
+
</MobileToolbarItem>
|
|
153
|
+
))}
|
|
154
|
+
</MobileToolbarGroup>
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<Popover>
|
|
161
|
+
<div className="relative h-full">
|
|
162
|
+
<Tooltip>
|
|
163
|
+
<TooltipTrigger asChild>
|
|
164
|
+
<PopoverTrigger disabled={isDisabled} asChild>
|
|
165
|
+
<Button
|
|
166
|
+
variant="ghost"
|
|
167
|
+
size="sm"
|
|
168
|
+
type="button"
|
|
169
|
+
style={{
|
|
170
|
+
color: currentColor,
|
|
171
|
+
}}
|
|
172
|
+
className={cn("h-8 w-14 p-0 font-normal")}
|
|
173
|
+
>
|
|
174
|
+
<span className="text-md">A</span>
|
|
175
|
+
<ChevronDownIcon className="ml-2 h-4 w-4" />
|
|
176
|
+
</Button>
|
|
177
|
+
</PopoverTrigger>
|
|
178
|
+
</TooltipTrigger>
|
|
179
|
+
<TooltipContent>Text Color & Highlight</TooltipContent>
|
|
180
|
+
</Tooltip>
|
|
181
|
+
|
|
182
|
+
<PopoverContent align="start" className="w-56 p-1 dark:bg-gray-2">
|
|
183
|
+
<ScrollArea className="max-h-80 overflow-y-auto pr-2">
|
|
184
|
+
<div className="mb-2.5 mt-2 px-2 text-xs text-gray-11">Color</div>
|
|
185
|
+
{TEXT_COLORS.map(({ name, color }) => (
|
|
186
|
+
<ColorHighlightButton
|
|
187
|
+
key={name}
|
|
188
|
+
name={name}
|
|
189
|
+
color={color}
|
|
190
|
+
isActive={currentColor === color}
|
|
191
|
+
onClick={() => handleSetColor(color)}
|
|
192
|
+
/>
|
|
193
|
+
))}
|
|
194
|
+
|
|
195
|
+
<Separator className="my-3" />
|
|
196
|
+
|
|
197
|
+
<div className="mb-2.5 w-full px-2 pr-3 text-xs text-gray-11">
|
|
198
|
+
Background
|
|
199
|
+
</div>
|
|
200
|
+
{HIGHLIGHT_COLORS.map(({ name, color }) => (
|
|
201
|
+
<ColorHighlightButton
|
|
202
|
+
key={name}
|
|
203
|
+
name={name}
|
|
204
|
+
color={color}
|
|
205
|
+
isActive={currentHighlight === color}
|
|
206
|
+
onClick={() => handleSetHighlight(color)}
|
|
207
|
+
isHighlight
|
|
208
|
+
/>
|
|
209
|
+
))}
|
|
210
|
+
</ScrollArea>
|
|
211
|
+
</PopoverContent>
|
|
212
|
+
</div>
|
|
213
|
+
</Popover>
|
|
214
|
+
);
|
|
215
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useCurrentEditor } from '@tiptap/react'
|
|
4
|
+
import { ScrollArea, ScrollBar } from 'components/ui/scroll-area'
|
|
5
|
+
import { Separator } from 'components/ui/separator'
|
|
6
|
+
import { TooltipProvider } from 'components/ui/tooltip'
|
|
7
|
+
import { AlignmentTooolbar } from './alignment'
|
|
8
|
+
import { BlockquoteToolbar } from './blockquote'
|
|
9
|
+
import { BoldToolbar } from './bold'
|
|
10
|
+
import { BulletListToolbar } from './bullet-list'
|
|
11
|
+
import { CodeToolbar } from './code'
|
|
12
|
+
import { CodeBlockToolbar } from './code-block'
|
|
13
|
+
import { ColorHighlightToolbar } from './color-and-highlight'
|
|
14
|
+
import { HeadingsToolbar } from './headings'
|
|
15
|
+
import { HorizontalRuleToolbar } from './horizontal-rule'
|
|
16
|
+
import { ImagePlaceholderToolbar } from './image-placeholder-toolbar'
|
|
17
|
+
import { ItalicToolbar } from './italic'
|
|
18
|
+
import { LinkToolbar } from './link'
|
|
19
|
+
import { OrderedListToolbar } from './ordered-list'
|
|
20
|
+
import { RedoToolbar } from './redo'
|
|
21
|
+
import { StrikeThroughToolbar } from './strikethrough'
|
|
22
|
+
import { ToolbarProvider } from './toolbar-provider'
|
|
23
|
+
import { UnderlineToolbar } from './underline'
|
|
24
|
+
import { UndoToolbar } from './undo'
|
|
25
|
+
|
|
26
|
+
export const EditorToolbar = () => {
|
|
27
|
+
const { editor } = useCurrentEditor()
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="sticky top-0 z-20 w-full border-b bg-background hidden sm:block">
|
|
31
|
+
<ToolbarProvider editor={editor}>
|
|
32
|
+
<TooltipProvider>
|
|
33
|
+
<ScrollArea className="h-fit py-0.5">
|
|
34
|
+
<div>
|
|
35
|
+
<div className="flex items-center gap-1 px-2">
|
|
36
|
+
{/* History Group */}
|
|
37
|
+
<UndoToolbar />
|
|
38
|
+
<RedoToolbar />
|
|
39
|
+
<Separator orientation="vertical" className="mx-1 h-7" />
|
|
40
|
+
|
|
41
|
+
{/* Text Structure Group */}
|
|
42
|
+
<HeadingsToolbar />
|
|
43
|
+
<BlockquoteToolbar />
|
|
44
|
+
<CodeToolbar />
|
|
45
|
+
<CodeBlockToolbar />
|
|
46
|
+
<Separator orientation="vertical" className="mx-1 h-7" />
|
|
47
|
+
|
|
48
|
+
{/* Basic Formatting Group */}
|
|
49
|
+
<BoldToolbar />
|
|
50
|
+
<ItalicToolbar />
|
|
51
|
+
<UnderlineToolbar />
|
|
52
|
+
<StrikeThroughToolbar />
|
|
53
|
+
<LinkToolbar />
|
|
54
|
+
<Separator orientation="vertical" className="mx-1 h-7" />
|
|
55
|
+
|
|
56
|
+
{/* Lists & Structure Group */}
|
|
57
|
+
<BulletListToolbar />
|
|
58
|
+
<OrderedListToolbar />
|
|
59
|
+
<HorizontalRuleToolbar />
|
|
60
|
+
<Separator orientation="vertical" className="mx-1 h-7" />
|
|
61
|
+
|
|
62
|
+
{/* Alignment Group */}
|
|
63
|
+
<AlignmentTooolbar />
|
|
64
|
+
<Separator orientation="vertical" className="mx-1 h-7" />
|
|
65
|
+
|
|
66
|
+
{/* Media & Styling Group */}
|
|
67
|
+
<ImagePlaceholderToolbar />
|
|
68
|
+
<ColorHighlightToolbar />
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
<ScrollBar className="hidden" orientation="horizontal" />
|
|
72
|
+
</ScrollArea>
|
|
73
|
+
</TooltipProvider>
|
|
74
|
+
</ToolbarProvider>
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { WrapText } from "lucide-react";
|
|
4
|
+
import React from "react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
import { Button, type ButtonProps } from "components/ui/button";
|
|
8
|
+
import {
|
|
9
|
+
Tooltip,
|
|
10
|
+
TooltipContent,
|
|
11
|
+
TooltipTrigger,
|
|
12
|
+
} from "components/ui/tooltip";
|
|
13
|
+
import { useToolbar } from "./toolbar-provider";
|
|
14
|
+
|
|
15
|
+
const HardBreakToolbar = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
16
|
+
({ className, onClick, children, ...props }, ref) => {
|
|
17
|
+
const { editor } = useToolbar();
|
|
18
|
+
return (
|
|
19
|
+
<Tooltip>
|
|
20
|
+
<TooltipTrigger asChild>
|
|
21
|
+
<Button
|
|
22
|
+
variant="ghost"
|
|
23
|
+
size="icon"
|
|
24
|
+
type="button"
|
|
25
|
+
className={cn("h-8 w-8 p-0 sm:h-9 sm:w-9", className)}
|
|
26
|
+
onClick={(e) => {
|
|
27
|
+
editor?.chain().focus().setHardBreak().run();
|
|
28
|
+
onClick?.(e);
|
|
29
|
+
}}
|
|
30
|
+
ref={ref}
|
|
31
|
+
{...props}
|
|
32
|
+
>
|
|
33
|
+
{children ?? <WrapText className="h-4 w-4" />}
|
|
34
|
+
</Button>
|
|
35
|
+
</TooltipTrigger>
|
|
36
|
+
<TooltipContent>
|
|
37
|
+
<span>Hard break</span>
|
|
38
|
+
</TooltipContent>
|
|
39
|
+
</Tooltip>
|
|
40
|
+
);
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
HardBreakToolbar.displayName = "HardBreakToolbar";
|
|
45
|
+
|
|
46
|
+
export { HardBreakToolbar };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Button } from '@/components/ui/button'
|
|
4
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
|
5
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
|
6
|
+
import { useMediaQuery } from '@/hooks/use-media-querry'
|
|
7
|
+
import { cn } from '@/lib/utils'
|
|
8
|
+
import { ChevronDown } from 'lucide-react'
|
|
9
|
+
import React from 'react'
|
|
10
|
+
import { MobileToolbarGroup, MobileToolbarItem } from './mobile-toolbar-group'
|
|
11
|
+
import { useToolbar } from './toolbar-provider'
|
|
12
|
+
|
|
13
|
+
const levels = [1, 2, 3, 4] as const
|
|
14
|
+
|
|
15
|
+
export const HeadingsToolbar = React.forwardRef<HTMLButtonElement, React.ButtonHTMLAttributes<HTMLButtonElement>>(
|
|
16
|
+
({ className, ...props }, ref) => {
|
|
17
|
+
const { editor } = useToolbar()
|
|
18
|
+
const isMobile = useMediaQuery('(max-width: 640px)')
|
|
19
|
+
const activeLevel = levels.find((level) => editor?.isActive('heading', { level }))
|
|
20
|
+
|
|
21
|
+
if (isMobile) {
|
|
22
|
+
return (
|
|
23
|
+
<MobileToolbarGroup label={activeLevel ? `H${activeLevel}` : 'Normal'}>
|
|
24
|
+
<MobileToolbarItem
|
|
25
|
+
onClick={() => editor?.chain().focus().setParagraph().run()}
|
|
26
|
+
active={!editor?.isActive('heading')}
|
|
27
|
+
>
|
|
28
|
+
Normal
|
|
29
|
+
</MobileToolbarItem>
|
|
30
|
+
{levels.map((level) => (
|
|
31
|
+
<MobileToolbarItem
|
|
32
|
+
key={level}
|
|
33
|
+
onClick={() => editor?.chain().focus().toggleHeading({ level }).run()}
|
|
34
|
+
active={editor?.isActive('heading', { level })}
|
|
35
|
+
>
|
|
36
|
+
H{level}
|
|
37
|
+
</MobileToolbarItem>
|
|
38
|
+
))}
|
|
39
|
+
</MobileToolbarGroup>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Tooltip>
|
|
45
|
+
<TooltipTrigger asChild>
|
|
46
|
+
<DropdownMenu>
|
|
47
|
+
<DropdownMenuTrigger asChild>
|
|
48
|
+
<Button
|
|
49
|
+
variant="ghost"
|
|
50
|
+
size="sm"
|
|
51
|
+
type="button"
|
|
52
|
+
className={cn(
|
|
53
|
+
'h-8 w-max gap-1 px-3 font-normal',
|
|
54
|
+
editor?.isActive('heading') && 'bg-accent',
|
|
55
|
+
className,
|
|
56
|
+
)}
|
|
57
|
+
ref={ref}
|
|
58
|
+
{...props}
|
|
59
|
+
>
|
|
60
|
+
{activeLevel ? `H${activeLevel}` : 'Normal'}
|
|
61
|
+
<ChevronDown className="h-4 w-4" />
|
|
62
|
+
</Button>
|
|
63
|
+
</DropdownMenuTrigger>
|
|
64
|
+
<DropdownMenuContent align="start">
|
|
65
|
+
<DropdownMenuItem
|
|
66
|
+
onClick={() => editor?.chain().focus().setParagraph().run()}
|
|
67
|
+
className={cn(
|
|
68
|
+
'flex items-center gap-2 h-fit',
|
|
69
|
+
!editor?.isActive('heading') && 'bg-accent',
|
|
70
|
+
)}
|
|
71
|
+
>
|
|
72
|
+
Normal
|
|
73
|
+
</DropdownMenuItem>
|
|
74
|
+
{levels.map((level) => (
|
|
75
|
+
<DropdownMenuItem
|
|
76
|
+
key={level}
|
|
77
|
+
onClick={() => editor?.chain().focus().toggleHeading({ level }).run()}
|
|
78
|
+
className={cn(
|
|
79
|
+
'flex items-center gap-2',
|
|
80
|
+
editor?.isActive('heading', { level }) && 'bg-accent',
|
|
81
|
+
)}
|
|
82
|
+
>
|
|
83
|
+
H{level}
|
|
84
|
+
</DropdownMenuItem>
|
|
85
|
+
))}
|
|
86
|
+
</DropdownMenuContent>
|
|
87
|
+
</DropdownMenu>
|
|
88
|
+
</TooltipTrigger>
|
|
89
|
+
<TooltipContent>
|
|
90
|
+
<span>Headings</span>
|
|
91
|
+
</TooltipContent>
|
|
92
|
+
</Tooltip>
|
|
93
|
+
)
|
|
94
|
+
},
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
HeadingsToolbar.displayName = 'HeadingsToolbar'
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { SeparatorHorizontal } from 'lucide-react'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
|
|
6
|
+
import { cn } from '@/lib/utils'
|
|
7
|
+
import { Button, type ButtonProps } from 'components/ui/button'
|
|
8
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from 'components/ui/tooltip'
|
|
9
|
+
import { useToolbar } from './toolbar-provider'
|
|
10
|
+
|
|
11
|
+
const HorizontalRuleToolbar = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
12
|
+
({ className, onClick, children, ...props }, ref) => {
|
|
13
|
+
const { editor } = useToolbar()
|
|
14
|
+
return (
|
|
15
|
+
<Tooltip>
|
|
16
|
+
<TooltipTrigger asChild>
|
|
17
|
+
<Button
|
|
18
|
+
variant="ghost"
|
|
19
|
+
size="icon"
|
|
20
|
+
type="button"
|
|
21
|
+
className={cn('h-8 w-8 p-0 sm:h-9 sm:w-9', className)}
|
|
22
|
+
onClick={(e) => {
|
|
23
|
+
editor?.chain().focus().setHorizontalRule().run()
|
|
24
|
+
onClick?.(e)
|
|
25
|
+
}}
|
|
26
|
+
ref={ref}
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
{children ?? <SeparatorHorizontal className="h-4 w-4" />}
|
|
30
|
+
</Button>
|
|
31
|
+
</TooltipTrigger>
|
|
32
|
+
<TooltipContent>
|
|
33
|
+
<span>Horizontal Rule</span>
|
|
34
|
+
</TooltipContent>
|
|
35
|
+
</Tooltip>
|
|
36
|
+
)
|
|
37
|
+
},
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
HorizontalRuleToolbar.displayName = 'HorizontalRuleToolbar'
|
|
41
|
+
|
|
42
|
+
export { HorizontalRuleToolbar }
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Image } from 'lucide-react'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
|
|
6
|
+
import { cn } from '@/lib/utils'
|
|
7
|
+
import { Button, type ButtonProps } from 'components/ui/button'
|
|
8
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from 'components/ui/tooltip'
|
|
9
|
+
import { useToolbar } from './toolbar-provider'
|
|
10
|
+
|
|
11
|
+
const ImagePlaceholderToolbar = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
12
|
+
({ className, onClick, children, ...props }, ref) => {
|
|
13
|
+
const { editor } = useToolbar()
|
|
14
|
+
return (
|
|
15
|
+
<Tooltip>
|
|
16
|
+
<TooltipTrigger asChild>
|
|
17
|
+
<Button
|
|
18
|
+
variant="ghost"
|
|
19
|
+
size="icon"
|
|
20
|
+
type="button"
|
|
21
|
+
className={cn(
|
|
22
|
+
'h-8 w-8 p-0 sm:h-9 sm:w-9',
|
|
23
|
+
editor?.isActive('image-placeholder') && 'bg-accent',
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
onClick={(e) => {
|
|
27
|
+
e.preventDefault()
|
|
28
|
+
editor?.chain().focus().insertImagePlaceholder().run()
|
|
29
|
+
onClick?.(e)
|
|
30
|
+
}}
|
|
31
|
+
ref={ref}
|
|
32
|
+
{...props}
|
|
33
|
+
>
|
|
34
|
+
{children ?? <Image className="h-4 w-4" />}
|
|
35
|
+
</Button>
|
|
36
|
+
</TooltipTrigger>
|
|
37
|
+
<TooltipContent>
|
|
38
|
+
<span>Image</span>
|
|
39
|
+
</TooltipContent>
|
|
40
|
+
</Tooltip>
|
|
41
|
+
)
|
|
42
|
+
},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
ImagePlaceholderToolbar.displayName = 'ImagePlaceholderToolbar'
|
|
46
|
+
|
|
47
|
+
export { ImagePlaceholderToolbar }
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { ItalicIcon } from 'lucide-react'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
|
|
6
|
+
import { cn } from '@/lib/utils'
|
|
7
|
+
import { Button, type ButtonProps } from 'components/ui/button'
|
|
8
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from 'components/ui/tooltip'
|
|
9
|
+
import { useToolbar } from './toolbar-provider'
|
|
10
|
+
|
|
11
|
+
const ItalicToolbar = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
12
|
+
({ className, onClick, children, ...props }, ref) => {
|
|
13
|
+
const { editor } = useToolbar()
|
|
14
|
+
return (
|
|
15
|
+
<Tooltip>
|
|
16
|
+
<TooltipTrigger asChild>
|
|
17
|
+
<Button
|
|
18
|
+
variant="ghost"
|
|
19
|
+
size="icon"
|
|
20
|
+
type="button"
|
|
21
|
+
className={cn(
|
|
22
|
+
'h-8 w-8 p-0 sm:h-9 sm:w-9',
|
|
23
|
+
editor?.isActive('italic') && 'bg-accent',
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
onClick={(e) => {
|
|
27
|
+
editor?.chain().focus().toggleItalic().run()
|
|
28
|
+
onClick?.(e)
|
|
29
|
+
}}
|
|
30
|
+
disabled={!editor?.can().chain().focus().toggleItalic().run()}
|
|
31
|
+
ref={ref}
|
|
32
|
+
{...props}
|
|
33
|
+
>
|
|
34
|
+
{children ?? <ItalicIcon className="h-4 w-4" />}
|
|
35
|
+
</Button>
|
|
36
|
+
</TooltipTrigger>
|
|
37
|
+
<TooltipContent>
|
|
38
|
+
<span>Italic</span>
|
|
39
|
+
<span className="ml-1 text-xs text-gray-11">(cmd + i)</span>
|
|
40
|
+
</TooltipContent>
|
|
41
|
+
</Tooltip>
|
|
42
|
+
)
|
|
43
|
+
},
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
ItalicToolbar.displayName = 'ItalicToolbar'
|
|
47
|
+
|
|
48
|
+
export { ItalicToolbar }
|