@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,15 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils"
|
|
2
|
+
|
|
3
|
+
function Skeleton({
|
|
4
|
+
className,
|
|
5
|
+
...props
|
|
6
|
+
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
7
|
+
return (
|
|
8
|
+
<div
|
|
9
|
+
className={cn("animate-pulse rounded-md bg-primary/10", className)}
|
|
10
|
+
{...props}
|
|
11
|
+
/>
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { Skeleton }
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type VariantProps, cva } from "class-variance-authority";
|
|
3
|
+
import { type LucideProps, Loader2Icon } from "lucide-react";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
const spinnerVariants = cva("animate-spin text-muted-foreground", {
|
|
7
|
+
defaultVariants: {
|
|
8
|
+
size: "default",
|
|
9
|
+
},
|
|
10
|
+
variants: {
|
|
11
|
+
size: {
|
|
12
|
+
default: "size-4",
|
|
13
|
+
icon: "size-10",
|
|
14
|
+
lg: "size-6",
|
|
15
|
+
sm: "size-2",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const Spinner = ({
|
|
21
|
+
className,
|
|
22
|
+
size,
|
|
23
|
+
...props
|
|
24
|
+
}: Partial<LucideProps & VariantProps<typeof spinnerVariants>>) => (
|
|
25
|
+
<Loader2Icon
|
|
26
|
+
className={cn(spinnerVariants({ size }), className)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
const Tabs = TabsPrimitive.Root
|
|
9
|
+
|
|
10
|
+
const TabsList = React.forwardRef<
|
|
11
|
+
React.ElementRef<typeof TabsPrimitive.List>,
|
|
12
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
|
13
|
+
>(({ className, ...props }, ref) => (
|
|
14
|
+
<TabsPrimitive.List
|
|
15
|
+
ref={ref}
|
|
16
|
+
className={cn(
|
|
17
|
+
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
))
|
|
23
|
+
TabsList.displayName = TabsPrimitive.List.displayName
|
|
24
|
+
|
|
25
|
+
const TabsTrigger = React.forwardRef<
|
|
26
|
+
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
|
27
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
|
28
|
+
>(({ className, ...props }, ref) => (
|
|
29
|
+
<TabsPrimitive.Trigger
|
|
30
|
+
ref={ref}
|
|
31
|
+
className={cn(
|
|
32
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
|
|
33
|
+
className
|
|
34
|
+
)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
))
|
|
38
|
+
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
|
39
|
+
|
|
40
|
+
const TabsContent = React.forwardRef<
|
|
41
|
+
React.ElementRef<typeof TabsPrimitive.Content>,
|
|
42
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
|
43
|
+
>(({ className, ...props }, ref) => (
|
|
44
|
+
<TabsPrimitive.Content
|
|
45
|
+
ref={ref}
|
|
46
|
+
className={cn(
|
|
47
|
+
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
48
|
+
className
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
))
|
|
53
|
+
TabsContent.displayName = TabsPrimitive.Content.displayName
|
|
54
|
+
|
|
55
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
|
|
4
|
+
import { type VariantProps } from "class-variance-authority"
|
|
5
|
+
import * as React from "react"
|
|
6
|
+
|
|
7
|
+
import { cn } from "@/lib/utils"
|
|
8
|
+
import { toggleVariants } from "components/ui/toggle"
|
|
9
|
+
|
|
10
|
+
const ToggleGroupContext = React.createContext<
|
|
11
|
+
VariantProps<typeof toggleVariants>
|
|
12
|
+
>({
|
|
13
|
+
size: "default",
|
|
14
|
+
variant: "default",
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const ToggleGroup = React.forwardRef<
|
|
18
|
+
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
|
19
|
+
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
|
|
20
|
+
VariantProps<typeof toggleVariants>
|
|
21
|
+
>(({ className, variant, size, children, ...props }, ref) => (
|
|
22
|
+
<ToggleGroupPrimitive.Root
|
|
23
|
+
ref={ref}
|
|
24
|
+
className={cn("flex items-center justify-center gap-1", className)}
|
|
25
|
+
{...props}
|
|
26
|
+
>
|
|
27
|
+
<ToggleGroupContext.Provider value={{ variant, size }}>
|
|
28
|
+
{children}
|
|
29
|
+
</ToggleGroupContext.Provider>
|
|
30
|
+
</ToggleGroupPrimitive.Root>
|
|
31
|
+
))
|
|
32
|
+
|
|
33
|
+
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
|
|
34
|
+
|
|
35
|
+
const ToggleGroupItem = React.forwardRef<
|
|
36
|
+
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
|
37
|
+
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
|
|
38
|
+
VariantProps<typeof toggleVariants>
|
|
39
|
+
>(({ className, children, variant, size, ...props }, ref) => {
|
|
40
|
+
const context = React.useContext(ToggleGroupContext)
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<ToggleGroupPrimitive.Item
|
|
44
|
+
ref={ref}
|
|
45
|
+
className={cn(
|
|
46
|
+
toggleVariants({
|
|
47
|
+
variant: context.variant || variant,
|
|
48
|
+
size: context.size || size,
|
|
49
|
+
}),
|
|
50
|
+
className
|
|
51
|
+
)}
|
|
52
|
+
{...props}
|
|
53
|
+
>
|
|
54
|
+
{children}
|
|
55
|
+
</ToggleGroupPrimitive.Item>
|
|
56
|
+
)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
|
|
60
|
+
|
|
61
|
+
export { ToggleGroup, ToggleGroupItem }
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as TogglePrimitive from "@radix-ui/react-toggle"
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
6
|
+
|
|
7
|
+
import { cn } from "@/lib/utils"
|
|
8
|
+
|
|
9
|
+
const toggleVariants = cva(
|
|
10
|
+
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
11
|
+
{
|
|
12
|
+
variants: {
|
|
13
|
+
variant: {
|
|
14
|
+
default: "bg-transparent",
|
|
15
|
+
outline:
|
|
16
|
+
"border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
|
|
17
|
+
},
|
|
18
|
+
size: {
|
|
19
|
+
default: "h-9 px-2 min-w-9",
|
|
20
|
+
sm: "h-8 px-1.5 min-w-8",
|
|
21
|
+
lg: "h-10 px-2.5 min-w-10",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultVariants: {
|
|
25
|
+
variant: "default",
|
|
26
|
+
size: "default",
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const Toggle = React.forwardRef<
|
|
32
|
+
React.ElementRef<typeof TogglePrimitive.Root>,
|
|
33
|
+
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
|
|
34
|
+
VariantProps<typeof toggleVariants>
|
|
35
|
+
>(({ className, variant, size, ...props }, ref) => (
|
|
36
|
+
<TogglePrimitive.Root
|
|
37
|
+
ref={ref}
|
|
38
|
+
className={cn(toggleVariants({ variant, size, className }))}
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
))
|
|
42
|
+
|
|
43
|
+
Toggle.displayName = TogglePrimitive.Root.displayName
|
|
44
|
+
|
|
45
|
+
export { Toggle, toggleVariants }
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
const TooltipProvider = TooltipPrimitive.Provider
|
|
9
|
+
|
|
10
|
+
const Tooltip = TooltipPrimitive.Root
|
|
11
|
+
|
|
12
|
+
const TooltipTrigger = TooltipPrimitive.Trigger
|
|
13
|
+
|
|
14
|
+
const TooltipContent = React.forwardRef<
|
|
15
|
+
React.ElementRef<typeof TooltipPrimitive.Content>,
|
|
16
|
+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
|
17
|
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
18
|
+
<TooltipPrimitive.Portal>
|
|
19
|
+
<TooltipPrimitive.Content
|
|
20
|
+
ref={ref}
|
|
21
|
+
sideOffset={sideOffset}
|
|
22
|
+
className={cn(
|
|
23
|
+
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
24
|
+
className
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
</TooltipPrimitive.Portal>
|
|
29
|
+
))
|
|
30
|
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
|
31
|
+
|
|
32
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
package/components.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "new-york",
|
|
4
|
+
"rsc": true,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "tailwind.config.ts",
|
|
8
|
+
"css": "app/globals.css",
|
|
9
|
+
"baseColor": "stone",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"aliases": {
|
|
14
|
+
"components": "@/components",
|
|
15
|
+
"utils": "@/lib/utils",
|
|
16
|
+
"ui": "@/components/ui",
|
|
17
|
+
"lib": "@/lib",
|
|
18
|
+
"hooks": "@/hooks"
|
|
19
|
+
},
|
|
20
|
+
"iconLibrary": "lucide"
|
|
21
|
+
}
|
package/config/site.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const siteConfig = {
|
|
2
|
+
name: 'Editor X',
|
|
3
|
+
url: 'https://open-cloud-initiative.github.io/editor-x/',
|
|
4
|
+
author: 'Sebastian Döll (@katallaxie)',
|
|
5
|
+
authorUrl: 'https://github.com/katallaxie',
|
|
6
|
+
links: {
|
|
7
|
+
github: 'https://github.com/open-cloud-initiative/editor-x',
|
|
8
|
+
},
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type SiteConfig = typeof siteConfig
|
|
12
|
+
|
|
13
|
+
export const navConfig = {
|
|
14
|
+
mainNav: [{ title: siteConfig.name, path: '/' }],
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import nextVitals from 'eslint-config-next/core-web-vitals'
|
|
2
|
+
import prettier from 'eslint-config-prettier/flat'
|
|
3
|
+
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
|
|
4
|
+
import { defineConfig, globalIgnores } from 'eslint/config'
|
|
5
|
+
|
|
6
|
+
const eslintConfig = defineConfig([
|
|
7
|
+
...nextVitals,
|
|
8
|
+
prettier,
|
|
9
|
+
eslintPluginPrettierRecommended,
|
|
10
|
+
// Override default ignores of eslint-config-next.
|
|
11
|
+
globalIgnores([
|
|
12
|
+
// Default ignores of eslint-config-next:
|
|
13
|
+
'.next/**',
|
|
14
|
+
'out/**',
|
|
15
|
+
'build/**',
|
|
16
|
+
'next-env.d.ts',
|
|
17
|
+
]),
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
export default eslintConfig
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ChangeEvent, useState } from "react";
|
|
4
|
+
|
|
5
|
+
type UseCharacterLimitProps = {
|
|
6
|
+
maxLength: number;
|
|
7
|
+
initialValue?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function useCharacterLimit({ maxLength, initialValue = "" }: UseCharacterLimitProps) {
|
|
11
|
+
const [value, setValue] = useState(initialValue);
|
|
12
|
+
const [characterCount, setCharacterCount] = useState(initialValue.length);
|
|
13
|
+
|
|
14
|
+
const handleChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
15
|
+
const newValue = e.target.value;
|
|
16
|
+
if (newValue.length <= maxLength) {
|
|
17
|
+
setValue(newValue);
|
|
18
|
+
setCharacterCount(newValue.length);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
value,
|
|
24
|
+
characterCount,
|
|
25
|
+
handleChange,
|
|
26
|
+
maxLength,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import { useState, useCallback } from "react"
|
|
3
|
+
|
|
4
|
+
export function useCopyToClipboard() {
|
|
5
|
+
const [isCopied, setIsCopied] = useState(false)
|
|
6
|
+
|
|
7
|
+
const copyToClipboard = useCallback((text: string) => {
|
|
8
|
+
if (typeof window === "undefined") return
|
|
9
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
10
|
+
setIsCopied(true)
|
|
11
|
+
setTimeout(() => setIsCopied(false), 2000)
|
|
12
|
+
})
|
|
13
|
+
}, [])
|
|
14
|
+
|
|
15
|
+
return { copyToClipboard, isCopied }
|
|
16
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
export function useDebounce<T>(value: T, delay: number): T {
|
|
4
|
+
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const timer = setTimeout(() => {
|
|
8
|
+
setDebouncedValue(value);
|
|
9
|
+
}, delay);
|
|
10
|
+
|
|
11
|
+
return () => {
|
|
12
|
+
clearTimeout(timer);
|
|
13
|
+
};
|
|
14
|
+
}, [value, delay]);
|
|
15
|
+
|
|
16
|
+
return debouncedValue;
|
|
17
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
interface UseImageUploadProps {
|
|
4
|
+
onUpload?: (url: string) => void;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function useImageUpload({ onUpload }: UseImageUploadProps = {}) {
|
|
8
|
+
const previewRef = useRef<string | null>(null);
|
|
9
|
+
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
10
|
+
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
|
|
11
|
+
const [fileName, setFileName] = useState<string | null>(null);
|
|
12
|
+
const [uploading, setUploading] = useState(false);
|
|
13
|
+
const [error, setError] = useState<string | null>(null);
|
|
14
|
+
|
|
15
|
+
// Dummy upload function that simulates a delay and returns the local preview URL
|
|
16
|
+
const dummyUpload = async (file: File, localUrl: string): Promise<string> => {
|
|
17
|
+
try {
|
|
18
|
+
setUploading(true);
|
|
19
|
+
// Simulate network delay
|
|
20
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
21
|
+
|
|
22
|
+
// Simulate random upload errors (20% chance)
|
|
23
|
+
if (Math.random() < 0.2) {
|
|
24
|
+
throw new Error("Upload failed - This is a demo error");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
setError(null);
|
|
28
|
+
// In a real implementation, this would be the URL from the server
|
|
29
|
+
return localUrl;
|
|
30
|
+
} catch (err) {
|
|
31
|
+
const errorMessage = err instanceof Error ? err.message : "Upload failed";
|
|
32
|
+
setError(errorMessage);
|
|
33
|
+
throw new Error(errorMessage);
|
|
34
|
+
} finally {
|
|
35
|
+
setUploading(false);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const handleThumbnailClick = useCallback(() => {
|
|
40
|
+
fileInputRef.current?.click();
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
const handleFileChange = useCallback(
|
|
44
|
+
async (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
45
|
+
const file = event.target.files?.[0];
|
|
46
|
+
if (file) {
|
|
47
|
+
setFileName(file.name);
|
|
48
|
+
const localUrl = URL.createObjectURL(file);
|
|
49
|
+
setPreviewUrl(localUrl);
|
|
50
|
+
previewRef.current = localUrl;
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const uploadedUrl = await dummyUpload(file, localUrl);
|
|
54
|
+
onUpload?.(uploadedUrl);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
URL.revokeObjectURL(localUrl);
|
|
57
|
+
setPreviewUrl(null);
|
|
58
|
+
setFileName(null);
|
|
59
|
+
return console.error(err)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
[onUpload]
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const handleRemove = useCallback(() => {
|
|
67
|
+
if (previewUrl) {
|
|
68
|
+
URL.revokeObjectURL(previewUrl);
|
|
69
|
+
}
|
|
70
|
+
setPreviewUrl(null);
|
|
71
|
+
setFileName(null);
|
|
72
|
+
previewRef.current = null;
|
|
73
|
+
if (fileInputRef.current) {
|
|
74
|
+
fileInputRef.current.value = "";
|
|
75
|
+
}
|
|
76
|
+
setError(null);
|
|
77
|
+
}, [previewUrl]);
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
return () => {
|
|
81
|
+
if (previewRef.current) {
|
|
82
|
+
URL.revokeObjectURL(previewRef.current);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}, []);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
previewUrl,
|
|
89
|
+
fileName,
|
|
90
|
+
fileInputRef,
|
|
91
|
+
handleThumbnailClick,
|
|
92
|
+
handleFileChange,
|
|
93
|
+
handleRemove,
|
|
94
|
+
uploading,
|
|
95
|
+
error,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
export function useMediaQuery(query: string): boolean {
|
|
4
|
+
const [matches, setMatches] = useState(false)
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const media = window.matchMedia(query)
|
|
8
|
+
if (media.matches !== matches) {
|
|
9
|
+
setMatches(media.matches)
|
|
10
|
+
}
|
|
11
|
+
const listener = () => setMatches(media.matches)
|
|
12
|
+
window.addEventListener('resize', listener)
|
|
13
|
+
return () => window.removeEventListener('resize', listener)
|
|
14
|
+
}, [matches, query])
|
|
15
|
+
|
|
16
|
+
return matches
|
|
17
|
+
}
|
|
18
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
const MOBILE_BREAKPOINT = 768
|
|
4
|
+
|
|
5
|
+
export function useIsMobile() {
|
|
6
|
+
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
|
7
|
+
|
|
8
|
+
React.useEffect(() => {
|
|
9
|
+
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
|
10
|
+
const onChange = () => {
|
|
11
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
12
|
+
}
|
|
13
|
+
mql.addEventListener("change", onChange)
|
|
14
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
15
|
+
return () => mql.removeEventListener("change", onChange)
|
|
16
|
+
}, [])
|
|
17
|
+
|
|
18
|
+
return !!isMobile
|
|
19
|
+
}
|
|
Binary file
|
package/lib/content.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const content = `
|
|
2
|
+
<h1>Explore the Tiptap rich text editor with Shadcn UI components 📝</h1>
|
|
3
|
+
<p>This is a powerful editor that supports many features:</p>
|
|
4
|
+
<ul class="list-disc">
|
|
5
|
+
<li>
|
|
6
|
+
<p>
|
|
7
|
+
Rich text formatting with <strong>bold</strong>, <em>italic</em>, and
|
|
8
|
+
<u>underline</u>
|
|
9
|
+
</p>
|
|
10
|
+
</li>
|
|
11
|
+
<li>
|
|
12
|
+
<p>Different heading levels</p>
|
|
13
|
+
</li>
|
|
14
|
+
<li>
|
|
15
|
+
<p>Lists (ordered and unordered)</p>
|
|
16
|
+
</li>
|
|
17
|
+
<li>
|
|
18
|
+
<p>Text alignment options</p>
|
|
19
|
+
</li>
|
|
20
|
+
<li>
|
|
21
|
+
<p>Image uploads and management</p>
|
|
22
|
+
</li>
|
|
23
|
+
</ul>
|
|
24
|
+
<h2>Try It Out!</h2>
|
|
25
|
+
<p>
|
|
26
|
+
Type '/' to see available commands or use the toolbar above to format your
|
|
27
|
+
content.
|
|
28
|
+
</p>
|
|
29
|
+
<img src="https://res.cloudinary.com/sham007/image/upload/v1737910103/ProfileImages/724shots_so.jpg" alt="Example image"
|
|
30
|
+
width="100%" align="center" caption="SAAS landing page template" aspectratio="1.640340218712029" />
|
|
31
|
+
<p>
|
|
32
|
+
You can also add links like
|
|
33
|
+
<a target="_blank" rel="noopener noreferrer nofollow" href="https://ehtisham.vercel.app">this one</a>
|
|
34
|
+
and use text alignment options.
|
|
35
|
+
</p>
|
|
36
|
+
<pre><code class="language-javascript">// Example code block
|
|
37
|
+
const greeting = "Hello, World!";
|
|
38
|
+
console.log(greeting);</code></pre>
|
|
39
|
+
`
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Client-side cookie utilities.
|
|
2
|
+
// These functions manage cookies in the browser only.
|
|
3
|
+
// Server actions handle cookie updates on the server side.
|
|
4
|
+
|
|
5
|
+
export function setClientCookie(key: string, value: string, days = 7) {
|
|
6
|
+
const expires = new Date(Date.now() + days * 864e5).toUTCString()
|
|
7
|
+
document.cookie = `${key}=${value}; expires=${expires}; path=/`
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getClientCookie(key: string) {
|
|
11
|
+
return document.cookie
|
|
12
|
+
.split('; ')
|
|
13
|
+
.find((row) => row.startsWith(`${key}=`))
|
|
14
|
+
?.split('=')[1]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function deleteClientCookie(key: string) {
|
|
18
|
+
document.cookie = `${key}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/`
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
export function setLocalStorageValue(key: string, value: string) {
|
|
4
|
+
try {
|
|
5
|
+
window.localStorage.setItem(key, value)
|
|
6
|
+
} catch (error) {
|
|
7
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
8
|
+
console.error('[localStorage] Failed to write value:', error)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getLocalStorageValue(key: string): string | null {
|
|
14
|
+
try {
|
|
15
|
+
return window.localStorage.getItem(key)
|
|
16
|
+
} catch {
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
}
|