@jhits/plugin-newsletter 0.0.15 → 0.0.17
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/api/email-utils.d.ts.map +1 -1
- package/dist/api/email-utils.js +45 -4
- package/dist/api/handlers/newsletters.d.ts.map +1 -1
- package/dist/api/handlers/newsletters.js +33 -16
- package/dist/api/handlers/send-newsletter.d.ts.map +1 -1
- package/dist/api/handlers/send-newsletter.js +54 -6
- package/dist/api/handlers/settings.d.ts.map +1 -1
- package/dist/api/handlers/settings.js +51 -1
- package/dist/index.d.ts +27 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -122
- package/dist/lib/blocks/BlockRenderer.d.ts.map +1 -1
- package/dist/lib/blocks/BlockRenderer.js +14 -2
- package/dist/lib/email/EmailRenderer.d.ts +1 -0
- package/dist/lib/email/EmailRenderer.d.ts.map +1 -1
- package/dist/lib/email/EmailRenderer.js +31 -19
- package/dist/lib/utils/config-resolver.d.ts +33 -0
- package/dist/lib/utils/config-resolver.d.ts.map +1 -0
- package/dist/lib/utils/config-resolver.js +47 -0
- package/dist/registry/BlockRegistry.d.ts +9 -1
- package/dist/registry/BlockRegistry.d.ts.map +1 -1
- package/dist/registry/BlockRegistry.js +126 -8
- package/dist/state/EditorContext.d.ts +11 -1
- package/dist/state/EditorContext.d.ts.map +1 -1
- package/dist/state/EditorContext.js +23 -5
- package/dist/state/types.d.ts +12 -0
- package/dist/state/types.d.ts.map +1 -1
- package/dist/types/block.d.ts +9 -0
- package/dist/types/block.d.ts.map +1 -1
- package/dist/types/newsletter.d.ts +4 -0
- package/dist/types/newsletter.d.ts.map +1 -1
- package/dist/views/CanvasEditor/BlockWrapper.d.ts.map +1 -1
- package/dist/views/CanvasEditor/BlockWrapper.js +24 -3
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
- package/dist/views/CanvasEditor/CanvasEditorView.js +77 -17
- package/dist/views/CanvasEditor/EditorBody.d.ts.map +1 -1
- package/dist/views/CanvasEditor/EditorBody.js +1 -1
- package/dist/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/EditorCanvas.js +158 -100
- package/dist/views/CanvasEditor/components/EditorSidebar.d.ts +3 -1
- package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/EditorSidebar.js +3 -3
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +1 -1
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +1 -1
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.js +6 -40
- package/dist/views/NewsletterManager.d.ts.map +1 -1
- package/dist/views/NewsletterManager.js +87 -5
- package/dist/views/components/DomainPromptModal.d.ts +13 -0
- package/dist/views/components/DomainPromptModal.d.ts.map +1 -0
- package/dist/views/components/DomainPromptModal.js +58 -0
- package/dist/views/components/NewsletterCard.d.ts +16 -0
- package/dist/views/components/NewsletterCard.d.ts.map +1 -0
- package/dist/views/components/NewsletterCard.js +94 -0
- package/dist/views/components/NewsletterGrid.d.ts +16 -0
- package/dist/views/components/NewsletterGrid.d.ts.map +1 -0
- package/dist/views/components/NewsletterGrid.js +13 -0
- package/dist/views/components/SendNewsletterModal.d.ts.map +1 -1
- package/dist/views/components/SendNewsletterModal.js +91 -22
- package/dist/views/components/SmtpSettingsModal.d.ts.map +1 -1
- package/dist/views/components/SmtpSettingsModal.js +10 -0
- package/dist/views/components/TestEmailModal.d.ts.map +1 -1
- package/dist/views/components/TestEmailModal.js +86 -17
- package/package.json +53 -9
- package/src/api/email-utils.ts +53 -4
- package/src/api/handlers/newsletters.ts +40 -20
- package/src/api/handlers/send-newsletter.ts +65 -6
- package/src/api/handlers/settings.ts +60 -2
- package/src/index.tsx +49 -155
- package/src/lib/blocks/BlockRenderer.tsx +16 -2
- package/src/lib/email/EmailRenderer.tsx +31 -20
- package/src/lib/utils/config-resolver.ts +71 -0
- package/src/registry/BlockRegistry.tsx +255 -0
- package/src/state/EditorContext.tsx +43 -8
- package/src/state/types.ts +16 -0
- package/src/types/block.ts +10 -0
- package/src/types/newsletter.ts +5 -0
- package/src/views/CanvasEditor/BlockWrapper.tsx +27 -2
- package/src/views/CanvasEditor/CanvasEditorView.tsx +142 -61
- package/src/views/CanvasEditor/EditorBody.tsx +17 -13
- package/src/views/CanvasEditor/components/EditorCanvas.tsx +178 -115
- package/src/views/CanvasEditor/components/EditorSidebar.tsx +57 -2
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +6 -45
- package/src/views/NewsletterManager.tsx +164 -6
- package/src/views/components/DomainPromptModal.tsx +160 -0
- package/src/views/components/NewsletterCard.tsx +212 -0
- package/src/views/components/NewsletterGrid.tsx +48 -0
- package/src/views/components/SendNewsletterModal.tsx +270 -184
- package/src/views/components/SmtpSettingsModal.tsx +11 -0
- package/src/views/components/TestEmailModal.tsx +235 -149
- package/src/registry/BlockRegistry.ts +0 -53
|
@@ -29,7 +29,7 @@ export interface CanvasEditorViewProps {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: propsBackgroundColors, siteId, locale, isWelcomeEmail }: CanvasEditorViewProps) {
|
|
32
|
-
const { state, helpers, dispatch, darkMode: contextDarkMode, backgroundColors: contextBackgroundColors, canUndo, canRedo } = useEditor();
|
|
32
|
+
const { state, helpers, dispatch, darkMode: contextDarkMode, backgroundColors: contextBackgroundColors, emailConfig: contextEmailConfig, canUndo, canRedo } = useEditor();
|
|
33
33
|
const effectiveDarkMode = darkMode !== undefined ? darkMode : contextDarkMode;
|
|
34
34
|
const effectiveBackgroundColors = propsBackgroundColors || contextBackgroundColors;
|
|
35
35
|
const [isSidebarOpen, setSidebarOpen] = useState(true);
|
|
@@ -42,6 +42,13 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
|
|
|
42
42
|
const [isLoadingLanguage, setIsLoadingLanguage] = useState(true);
|
|
43
43
|
const [availableLanguages, setAvailableLanguages] = useState<string[]>([locale || 'en']);
|
|
44
44
|
const [currentLanguage, setCurrentLanguage] = useState<string>(locale || 'en');
|
|
45
|
+
|
|
46
|
+
// Global branding settings from SMTP
|
|
47
|
+
const [globalUnsubscribeTranslations, setGlobalUnsubscribeTranslations] = useState<Record<string, string>>({
|
|
48
|
+
en: '',
|
|
49
|
+
nl: '',
|
|
50
|
+
sv: '',
|
|
51
|
+
});
|
|
45
52
|
|
|
46
53
|
// Get registered blocks
|
|
47
54
|
const registeredBlocks = useRegisteredBlocks();
|
|
@@ -97,6 +104,25 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
|
|
|
97
104
|
setPrimaryLanguage(primary);
|
|
98
105
|
setAvailableLanguages(available);
|
|
99
106
|
setCurrentLanguage(primary);
|
|
107
|
+
|
|
108
|
+
// Load global unsubscribe translations
|
|
109
|
+
if (smtpData.unsubscribeTranslations) {
|
|
110
|
+
setGlobalUnsubscribeTranslations(smtpData.unsubscribeTranslations);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Update emailConfig with logo from SMTP settings if available
|
|
114
|
+
if (smtpData.logoUrl) {
|
|
115
|
+
if (typeof window !== 'undefined') {
|
|
116
|
+
if (!(window as any).__JHITS_PLUGIN_PROPS__) (window as any).__JHITS_PLUGIN_PROPS__ = {};
|
|
117
|
+
if (!(window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter']) (window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter'] = {};
|
|
118
|
+
|
|
119
|
+
const currentConfig = (window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter'].emailConfig || {};
|
|
120
|
+
(window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter'].emailConfig = {
|
|
121
|
+
...currentConfig,
|
|
122
|
+
logoUrl: smtpData.logoUrl
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
100
126
|
} catch (error) {
|
|
101
127
|
console.error('Failed to fetch language settings:', error);
|
|
102
128
|
} finally {
|
|
@@ -106,6 +132,41 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
|
|
|
106
132
|
fetchLanguageSettings();
|
|
107
133
|
}, []);
|
|
108
134
|
|
|
135
|
+
// Handle global unsubscribe text update
|
|
136
|
+
const handleGlobalUnsubscribeUpdate = async (text: string) => {
|
|
137
|
+
const newTranslations = {
|
|
138
|
+
...globalUnsubscribeTranslations,
|
|
139
|
+
[currentLanguage]: text
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Optimistic update
|
|
143
|
+
setGlobalUnsubscribeTranslations(newTranslations);
|
|
144
|
+
|
|
145
|
+
// Update window props for immediate preview reflection
|
|
146
|
+
if (typeof window !== 'undefined') {
|
|
147
|
+
if (!(window as any).__JHITS_PLUGIN_PROPS__) (window as any).__JHITS_PLUGIN_PROPS__ = {};
|
|
148
|
+
if (!(window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter']) (window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter'] = {};
|
|
149
|
+
(window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter'].unsubscribeTranslations = newTranslations;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
// We need current SMTP settings to save along with the new translation
|
|
154
|
+
const smtpResponse = await fetch('/api/plugin-newsletter/smtp');
|
|
155
|
+
const smtpData = await smtpResponse.json();
|
|
156
|
+
|
|
157
|
+
await fetch('/api/plugin-newsletter/smtp', {
|
|
158
|
+
method: 'POST',
|
|
159
|
+
headers: { 'Content-Type': 'application/json' },
|
|
160
|
+
body: JSON.stringify({
|
|
161
|
+
...smtpData,
|
|
162
|
+
unsubscribeTranslations: newTranslations
|
|
163
|
+
}),
|
|
164
|
+
});
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error('Failed to save global unsubscribe translation:', error);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
109
170
|
// Handle language change for welcome email
|
|
110
171
|
const handleLanguageChange = async (newLanguage: string) => {
|
|
111
172
|
// Save current content first if dirty
|
|
@@ -210,17 +271,14 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
|
|
|
210
271
|
}
|
|
211
272
|
|
|
212
273
|
// Replace the block in place by updating the blocks array directly
|
|
213
|
-
// This preserves the position and ID
|
|
214
274
|
const newBlocks = state.blocks.map((block, idx) => {
|
|
215
275
|
if (idx === blockIndex) {
|
|
216
|
-
// Replace this block with the new type
|
|
217
276
|
return {
|
|
218
277
|
...block,
|
|
219
278
|
type: blockType,
|
|
220
279
|
data: { ...blockDefinition.defaultData },
|
|
221
280
|
};
|
|
222
281
|
}
|
|
223
|
-
// Keep all other blocks unchanged
|
|
224
282
|
return block;
|
|
225
283
|
});
|
|
226
284
|
dispatch({ type: 'SET_BLOCKS', payload: newBlocks });
|
|
@@ -232,12 +290,29 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
|
|
|
232
290
|
// Slash command hook
|
|
233
291
|
const slashCommand = useSlashCommand(registeredBlocks, handleSlashCommandSelect);
|
|
234
292
|
|
|
293
|
+
// Lock dashboard scroll when editor is active to ensure our internal scrolling works
|
|
294
|
+
useEffect(() => {
|
|
295
|
+
if (typeof window === 'undefined') return;
|
|
296
|
+
|
|
297
|
+
const dashboardContainer = document.querySelector('.custom-scrollbar.overflow-y-auto');
|
|
298
|
+
const originalOverflow = dashboardContainer ? (dashboardContainer as HTMLElement).style.overflow : '';
|
|
299
|
+
|
|
300
|
+
if (dashboardContainer) {
|
|
301
|
+
(dashboardContainer as HTMLElement).style.overflow = 'hidden';
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return () => {
|
|
305
|
+
if (dashboardContainer) {
|
|
306
|
+
(dashboardContainer as HTMLElement).style.overflow = originalOverflow;
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}, []);
|
|
310
|
+
|
|
235
311
|
// Handle save
|
|
236
312
|
const handleSave = async () => {
|
|
237
313
|
setIsSaving(true);
|
|
238
314
|
setSaveError(null);
|
|
239
315
|
try {
|
|
240
|
-
// Always pass language for saving (both welcome email and regular newsletters)
|
|
241
316
|
await helpers.save({ language: currentLanguage });
|
|
242
317
|
setIsSaving(false);
|
|
243
318
|
} catch (error: any) {
|
|
@@ -264,7 +339,7 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
|
|
|
264
339
|
}
|
|
265
340
|
|
|
266
341
|
return (
|
|
267
|
-
<div className="
|
|
342
|
+
<div className="absolute inset-0 flex flex-col font-sans transition-colors duration-300 overflow-hidden bg-white dark:bg-neutral-950">
|
|
268
343
|
{isLoadingLanguage ? (
|
|
269
344
|
<div className="h-full w-full flex items-center justify-center">
|
|
270
345
|
<div className="text-center">
|
|
@@ -273,58 +348,58 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
|
|
|
273
348
|
</div>
|
|
274
349
|
</div>
|
|
275
350
|
) : (
|
|
276
|
-
<main className="flex flex-1 flex-col relative min-h-0">
|
|
351
|
+
<main className="flex flex-1 flex-col relative min-h-0 overflow-hidden">
|
|
277
352
|
{/* Error Banner */}
|
|
278
353
|
<ErrorBanner error={saveError} onDismiss={() => setSaveError(null)} />
|
|
279
354
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
isSidebarOpen={isSidebarOpen}
|
|
284
|
-
onSidebarToggle={() => setSidebarOpen(!isSidebarOpen)}
|
|
285
|
-
isSaving={isSaving}
|
|
286
|
-
onSave={handleSave}
|
|
287
|
-
onSaveError={(error) => {
|
|
288
|
-
if (error) {
|
|
289
|
-
setSaveError(error);
|
|
290
|
-
} else {
|
|
291
|
-
setSaveError(null);
|
|
292
|
-
}
|
|
293
|
-
}}
|
|
294
|
-
isDirty={state.isDirty}
|
|
295
|
-
isWelcomeEmail={isWelcomeEmail}
|
|
296
|
-
languages={availableLanguages}
|
|
297
|
-
currentLanguage={currentLanguage}
|
|
298
|
-
onLanguageChange={handleLanguageChange}
|
|
299
|
-
onAddLanguage={handleAddLanguage}
|
|
300
|
-
/>
|
|
301
|
-
|
|
302
|
-
{/* Editor Content Wrapper */}
|
|
303
|
-
<div className="flex flex-1 relative overflow-hidden min-h-0 flex-nowrap">
|
|
304
|
-
{/* CENTER: THE WRITING CANVAS */}
|
|
305
|
-
<EditorCanvas
|
|
355
|
+
{/* Sticky Header */}
|
|
356
|
+
<div className="flex-none z-30 border-b border-dashboard-border">
|
|
357
|
+
<EditorHeader
|
|
306
358
|
isPreviewMode={isPreviewMode}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
onBlockMove={(id: string, newIndex: number, containerId?: string) => helpers.moveBlock(id, newIndex, containerId)}
|
|
320
|
-
slashCommand={slashCommand}
|
|
359
|
+
onPreviewToggle={() => setIsPreviewMode(!isPreviewMode)}
|
|
360
|
+
isSidebarOpen={isSidebarOpen}
|
|
361
|
+
onSidebarToggle={() => setSidebarOpen(!isSidebarOpen)}
|
|
362
|
+
isSaving={isSaving}
|
|
363
|
+
onSave={handleSave}
|
|
364
|
+
onSaveError={(error) => setSaveError(error || null)}
|
|
365
|
+
isDirty={state.isDirty}
|
|
366
|
+
isWelcomeEmail={isWelcomeEmail}
|
|
367
|
+
languages={availableLanguages}
|
|
368
|
+
currentLanguage={currentLanguage}
|
|
369
|
+
onLanguageChange={handleLanguageChange}
|
|
370
|
+
onAddLanguage={handleAddLanguage}
|
|
321
371
|
/>
|
|
372
|
+
</div>
|
|
373
|
+
|
|
374
|
+
{/* Editor Content Wrapper - Independently Scrollable Area */}
|
|
375
|
+
<div className="flex-1 flex relative overflow-hidden min-h-0 flex-nowrap">
|
|
376
|
+
{/* CENTER: THE WRITING CANVAS */}
|
|
377
|
+
<div className="flex-1 overflow-hidden h-full">
|
|
378
|
+
<EditorCanvas
|
|
379
|
+
isPreviewMode={isPreviewMode}
|
|
380
|
+
contentBlocks={state.blocks}
|
|
381
|
+
title={state.title}
|
|
382
|
+
siteId={siteId}
|
|
383
|
+
locale={currentLanguage}
|
|
384
|
+
darkMode={effectiveDarkMode}
|
|
385
|
+
backgroundColors={effectiveBackgroundColors}
|
|
386
|
+
metadata={state.metadata}
|
|
387
|
+
onTitleChange={(title: string) => dispatch({ type: 'SET_TITLE', payload: title })}
|
|
388
|
+
onMetadataChange={(metadata) => dispatch({ type: 'SET_METADATA', payload: metadata })}
|
|
389
|
+
onBlockAdd={(type: string, index: number, containerId?: string) => helpers.addBlock(type, index, containerId)}
|
|
390
|
+
onBlockUpdate={(id: string, data: Partial<Block['data']>) => helpers.updateBlock(id, data)}
|
|
391
|
+
onBlockDelete={(id: string) => helpers.deleteBlock(id)}
|
|
392
|
+
onBlockMove={(id: string, newIndex: number, containerId?: string) => helpers.moveBlock(id, newIndex, containerId)}
|
|
393
|
+
slashCommand={slashCommand}
|
|
394
|
+
/>
|
|
395
|
+
</div>
|
|
322
396
|
|
|
323
397
|
{/* RIGHT SIDEBAR: THE "DESK" (SETTINGS) */}
|
|
324
398
|
{!isPreviewMode && (
|
|
325
399
|
<aside
|
|
326
|
-
className={`transition-all duration-500 ease-[cubic-bezier(0.4,0,0.2,1)] border-l border-dashboard-border bg-dashboard-sidebar overflow-y-auto
|
|
327
|
-
|
|
400
|
+
className={`flex-none transition-all duration-500 ease-[cubic-bezier(0.4,0,0.2,1)] border-l border-dashboard-border bg-dashboard-sidebar overflow-y-auto custom-scrollbar h-full ${
|
|
401
|
+
isSidebarOpen ? 'w-80' : 'w-0 opacity-0 pointer-events-none border-l-0'
|
|
402
|
+
}`}
|
|
328
403
|
>
|
|
329
404
|
<EditorSidebar
|
|
330
405
|
metadata={state.metadata}
|
|
@@ -335,6 +410,8 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
|
|
|
335
410
|
languages={availableLanguages}
|
|
336
411
|
currentLanguage={currentLanguage}
|
|
337
412
|
onLanguageChange={handleLanguageChange}
|
|
413
|
+
globalUnsubscribeText={globalUnsubscribeTranslations[currentLanguage]}
|
|
414
|
+
onGlobalUnsubscribeUpdate={handleGlobalUnsubscribeUpdate}
|
|
338
415
|
/>
|
|
339
416
|
</aside>
|
|
340
417
|
)}
|
|
@@ -342,19 +419,23 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
|
|
|
342
419
|
|
|
343
420
|
{/* Slash Command Menu */}
|
|
344
421
|
{slashCommand.isOpen && slashCommand.position && (
|
|
345
|
-
<
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
// Use the replaceBlockId passed from the menu (which comes from state)
|
|
351
|
-
// This ensures we use the correct block ID that was set when the menu opened
|
|
352
|
-
slashCommand.selectCurrent(replaceBlockId);
|
|
422
|
+
<div
|
|
423
|
+
className="fixed z-50 transition-all duration-200"
|
|
424
|
+
style={{
|
|
425
|
+
left: slashCommand.position.left,
|
|
426
|
+
top: slashCommand.position.top
|
|
353
427
|
}}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
428
|
+
>
|
|
429
|
+
<SlashCommandMenu
|
|
430
|
+
blocks={slashCommand.filteredBlocks}
|
|
431
|
+
query={slashCommand.query}
|
|
432
|
+
selectedIndex={slashCommand.selectedIndex}
|
|
433
|
+
onSelect={(replaceBlockId) => slashCommand.selectCurrent(replaceBlockId)}
|
|
434
|
+
position={slashCommand.position}
|
|
435
|
+
onClose={slashCommand.closeMenu}
|
|
436
|
+
replaceBlockId={slashCommand.replaceBlockId}
|
|
437
|
+
/>
|
|
438
|
+
</div>
|
|
358
439
|
)}
|
|
359
440
|
</main>
|
|
360
441
|
)}
|
|
@@ -53,14 +53,18 @@ export function EditorBody({
|
|
|
53
53
|
{blocks.map((block, index) => (
|
|
54
54
|
<React.Fragment key={block.id}>
|
|
55
55
|
{/* Add Block Button Above */}
|
|
56
|
-
<div className="relative h-
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
<div className="relative h-4 group/add-button">
|
|
57
|
+
<div className="absolute inset-x-0 top-1/2 -translate-y-1/2 flex items-center justify-center">
|
|
58
|
+
<div className="w-full h-[1px] bg-neutral-100 dark:bg-neutral-800 opacity-0 group-hover/add-button:opacity-100 transition-opacity" />
|
|
59
|
+
<button
|
|
60
|
+
onClick={() => onBlockAdd('paragraph', index)}
|
|
61
|
+
className="mx-4 flex-shrink-0 w-6 h-6 flex items-center justify-center opacity-0 group-hover/add-button:opacity-100 transition-all hover:bg-primary hover:text-white bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 shadow-sm rounded-full z-10"
|
|
62
|
+
title="Add block here"
|
|
63
|
+
>
|
|
64
|
+
<Plus size={14} />
|
|
65
|
+
</button>
|
|
66
|
+
<div className="w-full h-[1px] bg-neutral-100 dark:bg-neutral-800 opacity-0 group-hover/add-button:opacity-100 transition-opacity" />
|
|
67
|
+
</div>
|
|
64
68
|
</div>
|
|
65
69
|
|
|
66
70
|
{/* Block */}
|
|
@@ -80,14 +84,14 @@ export function EditorBody({
|
|
|
80
84
|
</React.Fragment>
|
|
81
85
|
))}
|
|
82
86
|
|
|
83
|
-
{/* Add Block Button at Bottom */}
|
|
84
|
-
<div className="relative h-
|
|
87
|
+
{/* Add Block Button at Bottom - Subtle Plus on the left */}
|
|
88
|
+
<div className="relative h-12 mt-4 group/add-bottom">
|
|
85
89
|
<button
|
|
86
90
|
onClick={() => onBlockAdd('paragraph', blocks.length)}
|
|
87
|
-
className="absolute left-0 top-1/2 -translate-y-1/2 w-
|
|
88
|
-
title="Add block"
|
|
91
|
+
className="absolute left-0 top-1/2 -translate-y-1/2 w-8 h-8 flex items-center justify-center opacity-20 group-hover/add-bottom:opacity-100 hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-full transition-all text-neutral-500"
|
|
92
|
+
title="Add block at the end"
|
|
89
93
|
>
|
|
90
|
-
<Plus size={
|
|
94
|
+
<Plus size={18} />
|
|
91
95
|
</button>
|
|
92
96
|
</div>
|
|
93
97
|
</div>
|