@moontra/moonui-pro 2.8.10 → 2.8.12
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/dist/index.mjs +251 -76
- package/package.json +1 -1
- package/src/components/rich-text-editor/index.tsx +273 -50
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { twMerge } from 'tailwind-merge';
|
|
|
3
3
|
import * as t from 'react';
|
|
4
4
|
import t__default, { useState, useRef, useCallback, forwardRef, createContext, useEffect, useContext, useMemo, useLayoutEffect, useDebugValue, Component } from 'react';
|
|
5
5
|
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
|
6
|
-
import { ChevronDown, Info, AlertCircle, AlertTriangle, Check, X, MoreHorizontal, Loader2, Minus, Search, ChevronRight, Circle, ChevronUp, Lock, Sparkles, Plus, CreditCard, Globe, CheckCircle2, XCircle, RotateCcw, Download, Clock, HelpCircle, ChevronLeft, Calendar as Calendar$1, Edit, Trash2, MapPin, User, GripVertical, MessageCircle, Paperclip, Bold as Bold$1, Italic as Italic$1, Underline as Underline$1, Strikethrough, Code as Code$1, Type, Heading1, Heading2, Heading3, AlignLeft, AlignCenter, AlignRight, AlignJustify, List, ListOrdered, CheckSquare, Quote, Palette, Highlighter, Link2, Image as Image$1, Table as Table$1, Settings, Undo, Redo, Eye, RefreshCw, Wand2, Maximize, FileText, Briefcase, MessageSquare, Heart, GraduationCap, Zap, Languages, Lightbulb, TrendingUp, TrendingDown, ZoomOut, ZoomIn, FileSpreadsheet, FileJson, Maximize2, Move, Menu, Bell, CheckCheck, CheckCircle, Settings2, LogOut, Edit3, LayoutGrid, Upload, Share2, Save, Filter, FileDown, ArrowUp, ArrowDown, ArrowUpDown, ChevronsLeft, ChevronsRight, Pin, Sun, Moon, Monitor, Star, ExternalLink, CalendarIcon, DollarSign, Users, Github, GitFork, Activity, Server, EyeOff, RotateCw, Timer, Cpu, MemoryStick, HardDrive, Network, BarChart3, Video, Music, Archive, File, Columns, Grip, Unlock, Minimize2, Map as Map$1, Target, MoreVertical, BellOff, ArrowDownRight, ArrowUpRight } from 'lucide-react';
|
|
6
|
+
import { ChevronDown, Info, AlertCircle, AlertTriangle, Check, X, MoreHorizontal, Loader2, Minus, Search, ChevronRight, Circle, ChevronUp, Lock, Sparkles, Plus, CreditCard, Globe, CheckCircle2, XCircle, RotateCcw, Download, Clock, HelpCircle, ChevronLeft, Calendar as Calendar$1, Edit, Trash2, MapPin, User, GripVertical, MessageCircle, Paperclip, Bold as Bold$1, Italic as Italic$1, Underline as Underline$1, Strikethrough, Code as Code$1, Type, Heading1, Heading2, Heading3, AlignLeft, AlignCenter, AlignRight, AlignJustify, List, ListOrdered, CheckSquare, Quote, Palette, Highlighter, Link2, Image as Image$1, Table as Table$1, Settings, Undo, Redo, Eye, RefreshCw, Wand2, Maximize, FileText, Briefcase, MessageSquare, Heart, GraduationCap, Zap, Languages, Lightbulb, Copy, TrendingUp, TrendingDown, ZoomOut, ZoomIn, FileSpreadsheet, FileJson, Maximize2, Move, Menu, Bell, CheckCheck, CheckCircle, Settings2, LogOut, Edit3, LayoutGrid, Upload, Share2, Save, Filter, FileDown, ArrowUp, ArrowDown, ArrowUpDown, ChevronsLeft, ChevronsRight, Pin, Sun, Moon, Monitor, Star, ExternalLink, CalendarIcon, DollarSign, Users, Github, GitFork, Activity, Server, EyeOff, RotateCw, Timer, Cpu, MemoryStick, HardDrive, Network, BarChart3, Video, Music, Archive, File, Columns, Grip, Unlock, Minimize2, Map as Map$1, Target, MoreVertical, BellOff, ArrowDownRight, ArrowUpRight } from 'lucide-react';
|
|
7
7
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
8
|
import { cva } from 'class-variance-authority';
|
|
9
9
|
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
|
@@ -4044,7 +4044,7 @@ var me = t.forwardRef((r2, o) => {
|
|
|
4044
4044
|
var e;
|
|
4045
4045
|
return Array.from(((e = I.current) == null ? void 0 : e.querySelectorAll(ce)) || []);
|
|
4046
4046
|
}
|
|
4047
|
-
function
|
|
4047
|
+
function X20(e) {
|
|
4048
4048
|
let s = V()[e];
|
|
4049
4049
|
s && E.setState("value", s.getAttribute(T));
|
|
4050
4050
|
}
|
|
@@ -4059,10 +4059,10 @@ var me = t.forwardRef((r2, o) => {
|
|
|
4059
4059
|
s = e > 0 ? we(s, N) : De(s, N), i = s == null ? void 0 : s.querySelector(ce);
|
|
4060
4060
|
i ? E.setState("value", i.getAttribute(T)) : Q(e);
|
|
4061
4061
|
}
|
|
4062
|
-
let oe = () =>
|
|
4062
|
+
let oe = () => X20(V().length - 1), ie3 = (e) => {
|
|
4063
4063
|
e.preventDefault(), e.metaKey ? oe() : e.altKey ? re(1) : Q(1);
|
|
4064
4064
|
}, se = (e) => {
|
|
4065
|
-
e.preventDefault(), e.metaKey ?
|
|
4065
|
+
e.preventDefault(), e.metaKey ? X20(0) : e.altKey ? re(-1) : Q(-1);
|
|
4066
4066
|
};
|
|
4067
4067
|
return t.createElement(Primitive2.div, { ref: o, tabIndex: -1, ...O, "cmdk-root": "", onKeyDown: (e) => {
|
|
4068
4068
|
var s;
|
|
@@ -4089,7 +4089,7 @@ var me = t.forwardRef((r2, o) => {
|
|
|
4089
4089
|
break;
|
|
4090
4090
|
}
|
|
4091
4091
|
case "Home": {
|
|
4092
|
-
e.preventDefault(),
|
|
4092
|
+
e.preventDefault(), X20(0);
|
|
4093
4093
|
break;
|
|
4094
4094
|
}
|
|
4095
4095
|
case "End": {
|
|
@@ -50283,53 +50283,78 @@ var EditorColorPicker = ({
|
|
|
50283
50283
|
onColorSelect,
|
|
50284
50284
|
currentColor = "#000000"
|
|
50285
50285
|
}) => {
|
|
50286
|
-
|
|
50287
|
-
|
|
50288
|
-
|
|
50289
|
-
|
|
50290
|
-
|
|
50291
|
-
|
|
50292
|
-
|
|
50293
|
-
|
|
50294
|
-
|
|
50295
|
-
|
|
50296
|
-
|
|
50297
|
-
|
|
50298
|
-
|
|
50299
|
-
|
|
50300
|
-
|
|
50301
|
-
|
|
50302
|
-
|
|
50303
|
-
|
|
50304
|
-
|
|
50305
|
-
|
|
50306
|
-
|
|
50307
|
-
|
|
50308
|
-
"
|
|
50309
|
-
|
|
50310
|
-
|
|
50311
|
-
|
|
50312
|
-
|
|
50313
|
-
|
|
50314
|
-
|
|
50315
|
-
|
|
50316
|
-
|
|
50317
|
-
|
|
50318
|
-
|
|
50319
|
-
|
|
50320
|
-
|
|
50321
|
-
|
|
50322
|
-
|
|
50323
|
-
|
|
50324
|
-
|
|
50325
|
-
|
|
50326
|
-
|
|
50327
|
-
|
|
50328
|
-
|
|
50329
|
-
|
|
50330
|
-
|
|
50331
|
-
|
|
50332
|
-
|
|
50286
|
+
const [showAdvanced, setShowAdvanced] = t__default.useState(false);
|
|
50287
|
+
const quickColors = [
|
|
50288
|
+
"#000000",
|
|
50289
|
+
// Black
|
|
50290
|
+
"#ef4444",
|
|
50291
|
+
// Red
|
|
50292
|
+
"#f59e0b",
|
|
50293
|
+
// Orange
|
|
50294
|
+
"#10b981",
|
|
50295
|
+
// Green
|
|
50296
|
+
"#3b82f6",
|
|
50297
|
+
// Blue
|
|
50298
|
+
"#8b5cf6",
|
|
50299
|
+
// Purple
|
|
50300
|
+
"#6b7280",
|
|
50301
|
+
// Gray
|
|
50302
|
+
"#ec4899"
|
|
50303
|
+
// Pink
|
|
50304
|
+
];
|
|
50305
|
+
if (showAdvanced) {
|
|
50306
|
+
return /* @__PURE__ */ jsxs("div", { className: "p-4 w-64", children: [
|
|
50307
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
|
|
50308
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Choose Color" }),
|
|
50309
|
+
/* @__PURE__ */ jsx(
|
|
50310
|
+
MoonUIButtonPro,
|
|
50311
|
+
{
|
|
50312
|
+
size: "sm",
|
|
50313
|
+
variant: "ghost",
|
|
50314
|
+
onClick: () => setShowAdvanced(false),
|
|
50315
|
+
children: "Back"
|
|
50316
|
+
}
|
|
50317
|
+
)
|
|
50318
|
+
] }),
|
|
50319
|
+
/* @__PURE__ */ jsx(
|
|
50320
|
+
MoonUIColorPickerPro,
|
|
50321
|
+
{
|
|
50322
|
+
value: currentColor,
|
|
50323
|
+
onChange: (color) => {
|
|
50324
|
+
onColorSelect(color);
|
|
50325
|
+
setShowAdvanced(false);
|
|
50326
|
+
},
|
|
50327
|
+
showInput: true,
|
|
50328
|
+
showPresets: false,
|
|
50329
|
+
size: "sm"
|
|
50330
|
+
}
|
|
50331
|
+
)
|
|
50332
|
+
] });
|
|
50333
|
+
}
|
|
50334
|
+
return /* @__PURE__ */ jsx("div", { className: "p-3 w-36", children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
|
|
50335
|
+
quickColors.map((color) => /* @__PURE__ */ jsx(
|
|
50336
|
+
"button",
|
|
50337
|
+
{
|
|
50338
|
+
onClick: () => onColorSelect(color),
|
|
50339
|
+
className: cn(
|
|
50340
|
+
"w-10 h-10 rounded-md border-2 hover:scale-110 transition-transform",
|
|
50341
|
+
currentColor === color ? "border-primary" : "border-transparent"
|
|
50342
|
+
),
|
|
50343
|
+
style: { backgroundColor: color },
|
|
50344
|
+
title: color
|
|
50345
|
+
},
|
|
50346
|
+
color
|
|
50347
|
+
)),
|
|
50348
|
+
/* @__PURE__ */ jsx(
|
|
50349
|
+
"button",
|
|
50350
|
+
{
|
|
50351
|
+
onClick: () => setShowAdvanced(true),
|
|
50352
|
+
className: "w-10 h-10 rounded-md border-2 border-dashed border-muted-foreground/50 hover:border-primary transition-colors flex items-center justify-center",
|
|
50353
|
+
title: "More colors",
|
|
50354
|
+
children: /* @__PURE__ */ jsx(Palette, { className: "w-5 h-5 text-muted-foreground" })
|
|
50355
|
+
}
|
|
50356
|
+
)
|
|
50357
|
+
] }) });
|
|
50333
50358
|
};
|
|
50334
50359
|
var ToolbarButton = ({
|
|
50335
50360
|
active,
|
|
@@ -50434,6 +50459,8 @@ function RichTextEditor({
|
|
|
50434
50459
|
const [isTyping, setIsTyping] = useState(false);
|
|
50435
50460
|
const typingIntervalRef = useRef(null);
|
|
50436
50461
|
const [currentAction, setCurrentAction] = useState("");
|
|
50462
|
+
const [remainingText, setRemainingText] = useState("");
|
|
50463
|
+
const isTypingRef = useRef(false);
|
|
50437
50464
|
const [isSourceView, setIsSourceView] = useState(false);
|
|
50438
50465
|
const [sourceContent, setSourceContent] = useState("");
|
|
50439
50466
|
const [currentTextColor, setCurrentTextColor] = useState("#000000");
|
|
@@ -50444,6 +50471,10 @@ function RichTextEditor({
|
|
|
50444
50471
|
}
|
|
50445
50472
|
return "en";
|
|
50446
50473
|
});
|
|
50474
|
+
const [isAiPreviewOpen, setIsAiPreviewOpen] = useState(false);
|
|
50475
|
+
const [previewContent, setPreviewContent] = useState("");
|
|
50476
|
+
const [previewAction, setPreviewAction] = useState("");
|
|
50477
|
+
const [previewOriginalText, setPreviewOriginalText] = useState("");
|
|
50447
50478
|
useEffect(() => {
|
|
50448
50479
|
return () => {
|
|
50449
50480
|
if (typingIntervalRef.current) {
|
|
@@ -50451,6 +50482,62 @@ function RichTextEditor({
|
|
|
50451
50482
|
}
|
|
50452
50483
|
};
|
|
50453
50484
|
}, []);
|
|
50485
|
+
const stopTyping = () => {
|
|
50486
|
+
if (typingIntervalRef.current) {
|
|
50487
|
+
clearTimeout(typingIntervalRef.current);
|
|
50488
|
+
typingIntervalRef.current = null;
|
|
50489
|
+
}
|
|
50490
|
+
setIsTyping(false);
|
|
50491
|
+
isTypingRef.current = false;
|
|
50492
|
+
setTypingText("");
|
|
50493
|
+
setRemainingText("");
|
|
50494
|
+
toast({
|
|
50495
|
+
title: "AI typing stopped",
|
|
50496
|
+
description: "The AI response was interrupted."
|
|
50497
|
+
});
|
|
50498
|
+
};
|
|
50499
|
+
const shouldUseModal = (action) => {
|
|
50500
|
+
const modalActions = ["expand", "summarize", "ideas", "continue"];
|
|
50501
|
+
return modalActions.includes(action);
|
|
50502
|
+
};
|
|
50503
|
+
const applyAIContentToEditor = (content, replaceSelection = true) => {
|
|
50504
|
+
if (!editor)
|
|
50505
|
+
return;
|
|
50506
|
+
setIsTyping(true);
|
|
50507
|
+
isTypingRef.current = true;
|
|
50508
|
+
setTypingText("");
|
|
50509
|
+
if (replaceSelection) {
|
|
50510
|
+
const selection = editor.state.selection;
|
|
50511
|
+
const selectedText = editor.state.doc.textBetween(selection.from, selection.to, " ");
|
|
50512
|
+
if (selectedText) {
|
|
50513
|
+
editor.chain().focus().deleteSelection().run();
|
|
50514
|
+
}
|
|
50515
|
+
}
|
|
50516
|
+
let currentIndex = 0;
|
|
50517
|
+
const typeSpeed = 30;
|
|
50518
|
+
const typeNextChar = () => {
|
|
50519
|
+
if (currentIndex < content.length && isTypingRef.current) {
|
|
50520
|
+
const nextChar = content[currentIndex];
|
|
50521
|
+
setTypingText((prev) => prev + nextChar);
|
|
50522
|
+
editor.chain().focus().insertContent(nextChar).run();
|
|
50523
|
+
currentIndex++;
|
|
50524
|
+
setRemainingText(content.substring(currentIndex));
|
|
50525
|
+
typingIntervalRef.current = setTimeout(typeNextChar, typeSpeed);
|
|
50526
|
+
} else {
|
|
50527
|
+
setIsTyping(false);
|
|
50528
|
+
isTypingRef.current = false;
|
|
50529
|
+
setTypingText("");
|
|
50530
|
+
setRemainingText("");
|
|
50531
|
+
if (currentIndex >= content.length) {
|
|
50532
|
+
toast({
|
|
50533
|
+
title: "AI action completed",
|
|
50534
|
+
description: "Your text has been updated successfully."
|
|
50535
|
+
});
|
|
50536
|
+
}
|
|
50537
|
+
}
|
|
50538
|
+
};
|
|
50539
|
+
typeNextChar();
|
|
50540
|
+
};
|
|
50454
50541
|
const slashCommands = [
|
|
50455
50542
|
{
|
|
50456
50543
|
id: "rewrite",
|
|
@@ -50588,10 +50675,18 @@ function RichTextEditor({
|
|
|
50588
50675
|
const { from: from2, to } = editor.state.selection;
|
|
50589
50676
|
const selectedText = editor.state.doc.textBetween(from2, to, " ");
|
|
50590
50677
|
setIsProcessing(true);
|
|
50678
|
+
setCurrentAction(command2.id);
|
|
50591
50679
|
try {
|
|
50592
50680
|
const response = await command2.action(selectedText || editor.getText());
|
|
50593
50681
|
if (response.text) {
|
|
50594
|
-
|
|
50682
|
+
if (shouldUseModal(command2.id)) {
|
|
50683
|
+
setPreviewContent(response.text);
|
|
50684
|
+
setPreviewAction(command2.id);
|
|
50685
|
+
setPreviewOriginalText(selectedText || editor.getText());
|
|
50686
|
+
setIsAiPreviewOpen(true);
|
|
50687
|
+
} else {
|
|
50688
|
+
applyAIContentToEditor(response.text, !!selectedText);
|
|
50689
|
+
}
|
|
50595
50690
|
} else if (response.error) {
|
|
50596
50691
|
toast({
|
|
50597
50692
|
title: "AI Error",
|
|
@@ -50731,30 +50826,14 @@ function RichTextEditor({
|
|
|
50731
50826
|
const result = await callAI(action, selectedText || editor.getText(), targetLanguage);
|
|
50732
50827
|
processingToast.dismiss();
|
|
50733
50828
|
if (result) {
|
|
50734
|
-
|
|
50735
|
-
|
|
50736
|
-
|
|
50737
|
-
editor.
|
|
50829
|
+
if (shouldUseModal(action)) {
|
|
50830
|
+
setPreviewContent(result);
|
|
50831
|
+
setPreviewAction(action);
|
|
50832
|
+
setPreviewOriginalText(selectedText || editor.getText());
|
|
50833
|
+
setIsAiPreviewOpen(true);
|
|
50834
|
+
} else {
|
|
50835
|
+
applyAIContentToEditor(result, !!selectedText);
|
|
50738
50836
|
}
|
|
50739
|
-
let currentIndex = 0;
|
|
50740
|
-
const typeSpeed = 30;
|
|
50741
|
-
const typeNextChar = () => {
|
|
50742
|
-
if (currentIndex < result.length) {
|
|
50743
|
-
const nextChar = result[currentIndex];
|
|
50744
|
-
setTypingText((prev) => prev + nextChar);
|
|
50745
|
-
editor.chain().focus().insertContent(nextChar).run();
|
|
50746
|
-
currentIndex++;
|
|
50747
|
-
typingIntervalRef.current = setTimeout(typeNextChar, typeSpeed);
|
|
50748
|
-
} else {
|
|
50749
|
-
setIsTyping(false);
|
|
50750
|
-
setTypingText("");
|
|
50751
|
-
toast({
|
|
50752
|
-
title: "AI action completed",
|
|
50753
|
-
description: "Your text has been updated successfully."
|
|
50754
|
-
});
|
|
50755
|
-
}
|
|
50756
|
-
};
|
|
50757
|
-
typeNextChar();
|
|
50758
50837
|
}
|
|
50759
50838
|
};
|
|
50760
50839
|
const getActionDescription = (action, targetLanguage) => {
|
|
@@ -51301,6 +51380,17 @@ function RichTextEditor({
|
|
|
51301
51380
|
]
|
|
51302
51381
|
}
|
|
51303
51382
|
) }),
|
|
51383
|
+
isTyping && /* @__PURE__ */ jsx(
|
|
51384
|
+
MoonUIButtonPro,
|
|
51385
|
+
{
|
|
51386
|
+
variant: "destructive",
|
|
51387
|
+
size: "sm",
|
|
51388
|
+
className: "h-8 px-2",
|
|
51389
|
+
onClick: stopTyping,
|
|
51390
|
+
title: "Stop AI typing",
|
|
51391
|
+
children: /* @__PURE__ */ jsx(X, { className: "w-4 h-4" })
|
|
51392
|
+
}
|
|
51393
|
+
),
|
|
51304
51394
|
/* @__PURE__ */ jsxs(MoonUIDropdownMenuContentPro, { className: "w-64", children: [
|
|
51305
51395
|
/* @__PURE__ */ jsxs("div", { className: "px-2 py-1.5 text-xs font-semibold text-muted-foreground flex items-center gap-2", children: [
|
|
51306
51396
|
/* @__PURE__ */ jsx(Wand2, { className: "w-3 h-3" }),
|
|
@@ -51829,6 +51919,91 @@ function RichTextEditor({
|
|
|
51829
51919
|
}, children: "Apply Borders" })
|
|
51830
51920
|
] })
|
|
51831
51921
|
] }) }),
|
|
51922
|
+
/* @__PURE__ */ jsx(MoonUIDialogPro, { open: isAiPreviewOpen, onOpenChange: setIsAiPreviewOpen, children: /* @__PURE__ */ jsxs(MoonUIDialogContentPro, { className: "max-w-4xl max-h-[80vh] overflow-hidden flex flex-col", children: [
|
|
51923
|
+
/* @__PURE__ */ jsxs(MoonUIDialogHeaderPro, { children: [
|
|
51924
|
+
/* @__PURE__ */ jsxs(MoonUIDialogTitlePro, { className: "flex items-center gap-2", children: [
|
|
51925
|
+
/* @__PURE__ */ jsx(Wand2, { className: "w-5 h-5 text-purple-600 dark:text-purple-400" }),
|
|
51926
|
+
"AI Preview - ",
|
|
51927
|
+
getActionDescription(previewAction).replace("...", "")
|
|
51928
|
+
] }),
|
|
51929
|
+
/* @__PURE__ */ jsx(MoonUIDialogDescriptionPro, { children: "Review the AI-generated content before applying it to your editor." })
|
|
51930
|
+
] }),
|
|
51931
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-hidden flex flex-col gap-4", children: [
|
|
51932
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-shrink-0", children: [
|
|
51933
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-muted-foreground mb-2", children: "Original Text:" }),
|
|
51934
|
+
/* @__PURE__ */ jsx("div", { className: "p-3 bg-muted/50 rounded-md border max-h-32 overflow-auto", children: /* @__PURE__ */ jsx("p", { className: "text-sm whitespace-pre-wrap", children: previewOriginalText }) })
|
|
51935
|
+
] }),
|
|
51936
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-hidden flex flex-col", children: [
|
|
51937
|
+
/* @__PURE__ */ jsxs("div", { className: "text-sm font-medium text-muted-foreground mb-2 flex items-center gap-2", children: [
|
|
51938
|
+
/* @__PURE__ */ jsx(Sparkles, { className: "w-4 h-4" }),
|
|
51939
|
+
"AI Generated Content:"
|
|
51940
|
+
] }),
|
|
51941
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 p-4 bg-gradient-to-br from-purple-50 to-blue-50 dark:from-purple-950/50 dark:to-blue-950/50 rounded-md border-2 border-purple-200 dark:border-purple-800 overflow-auto", children: /* @__PURE__ */ jsx("div", { className: "prose prose-sm max-w-none dark:prose-invert", children: /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap text-sm leading-relaxed", children: previewContent }) }) })
|
|
51942
|
+
] })
|
|
51943
|
+
] }),
|
|
51944
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-shrink-0 flex justify-between items-center pt-4 border-t", children: [
|
|
51945
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
51946
|
+
/* @__PURE__ */ jsxs(
|
|
51947
|
+
MoonUIButtonPro,
|
|
51948
|
+
{
|
|
51949
|
+
variant: "outline",
|
|
51950
|
+
size: "sm",
|
|
51951
|
+
onClick: () => {
|
|
51952
|
+
navigator.clipboard.writeText(previewContent);
|
|
51953
|
+
toast({
|
|
51954
|
+
title: "Copied to clipboard",
|
|
51955
|
+
description: "The AI-generated content has been copied."
|
|
51956
|
+
});
|
|
51957
|
+
},
|
|
51958
|
+
children: [
|
|
51959
|
+
/* @__PURE__ */ jsx(Copy, { className: "w-4 h-4 mr-2" }),
|
|
51960
|
+
"Copy"
|
|
51961
|
+
]
|
|
51962
|
+
}
|
|
51963
|
+
),
|
|
51964
|
+
/* @__PURE__ */ jsxs(
|
|
51965
|
+
MoonUIButtonPro,
|
|
51966
|
+
{
|
|
51967
|
+
variant: "outline",
|
|
51968
|
+
size: "sm",
|
|
51969
|
+
onClick: () => {
|
|
51970
|
+
setIsAiPreviewOpen(false);
|
|
51971
|
+
handleAIAction(previewAction);
|
|
51972
|
+
},
|
|
51973
|
+
children: [
|
|
51974
|
+
/* @__PURE__ */ jsx(RefreshCw, { className: "w-4 h-4 mr-2" }),
|
|
51975
|
+
"Regenerate"
|
|
51976
|
+
]
|
|
51977
|
+
}
|
|
51978
|
+
)
|
|
51979
|
+
] }),
|
|
51980
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
51981
|
+
/* @__PURE__ */ jsx(
|
|
51982
|
+
MoonUIButtonPro,
|
|
51983
|
+
{
|
|
51984
|
+
variant: "outline",
|
|
51985
|
+
onClick: () => setIsAiPreviewOpen(false),
|
|
51986
|
+
children: "Cancel"
|
|
51987
|
+
}
|
|
51988
|
+
),
|
|
51989
|
+
/* @__PURE__ */ jsxs(
|
|
51990
|
+
MoonUIButtonPro,
|
|
51991
|
+
{
|
|
51992
|
+
onClick: () => {
|
|
51993
|
+
setIsAiPreviewOpen(false);
|
|
51994
|
+
const replaceSelection = previewOriginalText !== editor?.getText();
|
|
51995
|
+
applyAIContentToEditor(previewContent, replaceSelection);
|
|
51996
|
+
},
|
|
51997
|
+
className: "bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700",
|
|
51998
|
+
children: [
|
|
51999
|
+
/* @__PURE__ */ jsx(Check, { className: "w-4 h-4 mr-2" }),
|
|
52000
|
+
"Apply to Editor"
|
|
52001
|
+
]
|
|
52002
|
+
}
|
|
52003
|
+
)
|
|
52004
|
+
] })
|
|
52005
|
+
] })
|
|
52006
|
+
] }) }),
|
|
51832
52007
|
/* @__PURE__ */ jsx(
|
|
51833
52008
|
"div",
|
|
51834
52009
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moontra/moonui-pro",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.12",
|
|
4
4
|
"description": "Premium React components for MoonUI - Advanced UI library with 50+ pro components including performance, interactive, and gesture components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
@@ -73,7 +73,9 @@ import {
|
|
|
73
73
|
Heart,
|
|
74
74
|
GraduationCap,
|
|
75
75
|
Zap,
|
|
76
|
-
Lightbulb
|
|
76
|
+
Lightbulb,
|
|
77
|
+
X,
|
|
78
|
+
Copy
|
|
77
79
|
} from 'lucide-react';
|
|
78
80
|
import { cn } from '../../lib/utils';
|
|
79
81
|
import { Button } from '../ui/button';
|
|
@@ -218,23 +220,71 @@ const EditorColorPicker = ({
|
|
|
218
220
|
onColorSelect: (color: string) => void;
|
|
219
221
|
currentColor?: string;
|
|
220
222
|
}) => {
|
|
223
|
+
const [showAdvanced, setShowAdvanced] = React.useState(false);
|
|
224
|
+
|
|
225
|
+
// Quick colors - 3x3 grid of 8 colors + 1 advanced button
|
|
226
|
+
const quickColors = [
|
|
227
|
+
'#000000', // Black
|
|
228
|
+
'#ef4444', // Red
|
|
229
|
+
'#f59e0b', // Orange
|
|
230
|
+
'#10b981', // Green
|
|
231
|
+
'#3b82f6', // Blue
|
|
232
|
+
'#8b5cf6', // Purple
|
|
233
|
+
'#6b7280', // Gray
|
|
234
|
+
'#ec4899', // Pink
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
if (showAdvanced) {
|
|
238
|
+
return (
|
|
239
|
+
<div className="p-4 w-64">
|
|
240
|
+
<div className="flex items-center justify-between mb-3">
|
|
241
|
+
<span className="text-sm font-medium">Choose Color</span>
|
|
242
|
+
<Button
|
|
243
|
+
size="sm"
|
|
244
|
+
variant="ghost"
|
|
245
|
+
onClick={() => setShowAdvanced(false)}
|
|
246
|
+
>
|
|
247
|
+
Back
|
|
248
|
+
</Button>
|
|
249
|
+
</div>
|
|
250
|
+
<ColorPicker
|
|
251
|
+
value={currentColor}
|
|
252
|
+
onChange={(color) => {
|
|
253
|
+
onColorSelect(color);
|
|
254
|
+
setShowAdvanced(false);
|
|
255
|
+
}}
|
|
256
|
+
showInput={true}
|
|
257
|
+
showPresets={false}
|
|
258
|
+
size="sm"
|
|
259
|
+
/>
|
|
260
|
+
</div>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
221
264
|
return (
|
|
222
|
-
<
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
265
|
+
<div className="p-3 w-36">
|
|
266
|
+
<div className="grid grid-cols-3 gap-2">
|
|
267
|
+
{quickColors.map((color) => (
|
|
268
|
+
<button
|
|
269
|
+
key={color}
|
|
270
|
+
onClick={() => onColorSelect(color)}
|
|
271
|
+
className={cn(
|
|
272
|
+
"w-10 h-10 rounded-md border-2 hover:scale-110 transition-transform",
|
|
273
|
+
currentColor === color ? "border-primary" : "border-transparent"
|
|
274
|
+
)}
|
|
275
|
+
style={{ backgroundColor: color }}
|
|
276
|
+
title={color}
|
|
277
|
+
/>
|
|
278
|
+
))}
|
|
279
|
+
<button
|
|
280
|
+
onClick={() => setShowAdvanced(true)}
|
|
281
|
+
className="w-10 h-10 rounded-md border-2 border-dashed border-muted-foreground/50 hover:border-primary transition-colors flex items-center justify-center"
|
|
282
|
+
title="More colors"
|
|
283
|
+
>
|
|
284
|
+
<Palette className="w-5 h-5 text-muted-foreground" />
|
|
285
|
+
</button>
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
238
288
|
);
|
|
239
289
|
};
|
|
240
290
|
|
|
@@ -379,6 +429,8 @@ export function RichTextEditor({
|
|
|
379
429
|
const [isTyping, setIsTyping] = useState(false);
|
|
380
430
|
const typingIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
|
381
431
|
const [currentAction, setCurrentAction] = useState('');
|
|
432
|
+
const [remainingText, setRemainingText] = useState(''); // To store remaining text when stopped
|
|
433
|
+
const isTypingRef = useRef(false); // Ref to track typing state in closure
|
|
382
434
|
const [isSourceView, setIsSourceView] = useState(false);
|
|
383
435
|
const [sourceContent, setSourceContent] = useState('');
|
|
384
436
|
const [currentTextColor, setCurrentTextColor] = useState('#000000');
|
|
@@ -391,6 +443,12 @@ export function RichTextEditor({
|
|
|
391
443
|
return 'en';
|
|
392
444
|
});
|
|
393
445
|
|
|
446
|
+
// AI Preview Modal states
|
|
447
|
+
const [isAiPreviewOpen, setIsAiPreviewOpen] = useState(false);
|
|
448
|
+
const [previewContent, setPreviewContent] = useState('');
|
|
449
|
+
const [previewAction, setPreviewAction] = useState('');
|
|
450
|
+
const [previewOriginalText, setPreviewOriginalText] = useState('');
|
|
451
|
+
|
|
394
452
|
// Clean up typewriter effect on unmount
|
|
395
453
|
useEffect(() => {
|
|
396
454
|
return () => {
|
|
@@ -399,6 +457,77 @@ export function RichTextEditor({
|
|
|
399
457
|
}
|
|
400
458
|
};
|
|
401
459
|
}, []);
|
|
460
|
+
|
|
461
|
+
// Stop typing function
|
|
462
|
+
const stopTyping = () => {
|
|
463
|
+
if (typingIntervalRef.current) {
|
|
464
|
+
clearTimeout(typingIntervalRef.current);
|
|
465
|
+
typingIntervalRef.current = null;
|
|
466
|
+
}
|
|
467
|
+
setIsTyping(false);
|
|
468
|
+
isTypingRef.current = false;
|
|
469
|
+
setTypingText('');
|
|
470
|
+
setRemainingText('');
|
|
471
|
+
toast({
|
|
472
|
+
title: "AI typing stopped",
|
|
473
|
+
description: "The AI response was interrupted.",
|
|
474
|
+
});
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
// Check if action should use modal preview
|
|
478
|
+
const shouldUseModal = (action: string): boolean => {
|
|
479
|
+
const modalActions = ['expand', 'summarize', 'ideas', 'continue'];
|
|
480
|
+
return modalActions.includes(action);
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
// Apply AI content to editor with typewriter effect
|
|
484
|
+
const applyAIContentToEditor = (content: string, replaceSelection: boolean = true) => {
|
|
485
|
+
if (!editor) return;
|
|
486
|
+
|
|
487
|
+
// Start typewriter effect
|
|
488
|
+
setIsTyping(true);
|
|
489
|
+
isTypingRef.current = true;
|
|
490
|
+
setTypingText('');
|
|
491
|
+
|
|
492
|
+
if (replaceSelection) {
|
|
493
|
+
const selection = editor.state.selection;
|
|
494
|
+
const selectedText = editor.state.doc.textBetween(selection.from, selection.to, ' ');
|
|
495
|
+
if (selectedText) {
|
|
496
|
+
editor.chain().focus().deleteSelection().run();
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
let currentIndex = 0;
|
|
501
|
+
const typeSpeed = 30; // ms per character
|
|
502
|
+
|
|
503
|
+
const typeNextChar = () => {
|
|
504
|
+
if (currentIndex < content.length && isTypingRef.current) {
|
|
505
|
+
const nextChar = content[currentIndex];
|
|
506
|
+
setTypingText(prev => prev + nextChar);
|
|
507
|
+
editor.chain().focus().insertContent(nextChar).run();
|
|
508
|
+
currentIndex++;
|
|
509
|
+
setRemainingText(content.substring(currentIndex));
|
|
510
|
+
typingIntervalRef.current = setTimeout(typeNextChar, typeSpeed);
|
|
511
|
+
} else {
|
|
512
|
+
// Typing complete or stopped
|
|
513
|
+
setIsTyping(false);
|
|
514
|
+
isTypingRef.current = false;
|
|
515
|
+
setTypingText('');
|
|
516
|
+
setRemainingText('');
|
|
517
|
+
|
|
518
|
+
if (currentIndex >= content.length) {
|
|
519
|
+
// Success toast only if completed
|
|
520
|
+
toast({
|
|
521
|
+
title: "AI action completed",
|
|
522
|
+
description: "Your text has been updated successfully.",
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
// Start typing
|
|
529
|
+
typeNextChar();
|
|
530
|
+
};
|
|
402
531
|
|
|
403
532
|
// Slash commands tanımları
|
|
404
533
|
const slashCommands: SlashCommand[] = [
|
|
@@ -539,10 +668,22 @@ export function RichTextEditor({
|
|
|
539
668
|
const selectedText = editor.state.doc.textBetween(from, to, ' ');
|
|
540
669
|
|
|
541
670
|
setIsProcessing(true);
|
|
671
|
+
setCurrentAction(command.id);
|
|
672
|
+
|
|
542
673
|
try {
|
|
543
674
|
const response = await command.action(selectedText || editor.getText());
|
|
544
675
|
if (response.text) {
|
|
545
|
-
|
|
676
|
+
// Check if this command should use modal preview
|
|
677
|
+
if (shouldUseModal(command.id)) {
|
|
678
|
+
// Open preview modal
|
|
679
|
+
setPreviewContent(response.text);
|
|
680
|
+
setPreviewAction(command.id);
|
|
681
|
+
setPreviewOriginalText(selectedText || editor.getText());
|
|
682
|
+
setIsAiPreviewOpen(true);
|
|
683
|
+
} else {
|
|
684
|
+
// Apply directly with typewriter effect
|
|
685
|
+
applyAIContentToEditor(response.text, !!selectedText);
|
|
686
|
+
}
|
|
546
687
|
} else if (response.error) {
|
|
547
688
|
toast({
|
|
548
689
|
title: "AI Error",
|
|
@@ -697,39 +838,17 @@ export function RichTextEditor({
|
|
|
697
838
|
processingToast.dismiss();
|
|
698
839
|
|
|
699
840
|
if (result) {
|
|
700
|
-
//
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
editor.
|
|
841
|
+
// Check if this action should use modal preview
|
|
842
|
+
if (shouldUseModal(action)) {
|
|
843
|
+
// Open preview modal instead of directly applying
|
|
844
|
+
setPreviewContent(result);
|
|
845
|
+
setPreviewAction(action);
|
|
846
|
+
setPreviewOriginalText(selectedText || editor.getText());
|
|
847
|
+
setIsAiPreviewOpen(true);
|
|
848
|
+
} else {
|
|
849
|
+
// Apply directly with typewriter effect (for rewrite, fix grammar, tone changes, etc.)
|
|
850
|
+
applyAIContentToEditor(result, !!selectedText);
|
|
706
851
|
}
|
|
707
|
-
|
|
708
|
-
let currentIndex = 0;
|
|
709
|
-
const typeSpeed = 30; // ms per character
|
|
710
|
-
|
|
711
|
-
const typeNextChar = () => {
|
|
712
|
-
if (currentIndex < result.length) {
|
|
713
|
-
const nextChar = result[currentIndex];
|
|
714
|
-
setTypingText(prev => prev + nextChar);
|
|
715
|
-
editor.chain().focus().insertContent(nextChar).run();
|
|
716
|
-
currentIndex++;
|
|
717
|
-
typingIntervalRef.current = setTimeout(typeNextChar, typeSpeed);
|
|
718
|
-
} else {
|
|
719
|
-
// Typing complete
|
|
720
|
-
setIsTyping(false);
|
|
721
|
-
setTypingText('');
|
|
722
|
-
|
|
723
|
-
// Success toast
|
|
724
|
-
toast({
|
|
725
|
-
title: "AI action completed",
|
|
726
|
-
description: "Your text has been updated successfully.",
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
|
-
};
|
|
730
|
-
|
|
731
|
-
// Start typing
|
|
732
|
-
typeNextChar();
|
|
733
852
|
}
|
|
734
853
|
};
|
|
735
854
|
|
|
@@ -1364,6 +1483,17 @@ export function RichTextEditor({
|
|
|
1364
1483
|
{isTyping ? 'Typing...' : 'AI Tools'}
|
|
1365
1484
|
</Button>
|
|
1366
1485
|
</DropdownMenuTrigger>
|
|
1486
|
+
{isTyping && (
|
|
1487
|
+
<Button
|
|
1488
|
+
variant="destructive"
|
|
1489
|
+
size="sm"
|
|
1490
|
+
className="h-8 px-2"
|
|
1491
|
+
onClick={stopTyping}
|
|
1492
|
+
title="Stop AI typing"
|
|
1493
|
+
>
|
|
1494
|
+
<X className="w-4 h-4" />
|
|
1495
|
+
</Button>
|
|
1496
|
+
)}
|
|
1367
1497
|
<DropdownMenuContent className="w-64">
|
|
1368
1498
|
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground flex items-center gap-2">
|
|
1369
1499
|
<Wand2 className="w-3 h-3" />
|
|
@@ -1886,6 +2016,99 @@ export function RichTextEditor({
|
|
|
1886
2016
|
</DialogContent>
|
|
1887
2017
|
</Dialog>
|
|
1888
2018
|
|
|
2019
|
+
{/* AI Preview Modal */}
|
|
2020
|
+
<Dialog open={isAiPreviewOpen} onOpenChange={setIsAiPreviewOpen}>
|
|
2021
|
+
<DialogContent className="max-w-4xl max-h-[80vh] overflow-hidden flex flex-col">
|
|
2022
|
+
<DialogHeader>
|
|
2023
|
+
<DialogTitle className="flex items-center gap-2">
|
|
2024
|
+
<Wand2 className="w-5 h-5 text-purple-600 dark:text-purple-400" />
|
|
2025
|
+
AI Preview - {getActionDescription(previewAction).replace('...', '')}
|
|
2026
|
+
</DialogTitle>
|
|
2027
|
+
<DialogDescription>
|
|
2028
|
+
Review the AI-generated content before applying it to your editor.
|
|
2029
|
+
</DialogDescription>
|
|
2030
|
+
</DialogHeader>
|
|
2031
|
+
|
|
2032
|
+
<div className="flex-1 overflow-hidden flex flex-col gap-4">
|
|
2033
|
+
{/* Original Text Section */}
|
|
2034
|
+
<div className="flex-shrink-0">
|
|
2035
|
+
<div className="text-sm font-medium text-muted-foreground mb-2">Original Text:</div>
|
|
2036
|
+
<div className="p-3 bg-muted/50 rounded-md border max-h-32 overflow-auto">
|
|
2037
|
+
<p className="text-sm whitespace-pre-wrap">{previewOriginalText}</p>
|
|
2038
|
+
</div>
|
|
2039
|
+
</div>
|
|
2040
|
+
|
|
2041
|
+
{/* AI Generated Content */}
|
|
2042
|
+
<div className="flex-1 overflow-hidden flex flex-col">
|
|
2043
|
+
<div className="text-sm font-medium text-muted-foreground mb-2 flex items-center gap-2">
|
|
2044
|
+
<Sparkles className="w-4 h-4" />
|
|
2045
|
+
AI Generated Content:
|
|
2046
|
+
</div>
|
|
2047
|
+
<div className="flex-1 p-4 bg-gradient-to-br from-purple-50 to-blue-50 dark:from-purple-950/50 dark:to-blue-950/50 rounded-md border-2 border-purple-200 dark:border-purple-800 overflow-auto">
|
|
2048
|
+
<div className="prose prose-sm max-w-none dark:prose-invert">
|
|
2049
|
+
<div className="whitespace-pre-wrap text-sm leading-relaxed">
|
|
2050
|
+
{previewContent}
|
|
2051
|
+
</div>
|
|
2052
|
+
</div>
|
|
2053
|
+
</div>
|
|
2054
|
+
</div>
|
|
2055
|
+
</div>
|
|
2056
|
+
|
|
2057
|
+
{/* Action Buttons */}
|
|
2058
|
+
<div className="flex-shrink-0 flex justify-between items-center pt-4 border-t">
|
|
2059
|
+
<div className="flex gap-2">
|
|
2060
|
+
<Button
|
|
2061
|
+
variant="outline"
|
|
2062
|
+
size="sm"
|
|
2063
|
+
onClick={() => {
|
|
2064
|
+
navigator.clipboard.writeText(previewContent);
|
|
2065
|
+
toast({
|
|
2066
|
+
title: "Copied to clipboard",
|
|
2067
|
+
description: "The AI-generated content has been copied.",
|
|
2068
|
+
});
|
|
2069
|
+
}}
|
|
2070
|
+
>
|
|
2071
|
+
<Copy className="w-4 h-4 mr-2" />
|
|
2072
|
+
Copy
|
|
2073
|
+
</Button>
|
|
2074
|
+
<Button
|
|
2075
|
+
variant="outline"
|
|
2076
|
+
size="sm"
|
|
2077
|
+
onClick={() => {
|
|
2078
|
+
// Regenerate content
|
|
2079
|
+
setIsAiPreviewOpen(false);
|
|
2080
|
+
handleAIAction(previewAction);
|
|
2081
|
+
}}
|
|
2082
|
+
>
|
|
2083
|
+
<RefreshCw className="w-4 h-4 mr-2" />
|
|
2084
|
+
Regenerate
|
|
2085
|
+
</Button>
|
|
2086
|
+
</div>
|
|
2087
|
+
|
|
2088
|
+
<div className="flex gap-2">
|
|
2089
|
+
<Button
|
|
2090
|
+
variant="outline"
|
|
2091
|
+
onClick={() => setIsAiPreviewOpen(false)}
|
|
2092
|
+
>
|
|
2093
|
+
Cancel
|
|
2094
|
+
</Button>
|
|
2095
|
+
<Button
|
|
2096
|
+
onClick={() => {
|
|
2097
|
+
setIsAiPreviewOpen(false);
|
|
2098
|
+
// Apply with typewriter effect
|
|
2099
|
+
const replaceSelection = previewOriginalText !== editor?.getText();
|
|
2100
|
+
applyAIContentToEditor(previewContent, replaceSelection);
|
|
2101
|
+
}}
|
|
2102
|
+
className="bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700"
|
|
2103
|
+
>
|
|
2104
|
+
<Check className="w-4 h-4 mr-2" />
|
|
2105
|
+
Apply to Editor
|
|
2106
|
+
</Button>
|
|
2107
|
+
</div>
|
|
2108
|
+
</div>
|
|
2109
|
+
</DialogContent>
|
|
2110
|
+
</Dialog>
|
|
2111
|
+
|
|
1889
2112
|
{/* Editor */}
|
|
1890
2113
|
<div
|
|
1891
2114
|
className="overflow-auto relative"
|