@fufulog/brevomorphic-cms-sdk 1.0.1 → 1.0.3
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/brevoClient.js +11 -2
- package/dist/repository.js +15 -0
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
- package/react/BrevoWysiwyg.tsx +0 -1
- package/src/brevoClient.ts +14 -2
- package/src/repository.ts +16 -1
- package/src/types.ts +1 -1
- package/svelte/BrevoWysiwyg.svelte +137 -70
package/dist/brevoClient.js
CHANGED
|
@@ -127,11 +127,20 @@ export class BrevoClient {
|
|
|
127
127
|
*/
|
|
128
128
|
async sendEmail(params) {
|
|
129
129
|
try {
|
|
130
|
-
|
|
130
|
+
const replyToEmail = params.variables?.replyToEmail;
|
|
131
|
+
const replyToName = params.variables?.replyToName;
|
|
132
|
+
const payload = {
|
|
131
133
|
templateId: params.templateId,
|
|
132
134
|
to: [{ email: params.to }],
|
|
133
135
|
params: params.variables || {},
|
|
134
|
-
}
|
|
136
|
+
};
|
|
137
|
+
if (replyToEmail) {
|
|
138
|
+
payload.replyTo = {
|
|
139
|
+
email: replyToEmail,
|
|
140
|
+
...(replyToName ? { name: replyToName } : {}),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
await this.client.post('/smtp/email', payload);
|
|
135
144
|
}
|
|
136
145
|
catch (error) {
|
|
137
146
|
const msg = error.response?.data?.message || error.message;
|
package/dist/repository.js
CHANGED
|
@@ -33,6 +33,9 @@ export class EmailTemplateRepository {
|
|
|
33
33
|
* Fetch all event-to-template mappings from the local database
|
|
34
34
|
*/
|
|
35
35
|
async getAllMappings() {
|
|
36
|
+
if (this.dbType === 'custom') {
|
|
37
|
+
return this.dbClient.getAllMappings();
|
|
38
|
+
}
|
|
36
39
|
if (this.dbType === 'firestore') {
|
|
37
40
|
const snapshot = await this.dbClient.collection(this.tableName).get();
|
|
38
41
|
const mappings = [];
|
|
@@ -64,6 +67,9 @@ export class EmailTemplateRepository {
|
|
|
64
67
|
* Fetch mapping for a specific template ID
|
|
65
68
|
*/
|
|
66
69
|
async getMappingByTemplateId(templateId) {
|
|
70
|
+
if (this.dbType === 'custom') {
|
|
71
|
+
return this.dbClient.getMappingByTemplateId(templateId);
|
|
72
|
+
}
|
|
67
73
|
if (this.dbType === 'firestore') {
|
|
68
74
|
const doc = await this.dbClient.collection(this.tableName).doc(templateId.toString()).get();
|
|
69
75
|
if (!doc.exists)
|
|
@@ -98,6 +104,9 @@ export class EmailTemplateRepository {
|
|
|
98
104
|
* Assumes we want the active template linked to this event.
|
|
99
105
|
*/
|
|
100
106
|
async getActiveMappingByEvent(eventName) {
|
|
107
|
+
if (this.dbType === 'custom') {
|
|
108
|
+
return this.dbClient.getActiveMappingByEvent(eventName);
|
|
109
|
+
}
|
|
101
110
|
if (this.dbType === 'firestore') {
|
|
102
111
|
const snapshot = await this.dbClient
|
|
103
112
|
.collection(this.tableName)
|
|
@@ -140,6 +149,9 @@ export class EmailTemplateRepository {
|
|
|
140
149
|
* Upsert a mapping (insert or update template config)
|
|
141
150
|
*/
|
|
142
151
|
async upsertMapping(templateId, eventName, isActive) {
|
|
152
|
+
if (this.dbType === 'custom') {
|
|
153
|
+
return this.dbClient.upsertMapping(templateId, eventName, isActive);
|
|
154
|
+
}
|
|
143
155
|
if (this.dbType === 'firestore') {
|
|
144
156
|
await this.dbClient
|
|
145
157
|
.collection(this.tableName)
|
|
@@ -186,6 +198,9 @@ export class EmailTemplateRepository {
|
|
|
186
198
|
* If mapping does not exist, insert blank inactive mapping.
|
|
187
199
|
*/
|
|
188
200
|
async insertJITMapping(templateId) {
|
|
201
|
+
if (this.dbType === 'custom') {
|
|
202
|
+
return this.dbClient.insertJITMapping(templateId);
|
|
203
|
+
}
|
|
189
204
|
if (this.dbType === 'firestore') {
|
|
190
205
|
const docRef = this.dbClient.collection(this.tableName).doc(templateId.toString());
|
|
191
206
|
const doc = await docRef.get();
|
package/dist/types.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ export interface SDKConfig {
|
|
|
40
40
|
name?: string;
|
|
41
41
|
email: string;
|
|
42
42
|
};
|
|
43
|
-
dbType: 'postgres' | 'mysql' | 'firestore';
|
|
43
|
+
dbType: 'postgres' | 'mysql' | 'firestore' | 'custom';
|
|
44
44
|
/**
|
|
45
45
|
* For 'postgres': an object satisfying { query: (sql, params) => Promise<{ rows: any[] }> } or equivalent
|
|
46
46
|
* For 'mysql': an object satisfying { query: (sql, params) => Promise<[any[], any]> } or equivalent
|
package/package.json
CHANGED
package/react/BrevoWysiwyg.tsx
CHANGED
package/src/brevoClient.ts
CHANGED
|
@@ -158,11 +158,23 @@ export class BrevoClient {
|
|
|
158
158
|
variables?: Record<string, any>;
|
|
159
159
|
}): Promise<void> {
|
|
160
160
|
try {
|
|
161
|
-
|
|
161
|
+
const replyToEmail = params.variables?.replyToEmail;
|
|
162
|
+
const replyToName = params.variables?.replyToName;
|
|
163
|
+
|
|
164
|
+
const payload: any = {
|
|
162
165
|
templateId: params.templateId,
|
|
163
166
|
to: [{ email: params.to }],
|
|
164
167
|
params: params.variables || {},
|
|
165
|
-
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
if (replyToEmail) {
|
|
171
|
+
payload.replyTo = {
|
|
172
|
+
email: replyToEmail,
|
|
173
|
+
...(replyToName ? { name: replyToName } : {}),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
await this.client.post('/smtp/email', payload);
|
|
166
178
|
} catch (error: any) {
|
|
167
179
|
const msg = error.response?.data?.message || error.message;
|
|
168
180
|
throw new Error(`Failed to send event email via Brevo: ${msg}`);
|
package/src/repository.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SDKConfig, LocalMapping } from './types.js';
|
|
2
2
|
|
|
3
3
|
export class EmailTemplateRepository {
|
|
4
|
-
private dbType: 'postgres' | 'mysql' | 'firestore';
|
|
4
|
+
private dbType: 'postgres' | 'mysql' | 'firestore' | 'custom';
|
|
5
5
|
private dbClient: any;
|
|
6
6
|
private tableName: string;
|
|
7
7
|
|
|
@@ -38,6 +38,9 @@ export class EmailTemplateRepository {
|
|
|
38
38
|
* Fetch all event-to-template mappings from the local database
|
|
39
39
|
*/
|
|
40
40
|
async getAllMappings(): Promise<LocalMapping[]> {
|
|
41
|
+
if (this.dbType === 'custom') {
|
|
42
|
+
return this.dbClient.getAllMappings();
|
|
43
|
+
}
|
|
41
44
|
if (this.dbType === 'firestore') {
|
|
42
45
|
const snapshot = await this.dbClient.collection(this.tableName).get();
|
|
43
46
|
const mappings: LocalMapping[] = [];
|
|
@@ -69,6 +72,9 @@ export class EmailTemplateRepository {
|
|
|
69
72
|
* Fetch mapping for a specific template ID
|
|
70
73
|
*/
|
|
71
74
|
async getMappingByTemplateId(templateId: number): Promise<LocalMapping | null> {
|
|
75
|
+
if (this.dbType === 'custom') {
|
|
76
|
+
return this.dbClient.getMappingByTemplateId(templateId);
|
|
77
|
+
}
|
|
72
78
|
if (this.dbType === 'firestore') {
|
|
73
79
|
const doc = await this.dbClient.collection(this.tableName).doc(templateId.toString()).get();
|
|
74
80
|
if (!doc.exists) return null;
|
|
@@ -102,6 +108,9 @@ export class EmailTemplateRepository {
|
|
|
102
108
|
* Assumes we want the active template linked to this event.
|
|
103
109
|
*/
|
|
104
110
|
async getActiveMappingByEvent(eventName: string): Promise<LocalMapping | null> {
|
|
111
|
+
if (this.dbType === 'custom') {
|
|
112
|
+
return this.dbClient.getActiveMappingByEvent(eventName);
|
|
113
|
+
}
|
|
105
114
|
if (this.dbType === 'firestore') {
|
|
106
115
|
const snapshot = await this.dbClient
|
|
107
116
|
.collection(this.tableName)
|
|
@@ -145,6 +154,9 @@ export class EmailTemplateRepository {
|
|
|
145
154
|
* Upsert a mapping (insert or update template config)
|
|
146
155
|
*/
|
|
147
156
|
async upsertMapping(templateId: number, eventName: string, isActive: boolean): Promise<void> {
|
|
157
|
+
if (this.dbType === 'custom') {
|
|
158
|
+
return this.dbClient.upsertMapping(templateId, eventName, isActive);
|
|
159
|
+
}
|
|
148
160
|
if (this.dbType === 'firestore') {
|
|
149
161
|
await this.dbClient
|
|
150
162
|
.collection(this.tableName)
|
|
@@ -193,6 +205,9 @@ export class EmailTemplateRepository {
|
|
|
193
205
|
* If mapping does not exist, insert blank inactive mapping.
|
|
194
206
|
*/
|
|
195
207
|
async insertJITMapping(templateId: number): Promise<void> {
|
|
208
|
+
if (this.dbType === 'custom') {
|
|
209
|
+
return this.dbClient.insertJITMapping(templateId);
|
|
210
|
+
}
|
|
196
211
|
if (this.dbType === 'firestore') {
|
|
197
212
|
const docRef = this.dbClient.collection(this.tableName).doc(templateId.toString());
|
|
198
213
|
const doc = await docRef.get();
|
package/src/types.ts
CHANGED
|
@@ -46,7 +46,7 @@ export interface SDKConfig {
|
|
|
46
46
|
name?: string;
|
|
47
47
|
email: string;
|
|
48
48
|
};
|
|
49
|
-
dbType: 'postgres' | 'mysql' | 'firestore';
|
|
49
|
+
dbType: 'postgres' | 'mysql' | 'firestore' | 'custom';
|
|
50
50
|
/**
|
|
51
51
|
* For 'postgres': an object satisfying { query: (sql, params) => Promise<{ rows: any[] }> } or equivalent
|
|
52
52
|
* For 'mysql': an object satisfying { query: (sql, params) => Promise<[any[], any]> } or equivalent
|
|
@@ -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;
|