diff --git a/admin-spa/src/components/ui/rich-text-editor.tsx b/admin-spa/src/components/ui/rich-text-editor.tsx index 2ac0535..7da8174 100644 --- a/admin-spa/src/components/ui/rich-text-editor.tsx +++ b/admin-spa/src/components/ui/rich-text-editor.tsx @@ -76,14 +76,6 @@ export function RichTextEditor({ class: 'prose prose-sm max-w-none focus:outline-none min-h-[200px] px-4 py-3 [&_h1]:text-3xl [&_h1]:font-bold [&_h1]:mt-4 [&_h1]:mb-2 [&_h2]:text-2xl [&_h2]:font-bold [&_h2]:mt-3 [&_h2]:mb-2 [&_h3]:text-xl [&_h3]:font-bold [&_h3]:mt-2 [&_h3]:mb-1 [&_h4]:text-lg [&_h4]:font-bold [&_h4]:mt-2 [&_h4]:mb-1', }, - handleClick: (view, pos, event) => { - const target = event.target as HTMLElement; - if (target.tagName === 'A' || target.closest('a')) { - event.preventDefault(); - return true; - } - return false; - }, }, }); @@ -121,6 +113,8 @@ export function RichTextEditor({ const [buttonText, setButtonText] = useState('Click Here'); const [buttonHref, setButtonHref] = useState('{order_url}'); const [buttonStyle, setButtonStyle] = useState<'solid' | 'outline'>('solid'); + const [isEditingButton, setIsEditingButton] = useState(false); + const [editingButtonPos, setEditingButtonPos] = useState(null); const addImage = () => { openWPMediaImage((file) => { @@ -136,12 +130,81 @@ export function RichTextEditor({ setButtonText('Click Here'); setButtonHref('{order_url}'); setButtonStyle('solid'); + setIsEditingButton(false); + setEditingButtonPos(null); setButtonDialogOpen(true); }; + // Handle clicking on buttons in the editor to edit them + const handleEditorClick = (e: React.MouseEvent) => { + const target = e.target as HTMLElement; + const buttonEl = target.closest('a[data-button]') as HTMLElement | null; + + if (buttonEl && editor) { + e.preventDefault(); + e.stopPropagation(); + + // Get button attributes + const text = buttonEl.getAttribute('data-text') || buttonEl.textContent?.replace('🔘 ', '') || 'Click Here'; + const href = buttonEl.getAttribute('data-href') || '#'; + const style = (buttonEl.getAttribute('data-style') as 'solid' | 'outline') || 'solid'; + + // Find the position of this button node + const { state } = editor.view; + let foundPos: number | null = null; + + state.doc.descendants((node, pos) => { + if (node.type.name === 'button' && + node.attrs.text === text && + node.attrs.href === href) { + foundPos = pos; + return false; // Stop iteration + } + return true; + }); + + // Open dialog in edit mode + setButtonText(text); + setButtonHref(href); + setButtonStyle(style); + setIsEditingButton(true); + setEditingButtonPos(foundPos); + setButtonDialogOpen(true); + } + }; + const insertButton = () => { - editor.chain().focus().setButton({ text: buttonText, href: buttonHref, style: buttonStyle }).run(); + if (isEditingButton && editingButtonPos !== null && editor) { + // Delete old button and insert new one at same position + editor + .chain() + .focus() + .deleteRange({ from: editingButtonPos, to: editingButtonPos + 1 }) + .insertContentAt(editingButtonPos, { + type: 'button', + attrs: { text: buttonText, href: buttonHref, style: buttonStyle }, + }) + .run(); + } else { + // Insert new button + editor.chain().focus().setButton({ text: buttonText, href: buttonHref, style: buttonStyle }).run(); + } setButtonDialogOpen(false); + setIsEditingButton(false); + setEditingButtonPos(null); + }; + + const deleteButton = () => { + if (editingButtonPos !== null && editor) { + editor + .chain() + .focus() + .deleteRange({ from: editingButtonPos, to: editingButtonPos + 1 }) + .run(); + setButtonDialogOpen(false); + setIsEditingButton(false); + setEditingButtonPos(null); + } }; const getActiveHeading = () => { @@ -293,7 +356,9 @@ export function RichTextEditor({ {/* Editor */} - +
+ +
{/* Variables - Collapsible and Categorized */} {variables.length > 0 && ( @@ -381,12 +446,20 @@ export function RichTextEditor({ )} {/* Button Dialog */} - + { + setButtonDialogOpen(open); + if (!open) { + setIsEditingButton(false); + setEditingButtonPos(null); + } + }}> - {__('Insert Button')} + {isEditingButton ? __('Edit Button') : __('Insert Button')} - {__('Add a styled button to your content. Use variables for dynamic links.')} + {isEditingButton + ? __('Edit the button properties below. Click on the button to save.') + : __('Add a styled button to your content. Use variables for dynamic links.')} @@ -440,12 +513,17 @@ export function RichTextEditor({ - + + {isEditingButton && ( + + )} diff --git a/admin-spa/src/components/ui/tiptap-button-extension.ts b/admin-spa/src/components/ui/tiptap-button-extension.ts index fd44e1f..bb0e2fa 100644 --- a/admin-spa/src/components/ui/tiptap-button-extension.ts +++ b/admin-spa/src/components/ui/tiptap-button-extension.ts @@ -66,45 +66,23 @@ export const ButtonExtension = Node.create({ renderHTML({ HTMLAttributes }) { const { text, href, style } = HTMLAttributes; - const className = style === 'outline' ? 'button-outline' : 'button'; - - const buttonStyle: Record = style === 'solid' - ? { - display: 'inline-block', - background: '#7f54b3', - color: '#fff', - padding: '14px 28px', - borderRadius: '6px', - textDecoration: 'none', - fontWeight: '600', - cursor: 'pointer', - } - : { - display: 'inline-block', - background: 'transparent', - color: '#7f54b3', - padding: '12px 26px', - border: '2px solid #7f54b3', - borderRadius: '6px', - textDecoration: 'none', - fontWeight: '600', - cursor: 'pointer', - }; + // Simple link styling - no fancy button appearance in editor + // The actual button styling happens in email rendering (EmailRenderer.php) + // In editor, just show as a link with visual indicator it's a button return [ 'a', mergeAttributes(this.options.HTMLAttributes, { href, - class: className, - style: Object.entries(buttonStyle) - .map(([key, value]) => `${key.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${value}`) - .join('; '), + class: 'button-node', + style: 'color: #7f54b3; text-decoration: underline; cursor: pointer; font-weight: 500;', 'data-button': '', 'data-text': text, 'data-href': href, 'data-style': style, + title: `Button: ${text} → ${href}`, }), - text, + `🔘 ${text}`, ]; },