From 04a67014eb02e99136543cfb6952598caf283003 Mon Sep 17 00:00:00 2001 From: shiva raj badu Date: Sat, 2 Aug 2025 23:12:12 +0545 Subject: [PATCH] fix(nextjs): TextEditor Not applying styles issue --- .../components/TiptapText.tsx | 224 +++++++++--------- .../components/TiptapTextReplacer.tsx | 76 +----- .../context/LayoutContext.tsx | 9 +- .../hooks/useGroupLayouts.tsx | 1 - .../presentation/components/SlideContent.tsx | 19 +- .../nextjs/app/api/has-anthropic-key/route.ts | 8 + .../custom-layout/components/EachSlide.tsx | 28 ++- servers/nextjs/app/custom-layout/page.tsx | 49 +++- .../app/layout-preview/CustomLayout.tsx | 67 ++++++ servers/nextjs/app/layout-preview/page.tsx | 3 +- servers/nextjs/package.json | 4 - 11 files changed, 278 insertions(+), 210 deletions(-) create mode 100644 servers/nextjs/app/api/has-anthropic-key/route.ts create mode 100644 servers/nextjs/app/layout-preview/CustomLayout.tsx diff --git a/servers/nextjs/app/(presentation-generator)/components/TiptapText.tsx b/servers/nextjs/app/(presentation-generator)/components/TiptapText.tsx index f8fbe707..b8004665 100644 --- a/servers/nextjs/app/(presentation-generator)/components/TiptapText.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/TiptapText.tsx @@ -1,128 +1,140 @@ "use client"; -import React, { useEffect } from 'react'; +import React, { useEffect } from "react"; import { useEditor, EditorContent, BubbleMenu } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; import { Markdown } from "tiptap-markdown"; import Underline from "@tiptap/extension-underline"; import { - Bold, - Italic, - Underline as UnderlinedIcon, - Strikethrough, - Code, + Bold, + Italic, + Underline as UnderlinedIcon, + Strikethrough, + Code, } from "lucide-react"; interface TiptapTextProps { - content: string; - onContentChange?: (content: string) => void; - className?: string; - placeholder?: string; - tag?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span' | 'div'; + content: string; + onContentChange?: (content: string) => void; + className?: string; + placeholder?: string; + tag?: "H1" | "H2" | "H3" | "H4" | "H5" | "H6" | "P" | "SPAN" | "DIV" | any; } const TiptapText: React.FC = ({ - content, - onContentChange, - className = "", - placeholder = "Enter text...", - tag = 'div', + content, + onContentChange, + className = "", + placeholder = "Enter text...", + tag = "p", }) => { - const editor = useEditor({ - extensions: [StarterKit, Markdown, Underline], - content: content || placeholder, - editorProps: { - attributes: { - class: `outline-none focus:outline-none transition-all duration-200 ${className}`, - 'data-placeholder': placeholder, - }, - }, - onBlur: ({ editor }) => { - const markdown = editor?.storage.markdown.getMarkdown(); - if (onContentChange) { - onContentChange(markdown); - } - }, - editable: true, - immediatelyRender: false, - }); + const editor = useEditor({ + extensions: [StarterKit, Markdown, Underline], + content: content || placeholder, - // Update editor content when content prop changes - useEffect(() => { - if (editor && content !== editor.getText()) { - editor.commands.setContent(content || placeholder); - } - }, [content, editor, placeholder]); + editorProps: { + attributes: { + class: `outline-none focus:outline-none transition-all duration-200 ${className}`, + "data-placeholder": placeholder, + }, + }, + onBlur: ({ editor }) => { + const markdown = editor?.storage.markdown.getMarkdown(); + if (onContentChange) { + onContentChange(markdown); + } + }, + editable: true, + immediatelyRender: false, + }); - if (!editor) { - return
{content || placeholder}
; + // Update editor content when content prop changes + useEffect(() => { + if (editor && content !== editor.getText()) { + editor.commands.setContent(content || placeholder); } + }, [content, editor, placeholder]); - return ( - <> - -
- - - - - -
-
+ if (!editor) { + return
{content || placeholder}
; + } - + +
+ + + + + +
+
- }} - /> - - ); + + + ); }; -export default TiptapText; \ No newline at end of file +export default TiptapText; diff --git a/servers/nextjs/app/(presentation-generator)/components/TiptapTextReplacer.tsx b/servers/nextjs/app/(presentation-generator)/components/TiptapTextReplacer.tsx index 5cc70ce3..0c16a4a9 100644 --- a/servers/nextjs/app/(presentation-generator)/components/TiptapTextReplacer.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/TiptapTextReplacer.tsx @@ -63,81 +63,16 @@ const TiptapTextReplacer: React.FC = ({ if (shouldSkipElement(htmlElement)) return; // Get all computed styles to preserve them - const computedStyles = window.getComputedStyle(htmlElement); - const preservedStyles = { - fontSize: computedStyles.fontSize, - fontWeight: computedStyles.fontWeight, - fontFamily: computedStyles.fontFamily, - color: computedStyles.color, - lineHeight: computedStyles.lineHeight, - textAlign: computedStyles.textAlign, - marginTop: computedStyles.marginTop, - marginBottom: computedStyles.marginBottom, - marginLeft: computedStyles.marginLeft, - marginRight: computedStyles.marginRight, - paddingTop: computedStyles.paddingTop, - paddingBottom: computedStyles.paddingBottom, - paddingLeft: computedStyles.paddingLeft, - paddingRight: computedStyles.paddingRight, - borderRadius: computedStyles.borderRadius, - border: computedStyles.border, - backgroundColor: computedStyles.backgroundColor, - opacity: computedStyles.opacity, - zIndex: computedStyles.zIndex, - cursor: computedStyles.cursor, - boxShadow: computedStyles.boxShadow, - textShadow: computedStyles.textShadow, - textDecoration: computedStyles.textDecoration, - textTransform: computedStyles.textTransform, - letterSpacing: computedStyles.letterSpacing, - wordSpacing: computedStyles.wordSpacing, - textOverflow: computedStyles.textOverflow, - whiteSpace: computedStyles.whiteSpace, - wordBreak: computedStyles.wordBreak, - overflow: computedStyles.overflow, - textAlignLast: computedStyles.textAlignLast, - position: computedStyles.position, - top: computedStyles.top, - left: computedStyles.left, - right: computedStyles.right, - bottom: computedStyles.bottom, - display: computedStyles.display, - flexDirection: computedStyles.flexDirection, - flexWrap: computedStyles.flexWrap, - flexGrow: computedStyles.flexGrow, - flexShrink: computedStyles.flexShrink, - flexBasis: computedStyles.flexBasis, - alignItems: computedStyles.alignItems, - justifyContent: computedStyles.justifyContent, - gap: computedStyles.gap, - gridTemplateColumns: computedStyles.gridTemplateColumns, - gridTemplateRows: computedStyles.gridTemplateRows, - gridTemplateAreas: computedStyles.gridTemplateAreas, - gridTemplate: computedStyles.gridTemplate, - gridAutoFlow: computedStyles.gridAutoFlow, - gridAutoColumns: computedStyles.gridAutoColumns, - gridAutoRows: computedStyles.gridAutoRows, - gridColumn: computedStyles.gridColumn, - gridRow: computedStyles.gridRow, - gridArea: computedStyles.gridArea, - grid: computedStyles.grid, - }; - // Try to find matching data path + const allClasses = Array.from(htmlElement.classList); + const allStyles = htmlElement.getAttribute("style"); + const dataPath = findDataPath(slideData, trimmedText); // Create a container for the TiptapText const tiptapContainer = document.createElement("div"); - tiptapContainer.className = htmlElement.className; + tiptapContainer.style.cssText = allStyles || ""; + tiptapContainer.className = Array.from(allClasses).join(" "); - // Apply preserved styles - Object.entries(preservedStyles).forEach(([property, value]) => { - if (value && value !== "auto") { - tiptapContainer.style.setProperty( - property.replace(/([A-Z])/g, "-$1").toLowerCase(), - value - ); - } - }); // Replace the element htmlElement.parentNode?.replaceChild(tiptapContainer, htmlElement); // Mark as processed @@ -147,6 +82,7 @@ const TiptapTextReplacer: React.FC = ({ root.render( { if (dataPath && onContentChange) { onContentChange(content, dataPath.path, slideIndex); diff --git a/servers/nextjs/app/(presentation-generator)/context/LayoutContext.tsx b/servers/nextjs/app/(presentation-generator)/context/LayoutContext.tsx index 9c9a88bb..6b8fe710 100644 --- a/servers/nextjs/app/(presentation-generator)/context/LayoutContext.tsx +++ b/servers/nextjs/app/(presentation-generator)/context/LayoutContext.tsx @@ -112,7 +112,7 @@ const compileCustomLayout = (layoutCode: string, React: any, z: any) => { }; ` ); - // globalThis.z = z; + return factory(React, z); }; @@ -124,7 +124,6 @@ export const LayoutProvider: React.FC<{ const [error, setError] = useState(null); const [isPreloading, setIsPreloading] = useState(false); const dispatch = useDispatch(); - console.log("🔍 layoutData", layoutData); const buildData = async (groupedLayoutsData: GroupedLayoutsResponse[]) => { const layouts: LayoutInfo[] = []; @@ -278,7 +277,6 @@ export const LayoutProvider: React.FC<{ await layoutResponse.json(); if (!groupedLayoutsData || groupedLayoutsData.length === 0) { - console.warn("⚠️ API returned empty data"); setError("No layout groups found"); return; } @@ -344,11 +342,10 @@ export const LayoutProvider: React.FC<{ "/api/v1/ppt/layout-management/summary" ); const customGroupData = await customGroupResponse.json(); - console.log("🔍 customGroupData", customGroupData); + const customGroup = customGroupData.presentations; - console.log("🔍 customGroup", customGroup); + for (const group of customGroup) { - console.log("🔍 group", group); const groupName = `custom-${group.presentation_id}`; fullDataByGroup.set(groupName, []); if (!layoutsByGroup.has(groupName)) { diff --git a/servers/nextjs/app/(presentation-generator)/hooks/useGroupLayouts.tsx b/servers/nextjs/app/(presentation-generator)/hooks/useGroupLayouts.tsx index fb25ec50..24b66bb0 100644 --- a/servers/nextjs/app/(presentation-generator)/hooks/useGroupLayouts.tsx +++ b/servers/nextjs/app/(presentation-generator)/hooks/useGroupLayouts.tsx @@ -18,7 +18,6 @@ export const useGroupLayouts = () => { if (layout) { return getLayout(layoutId); } - console.warn(`Layout ${layoutId} not found in group ${groupName}`); return null; }; }, [getLayoutByIdAndGroup, getLayout]); diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx index 6fb374f8..32545d64 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx +++ b/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx @@ -107,16 +107,15 @@ const SlideContent = ({ slide, index, presentationId }: SlideContentProps) => { if (isStreaming || loading) { return; } - if (slide.layout_group.includes("custom")) { - const existingScript = document.querySelector( - 'script[src*="tailwindcss.com"]' - ); - if (!existingScript) { - const script = document.createElement("script"); - script.src = "https://cdn.tailwindcss.com"; - script.async = true; - document.head.appendChild(script); - } + + const existingScript = document.querySelector( + 'script[src*="tailwindcss.com"]' + ); + if (!existingScript) { + const script = document.createElement("script"); + script.src = "https://cdn.tailwindcss.com"; + script.async = true; + document.head.appendChild(script); } }, [slide, isStreaming, loading]); diff --git a/servers/nextjs/app/api/has-anthropic-key/route.ts b/servers/nextjs/app/api/has-anthropic-key/route.ts new file mode 100644 index 00000000..97c8ff0b --- /dev/null +++ b/servers/nextjs/app/api/has-anthropic-key/route.ts @@ -0,0 +1,8 @@ +import { NextResponse } from "next/server"; + +export const dynamic = "force-dynamic"; + +export async function GET() { + const hasKey = process.env.ANTHROPIC_API_KEY !== ""; + return NextResponse.json({ hasKey }); +} diff --git a/servers/nextjs/app/custom-layout/components/EachSlide.tsx b/servers/nextjs/app/custom-layout/components/EachSlide.tsx index 0f327898..8f7804e1 100644 --- a/servers/nextjs/app/custom-layout/components/EachSlide.tsx +++ b/servers/nextjs/app/custom-layout/components/EachSlide.tsx @@ -25,14 +25,14 @@ const EachSlide = ({ retrySlide, setSlides, onSlideUpdate, - isProcessingPptx, + isProcessing, }: { slide: any; index: number; retrySlide: (index: number) => void; setSlides: React.Dispatch>; onSlideUpdate?: (updatedSlideData: any) => void; - isProcessingPptx: boolean; + isProcessing: boolean; }) => { const [isUpdating, setIsUpdating] = useState(false); const [prompt, setPrompt] = useState(""); @@ -418,14 +418,19 @@ const EachSlide = ({ )} - {!isProcessingPptx && slide.processed && ( + {slide.processed && (
{slide.processed && slide.html && !isEditMode && (
diff --git a/servers/nextjs/app/custom-layout/page.tsx b/servers/nextjs/app/custom-layout/page.tsx index 8e50ef49..05f584ef 100644 --- a/servers/nextjs/app/custom-layout/page.tsx +++ b/servers/nextjs/app/custom-layout/page.tsx @@ -10,7 +10,6 @@ import { CardTitle, } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; -import { Progress } from "@/components/ui/progress"; import { toast } from "sonner"; import { Upload, FileText, X, Loader2 } from "lucide-react"; import { ApiResponseHandler } from "@/app/(presentation-generator)/services/api/api-error-handler"; @@ -60,8 +59,17 @@ const CustomLayoutPage = () => { const [isLayoutSaved, setIsLayoutSaved] = useState(false); const [UploadedFonts, setUploadedFonts] = useState([]); const [fontsData, setFontsData] = useState(null); + const [hasAnthropicKey, setHasAnthropicKey] = useState(false); + const [isAnthropicKeyLoading, setIsAnthropicKeyLoading] = useState(true); - console.log(slides); + useEffect(() => { + fetch("/api/has-anthropic-key") + .then((res) => res.json()) + .then((data) => { + setHasAnthropicKey(data.hasKey); + setIsAnthropicKeyLoading(false); + }); + }, []); // Load uploaded fonts dynamically useEffect(() => { @@ -117,6 +125,9 @@ const CustomLayoutPage = () => { } return () => window.removeEventListener("beforeunload", handleBeforeUnload); }, [slides, isLayoutSaved]); + + // If User does not put Anthropic Key, Can't process the layout + // Font management functions const uploadFont = useCallback( async (fontName: string, file: File): Promise => { @@ -563,6 +574,36 @@ const CustomLayoutPage = () => { (slide) => slide.processed || slide.error ).length; + if (isAnthropicKeyLoading) { + return ( +
+
+
+
+ +

Checking Anthropic Key...

+
+
+
+ ); + } + if (!hasAnthropicKey) { + return ( +
+
+
+
+

+ Please put Anthropic Key To Process The Layout +

+

+ It Only works on Anthropic(Claude-4). +

+
+
+
+ ); + } return (
@@ -680,7 +721,7 @@ const CustomLayoutPage = () => { key={index} slide={slide} index={index} - isProcessingPptx={isProcessingPptx} + isProcessing={slides.some((s) => s.processing)} retrySlide={retrySlide} setSlides={setSlides} onSlideUpdate={(updatedSlideData) => @@ -696,7 +737,7 @@ const CustomLayoutPage = () => {
- {/* */} + {/* */}
); diff --git a/servers/nextjs/package.json b/servers/nextjs/package.json index 2db9ff0a..209df0e4 100644 --- a/servers/nextjs/package.json +++ b/servers/nextjs/package.json @@ -39,11 +39,9 @@ "@tiptap/extension-underline": "^2.0.0", "@tiptap/react": "^2.11.5", "@tiptap/starter-kit": "^2.11.5", - "@types/fabric": "^5.3.10", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.0", - "fabric": "^6.7.1", "html2canvas": "^1.4.1", "jsonrepair": "^3.12.0", "lucide-react": "^0.447.0", @@ -54,9 +52,7 @@ "puppeteer": "^24.13.0", "react": "^18", "react-dom": "^18", - "react-jsx-parser": "^2.4.0", "react-redux": "^9.1.2", - "react-sketch-canvas": "^6.2.0", "recharts": "^2.15.4", "sharp": "^0.34.3", "sonner": "^2.0.6",