@emailmaker/emailmaker 1.1.12 → 1.1.18

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.
Files changed (45) hide show
  1. package/emailmaker-core.18e2e08a.js +1 -0
  2. package/emailmaker-esm.js +109 -19
  3. package/emailmaker.d.ts +44 -0
  4. package/emailmaker.js +593 -3
  5. package/iframe/js/amp.C-2gKk7c.js +1 -0
  6. package/iframe/js/iframe-eblock.js +1 -1
  7. package/iframe/js/iframe.js +1 -1
  8. package/iframe/js/rules.CNqvmp_P.js +1 -1
  9. package/iframe/js/sanitize-html/sanitize-html.js +21756 -0
  10. package/iframe/js/sanitize-html/sanitize-html.min.js +16923 -0
  11. package/package.json +1 -1
  12. package/singlepage_headless_plugin.html +592 -0
  13. package/static/core/{fe996f08.js → 1c0e987c.js} +1 -1
  14. package/static/core/{c7edc4c6.js → 22a59378.js} +2 -2
  15. package/static/core/2545db9f.js +1 -0
  16. package/static/core/4829f3dc.js +51 -0
  17. package/static/core/{8df8364d.js → 82a963a8.js} +2 -2
  18. package/static/core/{bb64d285.js → 83f8d1a4.js} +1 -1
  19. package/static/core/84263298.js +20 -0
  20. package/static/core/97c7d145.js +471 -0
  21. package/static/core/{81f3e3c0.js → 9a11e317.js} +1 -1
  22. package/static/core/{d97c92e6.js → a899caaf.js} +2 -2
  23. package/static/core/b7cabc9c.js +78 -0
  24. package/static/core/cd98a657.js +834 -0
  25. package/static/core/{eb32c1fb.js → da8f68e0.js} +2 -2
  26. package/static/core/{ad026780.js → e865979e.js} +1 -1
  27. package/static/esm/104e77cb.js +1 -0
  28. package/static/esm/193ef89c.js +7 -0
  29. package/static/esm/{27964dfe.js → b9d66884.js} +1 -1
  30. package/static/esm/c4069a00.js +1 -0
  31. package/two_step_headless_plugin.html +1 -1
  32. package/emailmaker-core.f22adcc6.js +0 -1
  33. package/iframe/js/amp.CUKrusRy.js +0 -1
  34. package/iframe/js/sanitize-html.BWcyAj0s.js +0 -1
  35. package/static/core/4f5fa8f8.js +0 -1
  36. package/static/core/73fecffa.js +0 -1
  37. package/static/core/795854f6.js +0 -1
  38. package/static/core/a8a4b287.js +0 -853
  39. package/static/core/b0b98d59.js +0 -490
  40. package/static/core/db57afb1.js +0 -78
  41. package/static/core/f95aa023.js +0 -51
  42. package/static/esm/b86c52d7.js +0 -1
  43. package/static/esm/cadc597d.js +0 -1
  44. /package/iframe/{iframe-eblock.f22adcc6.html → iframe-eblock.18e2e08a.html} +0 -0
  45. /package/iframe/{iframe.f22adcc6.html → iframe.18e2e08a.html} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emailmaker/emailmaker",
3
- "version": "1.1.12",
3
+ "version": "1.1.18",
4
4
  "private": false,
5
5
  "main": "emailmaker.js",
6
6
  "module": "emailmaker-esm.js",
