@fufulog/brevomorphic-cms-sdk 1.0.2 → 1.0.4
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/package.json +25 -2
- package/react/BrevoWysiwyg.d.ts +16 -0
- package/react/BrevoWysiwyg.tsx +0 -1
- package/svelte/BrevoWysiwyg.svelte +137 -70
- package/svelte/BrevoWysiwyg.svelte.d.ts +10 -0
package/package.json
CHANGED
|
@@ -1,10 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fufulog/brevomorphic-cms-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "SDK library to bootstrap Brevo email templates and manage local mappings in Postgres, MySQL, or Firestore",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./svelte/BrevoWysiwyg.svelte": {
|
|
15
|
+
"types": "./svelte/BrevoWysiwyg.svelte.d.ts",
|
|
16
|
+
"import": "./svelte/BrevoWysiwyg.svelte",
|
|
17
|
+
"svelte": "./svelte/BrevoWysiwyg.svelte",
|
|
18
|
+
"default": "./svelte/BrevoWysiwyg.svelte"
|
|
19
|
+
},
|
|
20
|
+
"./react/BrevoWysiwyg": {
|
|
21
|
+
"types": "./react/BrevoWysiwyg.d.ts",
|
|
22
|
+
"import": "./react/BrevoWysiwyg.tsx",
|
|
23
|
+
"default": "./react/BrevoWysiwyg.tsx"
|
|
24
|
+
},
|
|
25
|
+
"./react/BrevoWysiwyg.css": {
|
|
26
|
+
"default": "./react/BrevoWysiwyg.css"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
8
29
|
"scripts": {
|
|
9
30
|
"build": "tsc",
|
|
10
31
|
"test": "tsx tests/sdk.test.ts",
|
|
@@ -28,9 +49,11 @@
|
|
|
28
49
|
"devDependencies": {
|
|
29
50
|
"@types/node": "^20.11.30",
|
|
30
51
|
"@types/express": "^4.17.21",
|
|
52
|
+
"@types/react": "^18.2.66",
|
|
31
53
|
"typescript": "^5.4.3",
|
|
32
54
|
"tsx": "^4.7.1",
|
|
33
|
-
"express": "^4.19.2"
|
|
55
|
+
"express": "^4.19.2",
|
|
56
|
+
"react": "^18.2.0"
|
|
34
57
|
},
|
|
35
58
|
"publishConfig": {
|
|
36
59
|
"access": "public"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface BrevoVariable {
|
|
4
|
+
label: string;
|
|
5
|
+
key: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface BrevoWysiwygProps {
|
|
9
|
+
value: string;
|
|
10
|
+
onChange: (html: string) => void;
|
|
11
|
+
variables?: BrevoVariable[];
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default function BrevoWysiwyg(props: BrevoWysiwygProps): React.JSX.Element;
|
package/react/BrevoWysiwyg.tsx
CHANGED
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
-->
|
|
27
27
|
|
|
28
28
|
<script>
|
|
29
|
-
import { onMount, createEventDispatcher, tick } from
|
|
29
|
+
import { onMount, createEventDispatcher, tick } from "svelte";
|
|
30
30
|
|
|
31
|
-
export let value =
|
|
31
|
+
export let value = "";
|
|
32
32
|
export let variables = [];
|
|
33
|
-
export let placeholder =
|
|
33
|
+
export let placeholder = "Start designing your email template...";
|
|
34
34
|
export let disabled = false;
|
|
35
35
|
|
|
36
36
|
const dispatch = createEventDispatcher();
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
let editorEl;
|
|
39
39
|
let sourceEl;
|
|
40
40
|
let isSourceMode = false;
|
|
41
|
-
let sourceCode =
|
|
41
|
+
let sourceCode = "";
|
|
42
42
|
let showVariableMenu = false;
|
|
43
43
|
let showHeadingMenu = false;
|
|
44
44
|
let isFocused = false;
|
|
@@ -65,20 +65,20 @@
|
|
|
65
65
|
const raw = editorEl.innerHTML;
|
|
66
66
|
// Restore any accidentally encoded Twig brackets
|
|
67
67
|
const cleaned = raw
|
|
68
|
-
.replace(/{/g,
|
|
69
|
-
.replace(/}/g,
|
|
70
|
-
.replace(/{/g,
|
|
71
|
-
.replace(/}/g,
|
|
72
|
-
.replace(/%/g,
|
|
73
|
-
.replace(/%/g,
|
|
68
|
+
.replace(/{/g, "{")
|
|
69
|
+
.replace(/}/g, "}")
|
|
70
|
+
.replace(/{/g, "{")
|
|
71
|
+
.replace(/}/g, "}")
|
|
72
|
+
.replace(/%/g, "%")
|
|
73
|
+
.replace(/%/g, "%");
|
|
74
74
|
value = cleaned;
|
|
75
|
-
dispatch(
|
|
75
|
+
dispatch("change", cleaned);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
function syncFromSource() {
|
|
79
79
|
sourceCode = sourceEl?.value ?? sourceCode;
|
|
80
80
|
value = sourceCode;
|
|
81
|
-
dispatch(
|
|
81
|
+
dispatch("change", value);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
async function toggleSourceMode() {
|
|
@@ -108,24 +108,24 @@
|
|
|
108
108
|
|
|
109
109
|
function updateActiveFormats() {
|
|
110
110
|
activeFormats = {
|
|
111
|
-
bold: document.queryCommandState(
|
|
112
|
-
italic: document.queryCommandState(
|
|
113
|
-
underline: document.queryCommandState(
|
|
114
|
-
strikeThrough: document.queryCommandState(
|
|
115
|
-
insertOrderedList: document.queryCommandState(
|
|
116
|
-
insertUnorderedList: document.queryCommandState(
|
|
111
|
+
bold: document.queryCommandState("bold"),
|
|
112
|
+
italic: document.queryCommandState("italic"),
|
|
113
|
+
underline: document.queryCommandState("underline"),
|
|
114
|
+
strikeThrough: document.queryCommandState("strikeThrough"),
|
|
115
|
+
insertOrderedList: document.queryCommandState("insertOrderedList"),
|
|
116
|
+
insertUnorderedList: document.queryCommandState("insertUnorderedList"),
|
|
117
117
|
};
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
function insertHeading(tag) {
|
|
121
|
-
execCmd(
|
|
121
|
+
execCmd("formatBlock", tag);
|
|
122
122
|
showHeadingMenu = false;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
function insertLink() {
|
|
126
126
|
if (isSourceMode || disabled) return;
|
|
127
|
-
const url = prompt(
|
|
128
|
-
if (url) execCmd(
|
|
127
|
+
const url = prompt("Enter URL:");
|
|
128
|
+
if (url) execCmd("createLink", url);
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
function insertVariable(variable) {
|
|
@@ -141,17 +141,18 @@
|
|
|
141
141
|
sourceCode = before + twigExpr + after;
|
|
142
142
|
sourceEl.value = sourceCode;
|
|
143
143
|
value = sourceCode;
|
|
144
|
-
dispatch(
|
|
144
|
+
dispatch("change", value);
|
|
145
145
|
// Restore cursor after inserted text
|
|
146
146
|
tick().then(() => {
|
|
147
|
-
sourceEl.selectionStart = sourceEl.selectionEnd =
|
|
147
|
+
sourceEl.selectionStart = sourceEl.selectionEnd =
|
|
148
|
+
start + twigExpr.length;
|
|
148
149
|
sourceEl.focus();
|
|
149
150
|
});
|
|
150
151
|
} else if (editorEl) {
|
|
151
152
|
// Insert into contentEditable at cursor position
|
|
152
153
|
editorEl.focus();
|
|
153
154
|
// Use insertHTML to preserve Twig brackets exactly
|
|
154
|
-
document.execCommand(
|
|
155
|
+
document.execCommand("insertHTML", false, twigExpr);
|
|
155
156
|
syncFromEditor();
|
|
156
157
|
}
|
|
157
158
|
|
|
@@ -172,7 +173,7 @@
|
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
function closeMenus(e) {
|
|
175
|
-
if (!e.target.closest(
|
|
176
|
+
if (!e.target.closest(".brevo-dropdown")) {
|
|
176
177
|
showVariableMenu = false;
|
|
177
178
|
showHeadingMenu = false;
|
|
178
179
|
}
|
|
@@ -181,7 +182,11 @@
|
|
|
181
182
|
|
|
182
183
|
<svelte:window on:click={closeMenus} />
|
|
183
184
|
|
|
184
|
-
<div
|
|
185
|
+
<div
|
|
186
|
+
class="brevo-wysiwyg"
|
|
187
|
+
class:brevo-wysiwyg--disabled={disabled}
|
|
188
|
+
class:brevo-wysiwyg--focused={isFocused}
|
|
189
|
+
>
|
|
185
190
|
<!-- Toolbar -->
|
|
186
191
|
<div class="brevo-toolbar">
|
|
187
192
|
<div class="brevo-toolbar__group">
|
|
@@ -190,36 +195,36 @@
|
|
|
190
195
|
class="brevo-btn"
|
|
191
196
|
class:brevo-btn--active={activeFormats.bold}
|
|
192
197
|
title="Bold"
|
|
193
|
-
on:click={() => execCmd(
|
|
194
|
-
{disabled}
|
|
195
|
-
|
|
198
|
+
on:click={() => execCmd("bold")}
|
|
199
|
+
{disabled}><strong>B</strong></button
|
|
200
|
+
>
|
|
196
201
|
|
|
197
202
|
<button
|
|
198
203
|
type="button"
|
|
199
204
|
class="brevo-btn"
|
|
200
205
|
class:brevo-btn--active={activeFormats.italic}
|
|
201
206
|
title="Italic"
|
|
202
|
-
on:click={() => execCmd(
|
|
203
|
-
{disabled}
|
|
204
|
-
|
|
207
|
+
on:click={() => execCmd("italic")}
|
|
208
|
+
{disabled}><em>I</em></button
|
|
209
|
+
>
|
|
205
210
|
|
|
206
211
|
<button
|
|
207
212
|
type="button"
|
|
208
213
|
class="brevo-btn"
|
|
209
214
|
class:brevo-btn--active={activeFormats.underline}
|
|
210
215
|
title="Underline"
|
|
211
|
-
on:click={() => execCmd(
|
|
212
|
-
{disabled}
|
|
213
|
-
|
|
216
|
+
on:click={() => execCmd("underline")}
|
|
217
|
+
{disabled}><u>U</u></button
|
|
218
|
+
>
|
|
214
219
|
|
|
215
220
|
<button
|
|
216
221
|
type="button"
|
|
217
222
|
class="brevo-btn"
|
|
218
223
|
class:brevo-btn--active={activeFormats.strikeThrough}
|
|
219
224
|
title="Strikethrough"
|
|
220
|
-
on:click={() => execCmd(
|
|
221
|
-
{disabled}
|
|
222
|
-
|
|
225
|
+
on:click={() => execCmd("strikeThrough")}
|
|
226
|
+
{disabled}><s>S</s></button
|
|
227
|
+
>
|
|
223
228
|
</div>
|
|
224
229
|
|
|
225
230
|
<div class="brevo-toolbar__divider"></div>
|
|
@@ -230,16 +235,27 @@
|
|
|
230
235
|
type="button"
|
|
231
236
|
class="brevo-btn"
|
|
232
237
|
title="Headings"
|
|
233
|
-
on:click|stopPropagation={() => {
|
|
234
|
-
|
|
235
|
-
|
|
238
|
+
on:click|stopPropagation={() => {
|
|
239
|
+
showHeadingMenu = !showHeadingMenu;
|
|
240
|
+
showVariableMenu = false;
|
|
241
|
+
}}
|
|
242
|
+
{disabled}>H ▾</button
|
|
243
|
+
>
|
|
236
244
|
|
|
237
245
|
{#if showHeadingMenu}
|
|
238
246
|
<div class="brevo-dropdown__menu">
|
|
239
|
-
<button type="button" on:click={() => insertHeading(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
<button type="button" on:click={() => insertHeading(
|
|
247
|
+
<button type="button" on:click={() => insertHeading("h1")}
|
|
248
|
+
>Heading 1</button
|
|
249
|
+
>
|
|
250
|
+
<button type="button" on:click={() => insertHeading("h2")}
|
|
251
|
+
>Heading 2</button
|
|
252
|
+
>
|
|
253
|
+
<button type="button" on:click={() => insertHeading("h3")}
|
|
254
|
+
>Heading 3</button
|
|
255
|
+
>
|
|
256
|
+
<button type="button" on:click={() => insertHeading("p")}
|
|
257
|
+
>Paragraph</button
|
|
258
|
+
>
|
|
243
259
|
</div>
|
|
244
260
|
{/if}
|
|
245
261
|
</div>
|
|
@@ -252,31 +268,55 @@
|
|
|
252
268
|
class="brevo-btn"
|
|
253
269
|
class:brevo-btn--active={activeFormats.insertUnorderedList}
|
|
254
270
|
title="Bullet List"
|
|
255
|
-
on:click={() => execCmd(
|
|
256
|
-
{disabled}
|
|
257
|
-
|
|
271
|
+
on:click={() => execCmd("insertUnorderedList")}
|
|
272
|
+
{disabled}>• List</button
|
|
273
|
+
>
|
|
258
274
|
|
|
259
275
|
<button
|
|
260
276
|
type="button"
|
|
261
277
|
class="brevo-btn"
|
|
262
278
|
class:brevo-btn--active={activeFormats.insertOrderedList}
|
|
263
279
|
title="Numbered List"
|
|
264
|
-
on:click={() => execCmd(
|
|
265
|
-
{disabled}
|
|
266
|
-
>
|
|
280
|
+
on:click={() => execCmd("insertOrderedList")}
|
|
281
|
+
{disabled}>1. List</button
|
|
282
|
+
>
|
|
267
283
|
</div>
|
|
268
284
|
|
|
269
285
|
<div class="brevo-toolbar__divider"></div>
|
|
270
286
|
|
|
271
287
|
<div class="brevo-toolbar__group">
|
|
272
|
-
<button
|
|
273
|
-
|
|
274
|
-
|
|
288
|
+
<button
|
|
289
|
+
type="button"
|
|
290
|
+
class="brevo-btn"
|
|
291
|
+
title="Align Left"
|
|
292
|
+
on:click={() => execCmd("justifyLeft")}
|
|
293
|
+
{disabled}>⫷</button
|
|
294
|
+
>
|
|
295
|
+
<button
|
|
296
|
+
type="button"
|
|
297
|
+
class="brevo-btn"
|
|
298
|
+
title="Align Center"
|
|
299
|
+
on:click={() => execCmd("justifyCenter")}
|
|
300
|
+
{disabled}>⫿</button
|
|
301
|
+
>
|
|
302
|
+
<button
|
|
303
|
+
type="button"
|
|
304
|
+
class="brevo-btn"
|
|
305
|
+
title="Align Right"
|
|
306
|
+
on:click={() => execCmd("justifyRight")}
|
|
307
|
+
{disabled}>⫸</button
|
|
308
|
+
>
|
|
275
309
|
</div>
|
|
276
310
|
|
|
277
311
|
<div class="brevo-toolbar__divider"></div>
|
|
278
312
|
|
|
279
|
-
<button
|
|
313
|
+
<button
|
|
314
|
+
type="button"
|
|
315
|
+
class="brevo-btn"
|
|
316
|
+
title="Insert Link"
|
|
317
|
+
on:click={insertLink}
|
|
318
|
+
{disabled}>🔗</button
|
|
319
|
+
>
|
|
280
320
|
|
|
281
321
|
<!-- Variable injector -->
|
|
282
322
|
{#if variables.length > 0}
|
|
@@ -286,16 +326,20 @@
|
|
|
286
326
|
type="button"
|
|
287
327
|
class="brevo-btn brevo-btn--accent"
|
|
288
328
|
title="Insert Variable"
|
|
289
|
-
on:click|stopPropagation={() => {
|
|
290
|
-
|
|
291
|
-
|
|
329
|
+
on:click|stopPropagation={() => {
|
|
330
|
+
showVariableMenu = !showVariableMenu;
|
|
331
|
+
showHeadingMenu = false;
|
|
332
|
+
}}
|
|
333
|
+
{disabled}>{{ }} Insert Variable ▾</button
|
|
334
|
+
>
|
|
292
335
|
|
|
293
336
|
{#if showVariableMenu}
|
|
294
337
|
<div class="brevo-dropdown__menu brevo-dropdown__menu--variables">
|
|
295
338
|
{#each variables as variable}
|
|
296
339
|
<button type="button" on:click={() => insertVariable(variable)}>
|
|
297
340
|
<span class="brevo-var-label">{variable.label}</span>
|
|
298
|
-
<code class="brevo-var-key">{
|
|
341
|
+
<code class="brevo-var-key">{"{{ " + variable.key + " }}"}</code
|
|
342
|
+
>
|
|
299
343
|
</button>
|
|
300
344
|
{/each}
|
|
301
345
|
</div>
|
|
@@ -311,8 +355,8 @@
|
|
|
311
355
|
class:brevo-btn--active={isSourceMode}
|
|
312
356
|
title="Toggle Source View"
|
|
313
357
|
on:click={toggleSourceMode}
|
|
314
|
-
{disabled}
|
|
315
|
-
>
|
|
358
|
+
{disabled}>{isSourceMode ? "✎ Visual" : "</> Source"}</button
|
|
359
|
+
>
|
|
316
360
|
</div>
|
|
317
361
|
|
|
318
362
|
<!-- Editor Area -->
|
|
@@ -364,7 +408,7 @@
|
|
|
364
408
|
--brevo-btn-hover: #2a2a4a;
|
|
365
409
|
--brevo-btn-active: #3730a3;
|
|
366
410
|
--brevo-radius: 8px;
|
|
367
|
-
--brevo-font:
|
|
411
|
+
--brevo-font: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
|
|
368
412
|
|
|
369
413
|
font-family: var(--brevo-font);
|
|
370
414
|
border: 1.5px solid var(--brevo-border);
|
|
@@ -479,8 +523,14 @@
|
|
|
479
523
|
}
|
|
480
524
|
|
|
481
525
|
@keyframes brevo-dropdown-in {
|
|
482
|
-
from {
|
|
483
|
-
|
|
526
|
+
from {
|
|
527
|
+
opacity: 0;
|
|
528
|
+
transform: translateY(-4px);
|
|
529
|
+
}
|
|
530
|
+
to {
|
|
531
|
+
opacity: 1;
|
|
532
|
+
transform: translateY(0);
|
|
533
|
+
}
|
|
484
534
|
}
|
|
485
535
|
|
|
486
536
|
.brevo-dropdown__menu button {
|
|
@@ -541,12 +591,29 @@
|
|
|
541
591
|
pointer-events: none;
|
|
542
592
|
}
|
|
543
593
|
|
|
544
|
-
.brevo-editor :global(h1) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
594
|
+
.brevo-editor :global(h1) {
|
|
595
|
+
font-size: 1.8em;
|
|
596
|
+
font-weight: 700;
|
|
597
|
+
margin: 0.4em 0;
|
|
598
|
+
}
|
|
599
|
+
.brevo-editor :global(h2) {
|
|
600
|
+
font-size: 1.4em;
|
|
601
|
+
font-weight: 600;
|
|
602
|
+
margin: 0.4em 0;
|
|
603
|
+
}
|
|
604
|
+
.brevo-editor :global(h3) {
|
|
605
|
+
font-size: 1.15em;
|
|
606
|
+
font-weight: 600;
|
|
607
|
+
margin: 0.4em 0;
|
|
608
|
+
}
|
|
609
|
+
.brevo-editor :global(a) {
|
|
610
|
+
color: var(--brevo-accent-hover);
|
|
611
|
+
text-decoration: underline;
|
|
612
|
+
}
|
|
548
613
|
.brevo-editor :global(ul),
|
|
549
|
-
.brevo-editor :global(ol) {
|
|
614
|
+
.brevo-editor :global(ol) {
|
|
615
|
+
padding-left: 1.6em;
|
|
616
|
+
}
|
|
550
617
|
|
|
551
618
|
/* ── Source ── */
|
|
552
619
|
.brevo-source {
|
|
@@ -557,7 +624,7 @@
|
|
|
557
624
|
border: none;
|
|
558
625
|
background: #0d1117;
|
|
559
626
|
color: #c9d1d9;
|
|
560
|
-
font-family:
|
|
627
|
+
font-family: "JetBrains Mono", "Fira Code", "Cascadia Code", monospace;
|
|
561
628
|
font-size: 13px;
|
|
562
629
|
line-height: 1.65;
|
|
563
630
|
resize: vertical;
|