@@ -0,0 +1,592 @@
1
+ <!doctype html>
2
+ <html lang="ru">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Single-page host demo</title>
6
+ <script id="plugin-script" type="text/javascript" src="emailmaker.js?18e2e08a"></script>
7
+ <style>
8
+ :root {
9
+ --bg: #eef1f8;
10
+ --card: #ffffff;
11
+ --text: #1a1d2e;
12
+ --muted: #6b7289;
13
+ --border: #dde1ec;
14
+ --border-focus: #5b5cf6;
15
+ --primary: #5b5cf6;
16
+ --primary-hover: #4a4be8;
17
+ --secondary-bg: #f3f4f8;
18
+ --secondary-hover: #e8eaf2;
19
+ --shadow: 0 12px 40px rgba(26, 29, 46, 0.08);
20
+ --radius: 16px;
21
+ }
22
+ * {
23
+ box-sizing: border-box;
24
+ }
25
+ html,
26
+ body {
27
+ margin: 0;
28
+ min-height: 100%;
29
+ }
30
+ body {
31
+ font-family:
32
+ 'Segoe UI',
33
+ system-ui,
34
+ -apple-system,
35
+ sans-serif;
36
+ font-size: 15px;
37
+ line-height: 1.5;
38
+ color: var(--text);
39
+ background: radial-gradient(circle at 20% 0%, #e8eaff 0%, transparent 45%), var(--bg);
40
+ }
41
+ body.editor-open {
42
+ overflow: hidden;
43
+ }
44
+ .page {
45
+ min-height: 100vh;
46
+ display: flex;
47
+ align-items: center;
48
+ justify-content: center;
49
+ padding: 32px 20px;
50
+ }
51
+ .card {
52
+ width: 100%;
53
+ max-width: 520px;
54
+ background: var(--card);
55
+ border-radius: var(--radius);
56
+ box-shadow: var(--shadow);
57
+ padding: 32px;
58
+ }
59
+ .card h1 {
60
+ margin: 0 0 8px;
61
+ font-size: 22px;
62
+ font-weight: 600;
63
+ letter-spacing: -0.02em;
64
+ }
65
+ .card__hint {
66
+ margin: 0 0 28px;
67
+ color: var(--muted);
68
+ font-size: 14px;
69
+ }
70
+ .field {
71
+ margin-bottom: 20px;
72
+ }
73
+ .field__label {
74
+ display: block;
75
+ margin-bottom: 8px;
76
+ font-size: 13px;
77
+ font-weight: 600;
78
+ color: var(--muted);
79
+ text-transform: uppercase;
80
+ letter-spacing: 0.04em;
81
+ }
82
+ .field__input {
83
+ width: 100%;
84
+ padding: 14px 16px;
85
+ border: 1px solid var(--border);
86
+ border-radius: 12px;
87
+ background: #fafbfe;
88
+ color: var(--text);
89
+ font: inherit;
90
+ font-size: 16px;
91
+ transition:
92
+ border-color 0.15s,
93
+ box-shadow 0.15s,
94
+ background 0.15s;
95
+ }
96
+ .field__input::placeholder {
97
+ color: #a0a6b8;
98
+ }
99
+ .field__input:hover {
100
+ border-color: #c5cad8;
101
+ background: #fff;
102
+ }
103
+ .field__input:focus {
104
+ outline: none;
105
+ border-color: var(--border-focus);
106
+ background: #fff;
107
+ box-shadow: 0 0 0 4px rgba(91, 92, 246, 0.12);
108
+ }
109
+ .actions {
110
+ display: flex;
111
+ flex-wrap: wrap;
112
+ gap: 12px;
113
+ margin-top: 28px;
114
+ }
115
+ .btn {
116
+ display: inline-flex;
117
+ align-items: center;
118
+ justify-content: center;
119
+ min-height: 48px;
120
+ padding: 0 24px;
121
+ border: none;
122
+ border-radius: 12px;
123
+ font: inherit;
124
+ font-size: 15px;
125
+ font-weight: 600;
126
+ cursor: pointer;
127
+ transition:
128
+ background 0.15s,
129
+ transform 0.1s,
130
+ opacity 0.15s;
131
+ }
132
+ .btn:active:not(:disabled) {
133
+ transform: scale(0.98);
134
+ }
135
+ .btn:disabled {
136
+ opacity: 0.55;
137
+ cursor: not-allowed;
138
+ }
139
+ .btn--primary {
140
+ flex: 1;
141
+ min-width: 140px;
142
+ background: var(--primary);
143
+ color: #fff;
144
+ }
145
+ .btn--primary:hover:not(:disabled) {
146
+ background: var(--primary-hover);
147
+ }
148
+ .btn--secondary {
149
+ flex: 1;
150
+ min-width: 140px;
151
+ background: var(--secondary-bg);
152
+ color: var(--text);
153
+ }
154
+ .btn--secondary:hover:not(:disabled) {
155
+ background: var(--secondary-hover);
156
+ }
157
+ .status {
158
+ margin-top: 16px;
159
+ min-height: 20px;
160
+ font-size: 13px;
161
+ color: var(--muted);
162
+ }
163
+ .status--busy {
164
+ color: #b45309;
165
+ }
166
+ .status--ok {
167
+ color: #059669;
168
+ }
169
+ #view_host.is-hidden {
170
+ display: none !important;
171
+ }
172
+ #view_editor {
173
+ display: none;
174
+ position: fixed;
175
+ top: 0;
176
+ left: 0;
177
+ right: 0;
178
+ bottom: 0;
179
+ width: 100vw;
180
+ height: 100vh;
181
+ height: 100dvh;
182
+ margin: 0;
183
+ padding: 0;
184
+ z-index: 2147483647;
185
+ background: #fff;
186
+ overflow: hidden;
187
+ }
188
+ #view_editor.is-open {
189
+ display: block;
190
+ }
191
+ #plugin-editor-mount {
192
+ position: absolute;
193
+ top: 0;
194
+ left: 0;
195
+ right: 0;
196
+ bottom: 0;
197
+ width: 100%;
198
+ height: 100%;
199
+ overflow: hidden;
200
+ }
201
+ </style>
202
+ </head>
203
+ <body>
204
+ <div id="view_host">
205
+ <div class="page">
206
+ <div class="card">
207
+ <h1>Письмо</h1>
208
+ <p class="card__hint">Тема и прехедер на стороне хоста, дизайн — в редакторе.</p>
209
+
210
+ <div class="field">
211
+ <label class="field__label" for="field_subject">Тема</label>
212
+ <input
213
+ id="field_subject"
214
+ class="field__input"
215
+ type="text"
216
+ autocomplete="off"
217
+ placeholder="Например: Скидка 20% только до пятницы"
218
+ />
219
+ </div>
220
+
221
+ <div class="field">
222
+ <label class="field__label" for="field_preheader">Прехедер</label>
223
+ <input
224
+ id="field_preheader"
225
+ class="field__input"
226
+ type="text"
227
+ autocomplete="off"
228
+ placeholder="Короткий текст, видимый в списке писем"
229
+ />
230
+ </div>
231
+
232
+ <div class="actions">
233
+ <button type="button" class="btn btn--primary" id="btn_save">Сохранить</button>
234
+ <button type="button" class="btn btn--secondary design-nav" id="btn_edit_design">Редактировать дизайн</button>
235
+ </div>
236
+
237
+ <p class="status" id="status_line" aria-live="polite"></p>
238
+ </div>
239
+ </div>
240
+ </div>
241
+
242
+ <div id="view_editor">
243
+ <div id="plugin-editor-mount"></div>
244
+ </div>
245
+
246
+ <script async defer>
247
+ const APP_NAME = "emailmaker";
248
+ const getEditorSdk = () => window[APP_NAME];
249
+
250
+ const STORAGE_KEY = 'plugin_demo_singlepage_letter_v4';
251
+ const DEMO_LETTER_ID = 'test_test_test_test';
252
+ const INITIAL_TEMPLATE_ID = 321739;
253
+
254
+ const SAMPLE_AUTOSAVE_HTML =
255
+ '<!DOCTYPE html><html><head><meta charset="utf-8"></head><body style="margin:0;padding:24px;font-family:Arial,sans-serif;"><h1>Черновик письма</h1><p>Контент из редактора.</p></body></html>';
256
+ const COMMON_STYLES =
257
+ '{"text":{"fontFamily":["Arial,sans-serif"],"fontSize":16,"color":"#333"},"headings":{"fontSize":24,"bold":true}}';
258
+
259
+ const systemfields = [
260
+ { label: 'Имя получателя', value: 'ToName' },
261
+ { label: 'Имя', value: 'Name' },
262
+ ];
263
+
264
+ const getAuthToken = async () => {
265
+ const formdata = new FormData();
266
+ formdata.append('grant_type', 'client_credentials');
267
+ formdata.append('client_id', '****');
268
+ formdata.append('client_secret', '****');
269
+ formdata.append('user_id', 'test_test_test');
270
+ return fetch('/api?oauth=token', { method: 'POST', body: formdata })
271
+ .then((r) => r.json())
272
+ .then((r) => r.access_token)
273
+ .catch(() => '');
274
+ };
275
+
276
+ const defaultLetterStore = () => ({
277
+ id: DEMO_LETTER_ID,
278
+ subject: 'Тема письма',
279
+ html: SAMPLE_AUTOSAVE_HTML,
280
+ json: JSON.stringify({
281
+ id: DEMO_LETTER_ID,
282
+ subject: 'Тема письма',
283
+ preheader: 'Текст прехедера для инбокса',
284
+ common_styles: COMMON_STYLES,
285
+ }),
286
+ revisions: [],
287
+ });
288
+
289
+ let letterStore = loadLetterFromStorage();
290
+ let editorInstance = null;
291
+ let isEmailInitialized = false;
292
+ let updateLoading = false;
293
+
294
+ const statusLine = document.getElementById('status_line');
295
+ const btnSave = document.getElementById('btn_save');
296
+ const btnEditDesign = document.getElementById('btn_edit_design');
297
+
298
+ const setStatus = (text, kind = '') => {
299
+ statusLine.textContent = text;
300
+ statusLine.className = 'status' + (kind ? ` status--${kind}` : '');
301
+ };
302
+
303
+ const setBusy = (busy) => {
304
+ updateLoading = busy;
305
+ btnSave.disabled = busy;
306
+ btnEditDesign.disabled = busy;
307
+ if (busy) {
308
+ setStatus('Сохранение…', 'busy');
309
+ }
310
+ };
311
+
312
+ function loadLetterFromStorage() {
313
+ try {
314
+ const raw = localStorage.getItem(STORAGE_KEY);
315
+ if (!raw) return defaultLetterStore();
316
+ return { ...defaultLetterStore(), ...JSON.parse(raw), id: DEMO_LETTER_ID };
317
+ } catch {
318
+ return defaultLetterStore();
319
+ }
320
+ }
321
+
322
+ function saveLetterToStorage() {
323
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(letterStore));
324
+ }
325
+
326
+ const parseLetterJson = () => {
327
+ try {
328
+ return JSON.parse(letterStore.json || '{}');
329
+ } catch {
330
+ return {};
331
+ }
332
+ };
333
+
334
+ const getFieldValues = () => ({
335
+ subject: document.getElementById('field_subject').value,
336
+ preheader: document.getElementById('field_preheader').value,
337
+ });
338
+
339
+ const persistHostMetadataFromInputs = () => {
340
+ const { subject, preheader } = getFieldValues();
341
+ letterStore.subject = subject;
342
+ letterStore.json = JSON.stringify({ ...parseLetterJson(), subject, preheader });
343
+ saveLetterToStorage();
344
+ };
345
+
346
+ const applySubjectFromEditor = (subject) => {
347
+ const value = String(subject ?? '');
348
+ letterStore.subject = value;
349
+ letterStore.json = JSON.stringify({ ...parseLetterJson(), subject: value });
350
+ document.getElementById('field_subject').value = value;
351
+ saveLetterToStorage();
352
+ };
353
+
354
+ const applyPreheaderFromEditor = (preheader) => {
355
+ const value = String(preheader ?? '');
356
+ letterStore.json = JSON.stringify({ ...parseLetterJson(), preheader: value });
357
+ document.getElementById('field_preheader').value = value;
358
+ saveLetterToStorage();
359
+ };
360
+
361
+ const resolveMetadata = (data, patch = {}) => {
362
+ const host = getFieldValues();
363
+ const email = parseLetterJson();
364
+ const subject = (
365
+ patch.subject ??
366
+ data?.subject ??
367
+ host.subject ??
368
+ letterStore.subject ??
369
+ email.subject ??
370
+ ''
371
+ ).trim();
372
+ const preheader =
373
+ patch.preheader ??
374
+ (data?.preheader !== undefined ? data.preheader : undefined) ??
375
+ host.preheader ??
376
+ email.preheader ??
377
+ '';
378
+ return { subject, preheader: String(preheader) };
379
+ };
380
+
381
+ const syncFieldsFromStore = () => {
382
+ const email = parseLetterJson();
383
+ document.getElementById('field_subject').value = letterStore.subject ?? email.subject ?? '';
384
+ document.getElementById('field_preheader').value = email.preheader ?? '';
385
+ };
386
+
387
+ const updateEditButtonLabel = () => {
388
+ btnEditDesign.textContent = letterStore.html?.trim()
389
+ ? 'Редактировать дизайн'
390
+ : 'Добавить дизайн';
391
+ };
392
+
393
+ const getRevisionsForPlugin = () =>
394
+ (letterStore.revisions || []).map((r) => ({ ...r, parent: DEMO_LETTER_ID }));
395
+
396
+ const handleReadEmailRevisions = async () => getRevisionsForPlugin();
397
+
398
+ const buildPluginHandlers = () => ({
399
+ handleReadEmailRevisions,
400
+ handleEmailInit: async () => {
401
+ isEmailInitialized = true;
402
+ },
403
+ handleSubjectChanged: (subject) => {
404
+ applySubjectFromEditor(subject);
405
+ },
406
+ handlePreheaderChanged: (preheader) => {
407
+ applyPreheaderFromEditor(preheader);
408
+ },
409
+ });
410
+
411
+ const buildEditorConfig = () => ({
412
+ locale: 'ru_RU',
413
+ mode: 'pro',
414
+ theme: 'light',
415
+ revisionService: true,
416
+ previewIconEnabled: true,
417
+ livePreviewEnabled: true,
418
+ nextButtonEnabled: true,
419
+ mergeTags: {
420
+ syntax: ['{{%value%}}'],
421
+ items: [{ label: 'System', value: 'System', children: systemfields }],
422
+ },
423
+ });
424
+
425
+ const updateJsonAndEmail = async (data, patch = {}) => {
426
+ const email = parseLetterJson();
427
+ const { subject, preheader } = resolveMetadata(data, patch);
428
+ letterStore.subject = subject;
429
+ letterStore.json = JSON.stringify({
430
+ ...email,
431
+ ...data,
432
+ ...patch,
433
+ subject,
434
+ preheader,
435
+ });
436
+ if (editorInstance) {
437
+ const compiled = await editorInstance.compileEmail();
438
+ if (compiled?.compiled?.html) letterStore.html = compiled.compiled.html;
439
+ }
440
+ saveLetterToStorage();
441
+ syncFieldsFromStore();
442
+ updateEditButtonLabel();
443
+ };
444
+
445
+ const updateLetterAndJson = async ({ subject, preheader }) => {
446
+ if (!editorInstance) {
447
+ letterStore.subject = subject;
448
+ letterStore.json = JSON.stringify({ ...parseLetterJson(), subject, preheader });
449
+ saveLetterToStorage();
450
+ syncFieldsFromStore();
451
+ return;
452
+ }
453
+ setBusy(true);
454
+ try {
455
+ const email = parseLetterJson();
456
+ await editorInstance.show({
457
+ ...email,
458
+ id: DEMO_LETTER_ID,
459
+ subject,
460
+ preheader,
461
+ code: letterStore.html,
462
+ });
463
+ const data = await editorInstance.getEmail();
464
+ await updateJsonAndEmail({ ...data, id: DEMO_LETTER_ID }, { subject, preheader });
465
+ } finally {
466
+ setBusy(false);
467
+ }
468
+ };
469
+
470
+ const destroyEditor = async () => {
471
+ if (editorInstance) {
472
+ await editorInstance.destroy();
473
+ editorInstance = null;
474
+ }
475
+ isEmailInitialized = false;
476
+ };
477
+
478
+ const replaceEditorMount = () => {
479
+ const parent = document.getElementById('view_editor');
480
+ const old = document.getElementById('plugin-editor-mount');
481
+ const fresh = document.createElement('div');
482
+ fresh.id = 'plugin-editor-mount';
483
+ parent.replaceChild(fresh, old);
484
+ };
485
+
486
+ const buildDesignContent = ({ useTemplate = false } = {}) => {
487
+ const { subject, preheader } = getFieldValues();
488
+ const email = { ...parseLetterJson(), subject, preheader };
489
+ delete email.code;
490
+ const autosaves = getRevisionsForPlugin();
491
+ if (!useTemplate && letterStore.html?.trim()) {
492
+ return { email: { ...email, id: DEMO_LETTER_ID, code: letterStore.html }, autosaves };
493
+ }
494
+ return { templateId: INITIAL_TEMPLATE_ID, email: { ...email, id: DEMO_LETTER_ID }, autosaves };
495
+ };
496
+
497
+ const initHeadlessEditor = async () => {
498
+ if (!letterStore.html?.trim()) return;
499
+ await destroyEditor();
500
+ const email = parseLetterJson();
501
+ editorInstance = await getEditorSdk().init({
502
+ getAuthToken,
503
+ config: buildEditorConfig(),
504
+ content: {
505
+ email: {
506
+ ...email,
507
+ id: DEMO_LETTER_ID,
508
+ subject: letterStore.subject,
509
+ preheader: email.preheader,
510
+ code: letterStore.html,
511
+ },
512
+ autosaves: getRevisionsForPlugin(),
513
+ },
514
+ ...buildPluginHandlers(),
515
+ });
516
+ };
517
+
518
+ const initFullEditor = async ({ useTemplate = false } = {}) => {
519
+ await destroyEditor();
520
+ editorInstance = await getEditorSdk().init({
521
+ getAuthToken,
522
+ element: '#plugin-editor-mount',
523
+ content: buildDesignContent({ useTemplate }),
524
+ config: buildEditorConfig(),
525
+ ...buildPluginHandlers(),
526
+ handleNextButtonClick: async (data) => {
527
+ await updateJsonAndEmail(data);
528
+ await showHostView();
529
+ setStatus('Дизайн сохранён', 'ok');
530
+ },
531
+ handleEmailAutosave: async ({ value }) => {
532
+ if (!isEmailInitialized) return value;
533
+ await updateJsonAndEmail(value);
534
+ return value;
535
+ },
536
+ handleSaveEmail: async ({ value }) => {
537
+ await updateJsonAndEmail(value);
538
+ return value;
539
+ },
540
+ });
541
+ };
542
+
543
+ const showHostView = async () => {
544
+ document.body.classList.remove('editor-open');
545
+ document.getElementById('view_editor').classList.remove('is-open');
546
+ document.getElementById('view_host').classList.remove('is-hidden');
547
+ await initHeadlessEditor();
548
+ syncFieldsFromStore();
549
+ updateEditButtonLabel();
550
+ };
551
+
552
+ const showEditorView = async ({ useTemplate = false } = {}) => {
553
+ if (updateLoading) return;
554
+ document.getElementById('view_host').classList.add('is-hidden');
555
+ document.getElementById('view_editor').classList.add('is-open');
556
+ document.body.classList.add('editor-open');
557
+ window.scrollTo(0, 0);
558
+ replaceEditorMount();
559
+ await initFullEditor({ useTemplate });
560
+ };
561
+
562
+ const goToDesign = async () => {
563
+ if (updateLoading) return;
564
+ persistHostMetadataFromInputs();
565
+ await showEditorView({ useTemplate: !letterStore.html?.trim() });
566
+ };
567
+
568
+ const onSave = async () => {
569
+ const { subject, preheader } = getFieldValues();
570
+ setBusy(true);
571
+ try {
572
+ await updateLetterAndJson({ subject, preheader });
573
+ setStatus('Сохранено', 'ok');
574
+ } catch (e) {
575
+ setStatus('Ошибка сохранения', 'busy');
576
+ console.error(e);
577
+ } finally {
578
+ setBusy(false);
579
+ }
580
+ };
581
+
582
+ btnSave.addEventListener('click', () => void onSave());
583
+ btnEditDesign.addEventListener('click', () => void goToDesign());
584
+
585
+ (async () => {
586
+ syncFieldsFromStore();
587
+ updateEditButtonLabel();
588
+ await showHostView();
589
+ })();
590
+ </script>
591
+ </body>
592
+ </html>