PluginProbe
Sharing Image / 2.0.4
Sharing Image v2.0.4
3.10 trunk 2.0 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.13 2.0.14 2.0.15 2.0.16 2.0.17 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 3.0 3.1 3.2 3.3 All 29 releases
sharing-image / assets / scripts / widget.js

widget.js in Sharing Image 2.0.4, at assets/scripts/widget.js

3,426 lines 83.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /******/ (() => { // webpackBootstrap
2 /******/ "use strict";
3 var __webpack_exports__ = {};
4
5 ;// CONCATENATED MODULE: ./src/scripts/builders/element.js
6 /**
7 * Helper to create new DOM element.
8 *
9 * @param {string} tag Element tagname.
10 * @param {Object} args List of element options.
11 */
12 function buildElement(tag, args) {
13 const element = document.createElement(tag); // Set class list
14
15 if (args.hasOwnProperty('classes')) {
16 args.classes.forEach(cl => {
17 element.classList.add(cl);
18 });
19 } // Set textContent
20
21
22 if (args.hasOwnProperty('text')) {
23 element.textContent = args.text;
24 } // Set innerHTML
25
26
27 if (args.hasOwnProperty('html')) {
28 element.innerHTML = args.html;
29 } // Set attributes
30
31
32 if (args.hasOwnProperty('attributes')) {
33 for (const key in args.attributes) {
34 const value = args.attributes[key];
35
36 if (undefined === value) {
37 continue;
38 }
39
40 element.setAttribute(key, value);
41 }
42 } // Set data attributes
43
44
45 if (args.hasOwnProperty('dataset')) {
46 for (const key in args.dataset) {
47 element.setAttribute('data-' + key, args.dataset[key]);
48 }
49 } // Append child
50
51
52 if (args.hasOwnProperty('append')) {
53 args.append.appendChild(element);
54 } // Prepend child
55
56
57 if (args.hasOwnProperty('prepend')) {
58 args.prepend.insertBefore(element, args.prepend.firstChild);
59 }
60
61 return element;
62 }
63
64 /* harmony default export */ const builders_element = (buildElement);
65 ;// CONCATENATED MODULE: ./src/scripts/builders/input.js
66
67
68 /**
69 * Helper to create input field.
70 *
71 * @param {Object} args List of control options.
72 * @param {HTMLElement} parent Parent HTML element to append this field.
73 */
74
75 function buildInput(args, parent) {
76 const field = builders_element('div', {
77 classes: args.classes || [],
78 append: parent
79 });
80
81 if (args.hasOwnProperty('label')) {
82 const label = builders_element('h4', {
83 text: args.label
84 });
85
86 if (null !== args.label) {
87 field.appendChild(label);
88 }
89 }
90
91 const input = builders_element('input', {
92 attributes: {
93 type: 'text'
94 },
95 dataset: args.dataset || {},
96 append: field
97 }); // Set attributes.
98
99 if (args.hasOwnProperty('attributes')) {
100 for (const key in args.attributes) {
101 const value = args.attributes[key];
102
103 if (undefined === value) {
104 continue;
105 }
106
107 input.setAttribute(key, value);
108 }
109 }
110
111 if ('range' === input.type) {
112 const counter = builders.element('em', {
113 text: input.value,
114 append: field
115 });
116 input.addEventListener('change', () => {
117 counter.textContent = input.value;
118 });
119 input.addEventListener('input', () => {
120 counter.textContent = input.value;
121 });
122 }
123
124 return input;
125 }
126
127 /* harmony default export */ const input = (buildInput);
128 ;// CONCATENATED MODULE: ./src/scripts/builders/checkbox.js
129
130 /**
131 * Helper to create radio field.
132 *
133 * @param {Object} args List of control options.
134 * @param {HTMLElement} parent Parent HTML element to append this field.
135 */
136
137 function buildCheckbox(args, parent) {
138 const field = builders_element('label', {
139 classes: args.classes || [],
140 append: parent
141 });
142 const checkbox = builders_element('input', {
143 attributes: {
144 type: 'checkbox'
145 },
146 dataset: args.dataset || {},
147 append: field
148 }); // Set attributes
149
150 if (args.hasOwnProperty('attributes')) {
151 for (const key in args.attributes) {
152 const value = args.attributes[key];
153
154 if (undefined === value) {
155 continue;
156 }
157
158 checkbox.setAttribute(key, value);
159 }
160 }
161
162 if (args.hasOwnProperty('checked')) {
163 const checked = args.checked;
164
165 if (checked && checked === checkbox.value) {
166 checkbox.setAttribute('checked', 'checked');
167 }
168 }
169
170 if (args.hasOwnProperty('label')) {
171 const label = builders_element('span', {
172 text: args.label
173 });
174
175 if (null !== args.label) {
176 field.appendChild(label);
177 }
178 }
179
180 return checkbox;
181 }
182
183 /* harmony default export */ const builders_checkbox = (buildCheckbox);
184 ;// CONCATENATED MODULE: ./src/scripts/builders/radio.js
185
186 /**
187 * Helper to create radio field.
188 *
189 * @param {Object} args List of control options.
190 * @param {HTMLElement} parent Parent HTML element to append this field.
191 */
192
193 function buildRadio(args, parent) {
194 const field = builders_element('label', {
195 classes: args.classes || [],
196 append: parent
197 });
198 const radio = builders_element('input', {
199 attributes: {
200 type: 'radio'
201 },
202 dataset: args.dataset || {},
203 append: field
204 }); // Set attributes
205
206 if (args.hasOwnProperty('attributes')) {
207 for (const key in args.attributes) {
208 const value = args.attributes[key];
209
210 if (undefined === value) {
211 continue;
212 }
213
214 radio.setAttribute(key, value);
215 }
216 }
217
218 if (args.hasOwnProperty('checked')) {
219 const checked = args.checked;
220
221 if (checked && checked === radio.value) {
222 radio.setAttribute('checked', 'checked');
223 }
224 }
225
226 if (args.hasOwnProperty('label')) {
227 const label = builders_element('span', {
228 text: args.label
229 });
230
231 if (null !== args.label) {
232 field.appendChild(label);
233 }
234 }
235
236 return radio;
237 }
238
239 /* harmony default export */ const builders_radio = (buildRadio);
240 ;// CONCATENATED MODULE: ./src/scripts/builders/select.js
241
242 /**
243 * Helper to create select field.
244 *
245 * @param {Object} args List of control options.
246 * @param {HTMLElement} parent Parent HTML element to append this field.
247 */
248
249 function buildSelect(args, parent) {
250 const field = builders_element('div', {
251 classes: args.classes || [],
252 append: parent
253 });
254
255 if (args.hasOwnProperty('label')) {
256 const label = builders_element('h4', {
257 text: args.label
258 });
259
260 if (null !== args.label) {
261 field.appendChild(label);
262 }
263 }
264
265 const select = builders_element('select', {
266 dataset: args.dataset || {},
267 append: field
268 }); // Set attributes
269
270 if (args.hasOwnProperty('attributes')) {
271 for (const key in args.attributes) {
272 const value = args.attributes[key];
273
274 if (undefined === value) {
275 continue;
276 }
277
278 select.setAttribute(key, value);
279 }
280 }
281
282 const options = args.options || {};
283
284 for (const key in options) {
285 const option = builders_element('option', {
286 text: options[key],
287 attributes: {
288 value: key
289 },
290 append: select
291 });
292
293 if (args.hasOwnProperty('selected')) {
294 const selected = args.selected;
295
296 if (selected && selected === option.value) {
297 option.setAttribute('selected', 'selected');
298 }
299 }
300 }
301
302 return select;
303 }
304
305 /* harmony default export */ const builders_select = (buildSelect);
306 ;// CONCATENATED MODULE: ./src/scripts/builders/textarea.js
307
308 /**
309 * Helper to create input field.
310 *
311 * @param {Object} args List of control options.
312 * @param {HTMLElement} parent Parent HTML element to append this field.
313 */
314
315 function buildTextarea(args, parent) {
316 const field = builders_element('div', {
317 classes: args.classes || [],
318 append: parent
319 });
320
321 if (args.hasOwnProperty('label')) {
322 const label = builders_element('h4', {
323 text: args.label
324 });
325
326 if (null !== args.label) {
327 field.appendChild(label);
328 }
329 }
330
331 const textarea = builders_element('textarea', {
332 dataset: args.dataset || {},
333 append: field
334 }); // Set attributes
335
336 if (args.hasOwnProperty('attributes')) {
337 for (const key in args.attributes) {
338 const value = args.attributes[key];
339
340 if (undefined === value) {
341 continue;
342 }
343
344 textarea.setAttribute(key, value);
345 }
346 } // Set content
347
348
349 if (args.hasOwnProperty('content')) {
350 const content = args.content;
351
352 if (undefined !== content) {
353 textarea.innerHTML = content;
354 }
355 }
356
357 return textarea;
358 }
359
360 /* harmony default export */ const builders_textarea = (buildTextarea);
361 ;// CONCATENATED MODULE: ./src/scripts/builders/control.js
362
363
364
365
366
367
368 /**
369 * Helper to create control.
370 *
371 * @param {Object} args List of control options.
372 */
373
374 function buildControl(args) {
375 const control = builders_element('div', {
376 classes: args.classes || []
377 });
378
379 if (args.hasOwnProperty('append')) {
380 args.append.appendChild(control);
381 }
382
383 if (args.hasOwnProperty('prepend')) {
384 args.prepend.insertBefore(control, args.prepend.firstChild);
385 }
386
387 if (args.hasOwnProperty('label')) {
388 builders_element('h3', {
389 text: args.label,
390 append: control
391 });
392 }
393
394 if (args.hasOwnProperty('description')) {
395 builders_element('p', {
396 text: args.description,
397 append: control
398 });
399 }
400
401 if (args.hasOwnProperty('fields')) {
402 args.fields.forEach(field => {
403 switch (field.group) {
404 case 'input':
405 input(field, control);
406 break;
407
408 case 'textarea':
409 builders_textarea(field, control);
410 break;
411
412 case 'radio':
413 builders_radio(field, control);
414 break;
415
416 case 'select':
417 builders_select(field, control);
418 break;
419
420 case 'checkbox':
421 builders_checkbox(field, control);
422 break;
423 }
424 });
425 }
426
427 if (args.hasOwnProperty('help')) {
428 builders_element('small', {
429 text: args.help,
430 append: control
431 });
432 }
433
434 return control;
435 }
436
437 /* harmony default export */ const control = (buildControl);
438 ;// CONCATENATED MODULE: ./src/scripts/builders/layer.js
439
440 /**
441 * Helper to create layer.
442 *
443 * @param {Object} args List of layer options.
444 */
445
446 function buildLayer(args) {
447 const layer = builders_element('div', {
448 classes: args.classes || []
449 });
450
451 if (args.hasOwnProperty('append')) {
452 args.append.appendChild(layer);
453 }
454
455 if (args.hasOwnProperty('prepend')) {
456 args.prepend.insertBefore(layer, args.prepend.firstChild);
457 }
458
459 if (args.hasOwnProperty('label')) {
460 builders_element('h2', {
461 text: args.label,
462 append: layer
463 });
464 }
465
466 if (args.hasOwnProperty('description')) {
467 builders_element('h5', {
468 text: args.description,
469 append: layer
470 });
471 }
472
473 return layer;
474 }
475
476 /* harmony default export */ const builders_layer = (buildLayer);
477 ;// CONCATENATED MODULE: ./src/scripts/helpers/param.js
478 /**
479 * Get current location search parameter.
480 *
481 * @param {string} key URL parameter key.
482 */
483 function getSearchParam(key) {
484 const params = new URL(document.location.href);
485 return params.searchParams.get(key);
486 }
487
488 /* harmony default export */ const param = (getSearchParam);
489 ;// CONCATENATED MODULE: ./src/scripts/helpers/attachment.js
490 /**
491 * Upload media frame.
492 *
493 * @param {string} header Frame header text.
494 * @param {Function} callback Callback function.
495 */
496 function uploadMedia(header, callback) {
497 const frame = wp.media({
498 title: header,
499 multiple: false
500 });
501 frame.on('select', () => {
502 const selection = frame.state().get('selection').first().toJSON();
503
504 if (selection.id) {
505 callback(selection.id);
506 }
507 });
508 frame.open();
509 }
510
511 /* harmony default export */ const attachment = (uploadMedia);
512 ;// CONCATENATED MODULE: ./src/scripts/helpers/defaults.js
513 /**
514 * Append empty default properties to object if not exist.
515 *
516 * @param {Object} object Source object.
517 * @param {Array} defaults Required defaults properties.
518 */
519 function intersectDefaults(object, defaults) {
520 defaults.forEach(item => {
521 if (undefined === object[item]) {
522 object[item] = {};
523 }
524 });
525 return object;
526 }
527
528 /* harmony default export */ const defaults = (intersectDefaults);
529 ;// CONCATENATED MODULE: ./src/scripts/helpers/index.js
530
531
532
533 const Helper = {
534 param: param,
535 attachment: attachment,
536 defaults: defaults
537 };
538 /* harmony default export */ const helpers = (Helper);
539 ;// CONCATENATED MODULE: ./src/scripts/builders/media.js
540
541
542
543 /**
544 * Helper to create media block.
545 *
546 * @param {Object} args List of media options.
547 */
548
549 function buildMedia(args) {
550 const media = control({
551 classes: args.classes || []
552 });
553
554 if (args.hasOwnProperty('append')) {
555 args.append.appendChild(media);
556 }
557
558 if (args.hasOwnProperty('prepend')) {
559 args.prepend.insertBefore(media, args.prepend.firstChild);
560 } // Labels are required.
561
562
563 args.labels = args.labels || {};
564 const attachment = builders_element('input', {
565 attributes: {
566 type: 'hidden',
567 name: args.name
568 },
569 append: media
570 });
571 const upload = builders_element('button', {
572 classes: ['button'],
573 text: args.labels.button,
574 attributes: {
575 type: 'button'
576 },
577 append: media
578 });
579 const details = builders_element('a', {
580 classes: ['hidden'],
581 text: args.labels.details,
582 attributes: {
583 target: '_blank'
584 }
585 });
586
587 if (args.hasOwnProperty('link')) {
588 media.appendChild(details);
589 } // Helper function to update attachment value.
590
591
592 const setAttachment = id => {
593 attachment.setAttribute('value', id);
594 attachment.dispatchEvent(new Event('change', {
595 bubbles: true
596 }));
597 let link = null;
598
599 if (args.hasOwnProperty('link')) {
600 link = new URL(args.link);
601 link.searchParams.set('item', id);
602 details.setAttribute('href', link.href);
603 }
604
605 if (args.remove) {
606 upload.textContent = args.labels.remove;
607 }
608
609 details.classList.remove('hidden');
610 }; // Helper function to remove attachment value.
611
612
613 const removeAttachment = () => {
614 attachment.setAttribute('value', '');
615 attachment.dispatchEvent(new Event('change', {
616 bubbles: true
617 })); // Set default button title.
618
619 upload.textContent = args.labels.button;
620 details.classList.add('hidden');
621 }; // Update fields if this layer has attachment.
622
623
624 if (args.value) {
625 setAttachment(args.value);
626 }
627
628 upload.addEventListener('click', () => {
629 if (args.remove && attachment.value) {
630 return removeAttachment();
631 }
632
633 helpers.attachment(args.labels.heading, id => {
634 setAttachment(id);
635 });
636 });
637 return media;
638 }
639
640 /* harmony default export */ const media = (buildMedia);
641 ;// CONCATENATED MODULE: ./src/scripts/builders/index.js
642
643
644
645
646
647
648
649
650
651 const Build = {
652 element: builders_element,
653 control: control,
654 layer: builders_layer,
655 checkbox: builders_checkbox,
656 media: media,
657 input: input,
658 textarea: builders_textarea,
659 radio: builders_radio,
660 select: builders_select
661 };
662 /* harmony default export */ const builders = (Build);
663 ;// CONCATENATED MODULE: ./src/scripts/sections/catalog.js
664
665 const {
666 __
667 } = wp.i18n; // Store global scriot object for settings page.
668
669 let params = null;
670 /**
671 * Create template card in catalog.
672 *
673 * @param {HTMLElement} catalog Catalog HTML element.
674 * @param {number} index Current card index.
675 * @param {Object} option List of template options.
676 */
677
678 function createCard(catalog, index, option) {
679 const card = builders.element('div', {
680 classes: ['sharing-image-catalog-card'],
681 append: catalog
682 });
683 const preview = builders.element('figure', {
684 classes: ['sharing-image-catalog-preview'],
685 append: card
686 });
687
688 if (option.preview) {
689 builders.element('img', {
690 attributes: {
691 src: option.preview,
692 alt: ''
693 },
694 append: preview
695 });
696 }
697
698 const footer = builders.element('footer', {
699 classes: ['sharing-image-catalog-footer'],
700 append: card
701 });
702 builders.element('h2', {
703 text: option.title || __('Untitled', 'sharing-image'),
704 append: footer
705 });
706 const link = new URL(document.location.href);
707 link.searchParams.set('template', index);
708 builders.element('a', {
709 classes: ['button'],
710 text: __('Edit template', 'sharing-image'),
711 attributes: {
712 href: link.href
713 },
714 append: footer
715 });
716 }
717 /**
718 * Create new template button in catalog.
719 *
720 * @param {HTMLElement} catalog Catalog HTML element.
721 * @param {number} index New card index.
722 */
723
724
725 function createNewButton(catalog, index) {
726 const link = new URL(document.location.href);
727 link.searchParams.set('template', index);
728 const button = builders.element('a', {
729 classes: ['sharing-image-catalog-new'],
730 attributes: {
731 href: link.href
732 },
733 append: catalog
734 });
735 const title = builders.element('h2', {
736 append: button
737 });
738 builders.element('strong', {
739 text: __('Add new template', 'sharing-image'),
740 append: title
741 }); // Restrict new template creation for not Premium users.
742
743 if (params.templates.length === 0) {
744 return;
745 }
746
747 const license = params.license || {};
748
749 if (license.premium || license.develop) {
750 return;
751 }
752
753 builders.element('span', {
754 text: __('(Availible for Premium only)', 'sharing-image'),
755 append: title
756 });
757
758 if (params.links.premium) {
759 button.href = params.links.premium;
760 }
761 }
762 /**
763 * Create templates catalog from options.
764 *
765 * @param {HTMLElement} content Settings content element.
766 * @param {Object} settings Global settings field.
767 */
768
769
770 function createCatalog(content, settings) {
771 params = settings;
772 const catalog = builders.element('div', {
773 classes: ['sharing-image-catalog'],
774 append: content
775 });
776 let index = 1;
777 settings.templates.forEach(template => {
778 createCard(catalog, index++, template);
779 });
780 createNewButton(catalog, index);
781 }
782
783 /* harmony default export */ const catalog = (createCatalog);
784 ;// CONCATENATED MODULE: ./src/scripts/sections/editor.js
785 /**
786 * Editor settings.
787 */
788
789 /* global ajaxurl:true */
790
791 const {
792 __: editor_
793 } = wp.i18n; // Store global scriot object for settings page.
794
795 let editor_params = null; // Preview element.
796
797 let preview = null; // Root editor element.
798
799 let editor = null;
800 /**
801 * Show template warning message.
802 *
803 * @param {string} message Warning message.
804 */
805
806 function showTemplateError(message) {
807 const viewport = preview.parentNode; // Try to find warning element.
808
809 const warning = viewport.querySelector('.sharing-image-editor-warning');
810
811 if (null === warning) {
812 return;
813 }
814
815 warning.classList.add('warning-visible');
816 warning.textContent = message || editor_('Unknown generation error', 'sharing-image');
817 }
818 /**
819 * Remove warning message block.
820 */
821
822
823 function hideTemplateError() {
824 const viewport = preview.parentNode; // Try to find warning element.
825
826 const warning = viewport.querySelector('.sharing-image-editor-warning');
827
828 if (null === warning) {
829 return;
830 }
831
832 warning.classList.remove('warning-visible');
833 }
834 /**
835 * Geneate template using editor data.
836 */
837
838
839 function generateTemplate() {
840 preview.classList.add('preview-loader');
841 const request = new XMLHttpRequest();
842 request.open('POST', ajaxurl);
843 request.responseType = 'blob'; // Create data bundle using form data.
844
845 const bundle = new window.FormData(editor);
846 bundle.set('action', 'sharing_image_show');
847 hideTemplateError(); // Set blob for success response.
848
849 request.addEventListener('readystatechange', () => {
850 if (request.readyState === 2) {
851 request.responseType = 'json';
852
853 if (request.status === 200) {
854 request.responseType = 'blob';
855 }
856 }
857 });
858 request.addEventListener('load', () => {
859 const response = request.response || {}; // Hide preview loader on request complete.
860
861 preview.classList.remove('preview-blank', 'preview-loader');
862
863 if (200 !== request.status) {
864 return showTemplateError(response.data);
865 }
866
867 let image = preview.querySelector('img');
868
869 if (null === image) {
870 image = builders.element('img', {
871 append: preview
872 });
873 } // Set new blob image source.
874
875
876 image.src = window.URL.createObjectURL(response);
877 });
878 request.addEventListener('error', () => {
879 showTemplateError(); // Hide preview loader on request complete.
880
881 preview.classList.remove('preview-blank', 'preview-loader');
882 });
883 request.send(bundle);
884 }
885 /**
886 * Save template while editor submiting.
887 */
888
889
890 function saveTemplate() {
891 const request = new XMLHttpRequest();
892 request.open('POST', ajaxurl);
893 request.responseType = 'json';
894 preview.classList.add('preview-loader'); // Create data bundle using editor data.
895
896 const bundle = new window.FormData(editor);
897 bundle.set('action', 'sharing_image_save');
898 request.addEventListener('load', () => {
899 const response = request.response || {};
900
901 if (!response.data) {
902 return showTemplateError();
903 }
904
905 if (!response.success) {
906 // Hide preview loader on request complete.
907 preview.classList.remove('preview-loader');
908 return showTemplateError(response.data);
909 }
910
911 const input = preview.querySelector('input');
912
913 if (null !== input) {
914 input.value = response.data;
915 }
916
917 editor.submit();
918 });
919 request.addEventListener('error', () => {
920 // Hide preview loader on request complete.
921 preview.classList.remove('preview-loader');
922 showTemplateError();
923 });
924 request.send(bundle);
925 }
926 /**
927 * Update form fields name attributes for layers
928 *
929 * @param {HTMLElement} designer Layouts designer element.
930 */
931
932
933 function reorderLayers(designer) {
934 const layers = designer.children;
935
936 for (let index = 0; index < layers.length; index++) {
937 const fields = layers[index].querySelectorAll('[name]');
938 fields.forEach(field => {
939 let name = field.getAttribute('name'); // Try to find layer index.
940
941 const match = name.match(/(.+?\[layers\])\[(\d+)\](\[.+?\])$/);
942
943 if (null !== match) {
944 name = match[1] + `[${index}]` + match[3];
945 }
946
947 field.name = name;
948 });
949 }
950 }
951 /**
952 * Update template background settings with custom logic.
953 *
954 * @param {HTMLElement} fieldset Fieldset HTML element.
955 * @param {Object} data Current template data.
956 */
957
958
959 function createPermanentAttachment(fieldset, data) {
960 data.background = data.background || null; // Create background settings control.
961
962 const control = builders.control({
963 classes: ['sharing-image-editor-control', 'control-reduced'],
964 label: editor_('Template background settings', 'sharing-image'),
965 fields: [{
966 group: 'radio',
967 classes: ['sharing-image-editor-control-radio'],
968 attributes: {
969 name: editor_params.name + '[background]',
970 value: 'blank'
971 },
972 label: editor_('Do not use background image', 'sharing-image'),
973 checked: data.background
974 }, {
975 group: 'radio',
976 classes: ['sharing-image-editor-control-radio'],
977 attributes: {
978 name: editor_params.name + '[background]',
979 value: 'dynamic'
980 },
981 label: editor_('Select for each post separately', 'sharing-image'),
982 checked: data.background
983 }, {
984 group: 'radio',
985 classes: ['sharing-image-editor-control-radio'],
986 attributes: {
987 name: editor_params.name + '[background]',
988 value: 'permanent'
989 },
990 label: editor_('Upload permanent background', 'sharing-image'),
991 checked: data.background
992 }],
993 append: fieldset
994 });
995 const media = builders.media({
996 name: editor_params.name + '[attachment]',
997 classes: ['sharing-image-editor-control', 'control-media'],
998 value: data.attachment,
999 link: editor_params.links.uploads,
1000 labels: {
1001 button: editor_('Upload image', 'sharing-image'),
1002 heading: editor_('Select background image', 'sharing-image'),
1003 details: editor_('Attachment details', 'sharing-image')
1004 },
1005 append: fieldset
1006 });
1007 const upload = media.querySelector('button');
1008 upload.disabled = true;
1009 builders.control({
1010 classes: ['sharing-image-editor-control'],
1011 label: editor_('Fill color', 'sharing-image'),
1012 fields: [{
1013 group: 'input',
1014 classes: ['sharing-image-editor-control-color'],
1015 attributes: {
1016 name: editor_params.name + '[fill]',
1017 type: 'color',
1018 value: data.fill
1019 }
1020 }],
1021 append: fieldset
1022 });
1023 control.querySelectorAll('input').forEach(radio => {
1024 if ('radio' !== radio.type) {
1025 return;
1026 } // Show upload button for checked permanent radio.
1027
1028
1029 if (radio.checked && 'permanent' === radio.value) {
1030 upload.disabled = false;
1031 }
1032
1033 radio.addEventListener('change', () => {
1034 upload.disabled = true;
1035
1036 if ('permanent' === radio.value) {
1037 upload.disabled = false;
1038 }
1039 });
1040 });
1041 }
1042 /**
1043 * Text layer dynamic/static fields manager.
1044 *
1045 * @param {HTMLElement} layer Current layer element.
1046 * @param {string} name Fields name attribute prefix.
1047 * @param {Object} data Layer data object.
1048 */
1049
1050
1051 function createDynamicFields(layer, name, data) {
1052 const control = builders.control({
1053 classes: ['sharing-image-editor-control'],
1054 append: layer
1055 });
1056 const checkbox = builders.checkbox({
1057 classes: ['sharing-image-editor-control-checkbox'],
1058 attributes: {
1059 name: name + '[dynamic]',
1060 value: 'dynamic'
1061 },
1062 label: editor_('Dynamic field. Filled in the post editing screen.', 'sharing-image'),
1063 checked: data.dynamic
1064 }, control);
1065 const fields = [];
1066 fields[fields.length] = builders.control({
1067 classes: ['sharing-image-editor-control', 'control-extend', 'control-hidden'],
1068 help: editor_('Displayed only in the metabox.', 'sharing-image'),
1069 fields: [{
1070 group: 'input',
1071 classes: ['sharing-image-editor-control-input'],
1072 attributes: {
1073 name: name + '[title]',
1074 value: data.title
1075 },
1076 label: editor_('Field name', 'sharing-image')
1077 }],
1078 append: layer
1079 });
1080 fields[fields.length] = builders.control({
1081 classes: ['sharing-image-editor-control', 'control-extend', 'control-hidden'],
1082 help: editor_('This field is used for example only, to see how the editor will look.', 'sharing-image'),
1083 fields: [{
1084 group: 'textarea',
1085 classes: ['sharing-image-editor-control-textarea'],
1086 content: data.sample || 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1087 attributes: {
1088 name: name + '[sample]',
1089 rows: 2
1090 },
1091 label: editor_('Text sample', 'sharing-image')
1092 }],
1093 append: layer
1094 });
1095 fields[fields.length] = builders.control({
1096 classes: ['sharing-image-editor-control', 'control-hidden'],
1097 label: editor_('Preset text field', 'sharing-image'),
1098 fields: [{
1099 group: 'radio',
1100 classes: ['sharing-image-editor-control-radio'],
1101 attributes: {
1102 name: name + '[preset]',
1103 value: 'none'
1104 },
1105 dataset: {
1106 persistent: true
1107 },
1108 label: editor_('Fill in manually', 'sharing-image'),
1109 checked: data.preset || 'none'
1110 }, {
1111 group: 'radio',
1112 classes: ['sharing-image-editor-control-radio'],
1113 attributes: {
1114 name: name + '[preset]',
1115 value: 'title'
1116 },
1117 dataset: {
1118 persistent: true
1119 },
1120 label: editor_('Take from post title', 'sharing-image'),
1121 checked: data.preset || 'none'
1122 }, {
1123 group: 'radio',
1124 classes: ['sharing-image-editor-control-radio'],
1125 attributes: {
1126 name: name + '[preset]',
1127 value: 'excerpt'
1128 },
1129 dataset: {
1130 persistent: true
1131 },
1132 label: editor_('Use post excerpt text', 'sharing-image'),
1133 checked: data.preset || 'none'
1134 }],
1135 append: layer
1136 });
1137 fields[fields.length] = builders.control({
1138 classes: ['sharing-image-editor-control', 'control-extend'],
1139 help: editor_('You can use non-breaking spaces to manage your string position.', 'sharing-image'),
1140 fields: [{
1141 group: 'textarea',
1142 classes: ['sharing-image-editor-control-textarea'],
1143 content: data.content,
1144 attributes: {
1145 name: name + '[content]',
1146 rows: 2
1147 },
1148 label: editor_('Content', 'sharing-image')
1149 }],
1150 append: layer
1151 }); // Helper function to toggle contols visibility.
1152
1153 const toggleClasses = () => {
1154 fields.forEach(field => {
1155 field.classList.toggle('control-hidden');
1156 });
1157 };
1158
1159 if (checkbox.checked) {
1160 toggleClasses();
1161 }
1162
1163 checkbox.addEventListener('change', () => {
1164 toggleClasses();
1165 });
1166 }
1167 /**
1168 * Text layer more options fields manager.
1169 *
1170 * @param {HTMLElement} layer Current layer element.
1171 * @param {string} name Fields name attribute prefix.
1172 * @param {Object} data Layer data object.
1173 */
1174
1175
1176 function createMoreFields(layer, name, data) {
1177 const fields = [];
1178 fields[fields.length] = createFontField(layer, name, data);
1179 fields[fields.length] = builders.control({
1180 classes: ['sharing-image-editor-control', 'control-hidden'],
1181 fields: [{
1182 group: 'input',
1183 classes: ['sharing-image-editor-control-color'],
1184 attributes: {
1185 type: 'color',
1186 name: name + '[color]',
1187 value: data.color || '#ffffff'
1188 },
1189 label: editor_('Text color', 'sharing-image')
1190 }],
1191 append: layer
1192 });
1193 fields[fields.length] = builders.control({
1194 classes: ['sharing-image-editor-control', 'control-series', 'control-hidden'],
1195 fields: [{
1196 group: 'select',
1197 classes: ['sharing-image-editor-control-select'],
1198 options: {
1199 left: editor_('Left', 'sharing-image'),
1200 center: editor_('Center', 'sharing-image'),
1201 right: editor_('Right', 'sharing-image')
1202 },
1203 attributes: {
1204 name: name + '[horizontal]'
1205 },
1206 label: editor_('Horizontal alignment', 'sharing-image'),
1207 selected: data.horizontal
1208 }, {
1209 group: 'select',
1210 classes: ['sharing-image-editor-control-select'],
1211 options: {
1212 top: editor_('Top', 'sharing-image'),
1213 center: editor_('Center', 'sharing-image'),
1214 bottom: editor_('Bottom', 'sharing-image')
1215 },
1216 attributes: {
1217 name: name + '[vertical]'
1218 },
1219 label: editor_('Vertical alignment', 'sharing-image'),
1220 selected: data.vertical
1221 }],
1222 append: layer
1223 });
1224 const control = builders.control({
1225 classes: ['sharing-image-editor-control'],
1226 append: layer
1227 });
1228 const button = builders.element('button', {
1229 classes: ['sharing-image-editor-more'],
1230 text: editor_('More options', 'sharing-image'),
1231 attributes: {
1232 type: 'button'
1233 },
1234 append: control
1235 });
1236 button.addEventListener('click', () => {
1237 fields.forEach(field => {
1238 field.classList.remove('control-hidden');
1239 }); // Remove button on expand.
1240
1241 layer.removeChild(control);
1242 }); // Open more fields for existing layers.
1243
1244 if (Object.keys(data).length > 0) {
1245 button.click();
1246 }
1247 }
1248 /**
1249 * Create font field in text layer.
1250 *
1251 * @param {HTMLElement} layer Current layer element.
1252 * @param {string} name Fields name attribute prefix.
1253 * @param {Object} data Layer data object.
1254 */
1255
1256
1257 function createFontField(layer, name, data) {
1258 const control = builders.control({
1259 classes: ['sharing-image-editor-control', 'control-upload', 'control-hidden'],
1260 append: layer
1261 });
1262 const select = builders.select({
1263 classes: ['sharing-image-editor-control-select'],
1264 options: editor_params.fonts,
1265 attributes: {
1266 name: name + '[fontname]'
1267 },
1268 label: editor_('Font family', 'sharing-image'),
1269 selected: data.fontname
1270 }, control);
1271 const media = builders.media({
1272 name: name + '[fontfile]',
1273 classes: ['sharing-image-editor-control-media'],
1274 value: data.fontfile,
1275 link: editor_params.links.uploads,
1276 labels: {
1277 button: editor_('Upload custom font', 'sharing-image'),
1278 heading: editor_('Upload custom font', 'sharing-image'),
1279 details: editor_('Font attachment', 'sharing-image'),
1280 remove: editor_('Remove font', 'sharing-image')
1281 },
1282 remove: true,
1283 append: control
1284 });
1285 builders.element('small', {
1286 text: editor_('Custom font can only be in .ttf format.'),
1287 append: control
1288 });
1289
1290 if (data.fontfile) {
1291 select.disabled = true;
1292 } // Find media attachment input.
1293
1294
1295 const input = media.querySelector('input');
1296 input.addEventListener('change', () => {
1297 select.disabled = false;
1298
1299 if (input.value) {
1300 select.disabled = true;
1301 }
1302 });
1303 return control;
1304 }
1305 /**
1306 * Rectangle layer outline option.
1307 *
1308 * @param {HTMLElement} layer Current layer element.
1309 * @param {string} name Fields name attribute prefix.
1310 * @param {Object} data Layer data object.
1311 */
1312
1313
1314 function createRectangleOutline(layer, name, data) {
1315 const control = builders.control({
1316 classes: ['sharing-image-editor-control'],
1317 append: layer
1318 });
1319 const checkbox = builders.checkbox({
1320 classes: ['sharing-image-editor-control-checkbox'],
1321 attributes: {
1322 name: name + '[outline]',
1323 value: 'outline'
1324 },
1325 label: editor_('Outline rectangle.', 'sharing-image'),
1326 checked: data.outline
1327 }, control);
1328 const range = builders.control({
1329 classes: ['sharing-image-editor-control', 'control-hidden'],
1330 fields: [{
1331 group: 'input',
1332 classes: ['sharing-image-editor-control-range'],
1333 attributes: {
1334 type: 'range',
1335 name: name + '[thickness]',
1336 min: 0,
1337 max: 50,
1338 step: 1,
1339 value: data.thickness || '0'
1340 },
1341 label: editor_('Border width', 'sharing-image')
1342 }],
1343 append: layer
1344 });
1345
1346 if (data.outline) {
1347 range.classList.remove('control-hidden');
1348 }
1349
1350 checkbox.addEventListener('change', () => {
1351 range.classList.add('control-hidden');
1352
1353 if (checkbox.checked) {
1354 range.classList.remove('control-hidden');
1355 }
1356 });
1357 }
1358 /**
1359 * Create catalog button in footer.
1360 *
1361 * @param {HTMLElement} footer Footer HTML element.
1362 */
1363
1364
1365 function createCatalogButton(footer) {
1366 const link = new URL(document.location.href);
1367 link.searchParams.delete('template');
1368 builders.element('a', {
1369 classes: ['button'],
1370 text: editor_('← Back to Catalog', 'sharing-image'),
1371 attributes: {
1372 href: link.href
1373 },
1374 append: footer
1375 });
1376 }
1377 /**
1378 * Create template deletion button in footer.
1379 *
1380 * @param {HTMLElement} footer Footer HTML element.
1381 */
1382
1383
1384 function createDeleteButton(footer) {
1385 const href = new URL(document.location.href); // Get template index from current link.
1386
1387 const index = href.searchParams.get('template'); // Set template index to delete link.
1388
1389 const link = new URL(editor.getAttribute('action'));
1390 link.searchParams.set('action', 'sharing_image_delete');
1391 link.searchParams.set('template', index);
1392 link.searchParams.set('nonce', editor_params.nonce);
1393 builders.element('a', {
1394 classes: ['sharing-image-editor-delete'],
1395 text: editor_('Delete template', 'sharing-image'),
1396 attributes: {
1397 href: link.href
1398 },
1399 append: footer
1400 });
1401 }
1402 /**
1403 * Create preview element.
1404 *
1405 * @param {HTMLElement} viewport Monitor viewport element.
1406 * @param {Object} data Template data object.
1407 */
1408
1409
1410 function createPreview(viewport, data) {
1411 preview = builders.element('div', {
1412 classes: ['sharing-image-editor-preview', 'preview-blank'],
1413 append: viewport
1414 });
1415
1416 if (data.preview) {
1417 builders.element('img', {
1418 attributes: {
1419 src: data.preview,
1420 alt: ''
1421 },
1422 append: preview
1423 });
1424 preview.classList.remove('preview-blank');
1425 }
1426
1427 builders.element('span', {
1428 classes: ['sharing-image-editor-loader'],
1429 append: preview
1430 });
1431 builders.element('input', {
1432 attributes: {
1433 type: 'hidden',
1434 name: editor_params.name + '[preview]',
1435 value: data.preview
1436 },
1437 append: preview
1438 });
1439 return preview;
1440 }
1441 /**
1442 * Create button inside layer box to change order.
1443 *
1444 * @param {HTMLElement} designer Layers designer HTML element.
1445 * @param {HTMLElement} layer Current layer HTML emelemt.
1446 */
1447
1448
1449 function createOrderLayersButton(designer, layer) {
1450 const button = builders.element('button', {
1451 classes: ['sharing-image-editor-order'],
1452 attributes: {
1453 type: 'button',
1454 title: editor_('Raise higher', 'sharing-image')
1455 },
1456 append: layer
1457 });
1458 button.addEventListener('click', () => {
1459 if (layer.previousSibling) {
1460 designer.insertBefore(layer, layer.previousSibling);
1461 } // Update fields name attributes.
1462
1463
1464 reorderLayers(designer);
1465
1466 if (editor.classList.contains('editor-suspend')) {
1467 return;
1468 }
1469
1470 generateTemplate();
1471 });
1472 }
1473 /**
1474 * Create button to delete layer.
1475 *
1476 * @param {HTMLElement} designer Layers designer HTML element.
1477 * @param {HTMLElement} layer Current layer HTML emelemt.
1478 */
1479
1480
1481 function createDeleteLayerButton(designer, layer) {
1482 const control = builders.control({
1483 classes: ['sharing-image-editor-control', 'control-footer'],
1484 append: layer
1485 });
1486 const button = builders.element('button', {
1487 classes: ['sharing-image-editor-delete'],
1488 text: editor_('Delete layer', 'sharing-image'),
1489 attributes: {
1490 type: 'button'
1491 },
1492 append: control
1493 });
1494 button.addEventListener('click', () => {
1495 designer.removeChild(layer); // Update fields name attributes.
1496
1497 reorderLayers(designer);
1498
1499 if (editor.classList.contains('editor-suspend')) {
1500 return;
1501 }
1502
1503 generateTemplate();
1504 });
1505 }
1506 /**
1507 * Create image layer.
1508 *
1509 * @param {number} index Current layer index.
1510 * @param {Object} data Current template layer data.
1511 */
1512
1513
1514 function createLayerImage(index, data) {
1515 const description = [];
1516 description.push(editor_('Use jpg, gif or png image formats.', 'sharing-image'));
1517 description.push(editor_('Leave width and height fields blank to use the original image size.', 'sharing-image'));
1518 description.push(editor_('Sizes are calculated proportionally if not filled.', 'sharing-image'));
1519 const layer = builders.layer({
1520 classes: ['sharing-image-editor-layer', 'layer-image'],
1521 label: editor_('Image', 'sharing-image'),
1522 description: description.join(' ')
1523 }); // Form fields name for this layer.
1524
1525 const name = editor_params.name + `[layers][${index}]`;
1526 builders.element('input', {
1527 attributes: {
1528 type: 'hidden',
1529 name: name + '[type]',
1530 value: 'image'
1531 },
1532 append: layer
1533 });
1534 builders.media({
1535 name: name + '[attachment]',
1536 classes: ['sharing-image-editor-control', 'control-media'],
1537 value: data.attachment,
1538 link: editor_params.links.uploads,
1539 labels: {
1540 button: editor_('Upload image', 'sharing-image'),
1541 heading: editor_('Select layer image', 'sharing-image'),
1542 details: editor_('Attachment details', 'sharing-image')
1543 },
1544 append: layer
1545 });
1546 builders.control({
1547 classes: ['sharing-image-editor-control', 'control-sizes'],
1548 fields: [{
1549 group: 'input',
1550 classes: ['sharing-image-editor-control-input'],
1551 attributes: {
1552 name: name + '[x]',
1553 value: data.x,
1554 placeholder: '10'
1555 },
1556 label: editor_('X', 'sharing-image')
1557 }, {
1558 group: 'input',
1559 classes: ['sharing-image-editor-control-input'],
1560 attributes: {
1561 name: name + '[y]',
1562 value: data.y,
1563 placeholder: '10'
1564 },
1565 label: editor_('Y', 'sharing-image')
1566 }, {
1567 group: 'input',
1568 classes: ['sharing-image-editor-control-input'],
1569 attributes: {
1570 name: name + '[width]',
1571 value: data.width
1572 },
1573 label: editor_('Width', 'sharing-image')
1574 }, {
1575 group: 'input',
1576 classes: ['sharing-image-editor-control-input'],
1577 attributes: {
1578 name: name + '[height]',
1579 value: data.height
1580 },
1581 label: editor_('Height', 'sharing-image')
1582 }],
1583 append: layer
1584 });
1585 return layer;
1586 }
1587 /**
1588 * Create text layer.
1589 *
1590 * @param {number} index Current layer index.
1591 * @param {Object} data Current template data.
1592 */
1593
1594
1595 function createLayerText(index, data) {
1596 const description = [];
1597 description.push(editor_('Write a text to the current image.', 'sharing-image'));
1598 description.push(editor_('If the font does not fit within your limits, its size will decrease.', 'sharing-image'));
1599 description.push(editor_('Avoid using large font sizes for long text – this affects performance.', 'sharing-image'));
1600 const layer = builders.layer({
1601 classes: ['sharing-image-editor-layer', 'layer-text'],
1602 label: editor_('Text', 'sharing-image'),
1603 description: description.join(' ')
1604 }); // Form fields name for this layer.
1605
1606 const name = editor_params.name + `[layers][${index}]`;
1607 builders.element('input', {
1608 attributes: {
1609 type: 'hidden',
1610 name: name + '[type]',
1611 value: 'text'
1612 },
1613 append: layer
1614 });
1615 builders.control({
1616 classes: ['sharing-image-editor-control', 'control-sizes'],
1617 fields: [{
1618 group: 'input',
1619 classes: ['sharing-image-editor-control-input'],
1620 attributes: {
1621 type: 'text',
1622 name: name + '[x]',
1623 value: data.x,
1624 placeholder: '10'
1625 },
1626 label: editor_('X', 'sharing-image')
1627 }, {
1628 group: 'input',
1629 classes: ['sharing-image-editor-control-input'],
1630 attributes: {
1631 type: 'text',
1632 name: name + '[y]',
1633 value: data.y,
1634 placeholder: '10'
1635 },
1636 label: editor_('Y', 'sharing-image')
1637 }, {
1638 group: 'input',
1639 classes: ['sharing-image-editor-control-input'],
1640 attributes: {
1641 type: 'text',
1642 name: name + '[width]',
1643 value: data.width,
1644 placeholder: '1000'
1645 },
1646 label: editor_('Width', 'sharing-image')
1647 }, {
1648 group: 'input',
1649 classes: ['sharing-image-editor-control-input'],
1650 attributes: {
1651 type: 'text',
1652 name: name + '[height]',
1653 value: data.height
1654 },
1655 label: editor_('Height', 'sharing-image')
1656 }],
1657 append: layer
1658 }); // Create static/dynamic text fields.
1659
1660 createDynamicFields(layer, name, data); // Create more options.
1661
1662 createMoreFields(layer, name, data);
1663 builders.control({
1664 classes: ['sharing-image-editor-control', 'control-series'],
1665 fields: [{
1666 group: 'input',
1667 classes: ['sharing-image-editor-control-range'],
1668 attributes: {
1669 type: 'range',
1670 name: name + '[fontsize]',
1671 min: 10,
1672 max: 200,
1673 step: 1,
1674 value: data.fontsize || '48'
1675 },
1676 label: editor_('Font size', 'sharing-image')
1677 }, {
1678 group: 'input',
1679 classes: ['sharing-image-editor-control-range'],
1680 attributes: {
1681 type: 'range',
1682 name: name + '[lineheight]',
1683 min: 0,
1684 max: 4,
1685 step: 0.125,
1686 value: data.lineheight || '1.5'
1687 },
1688 label: editor_('Line height', 'sharing-image')
1689 }],
1690 append: layer
1691 });
1692 return layer;
1693 }
1694 /**
1695 * Create filter layer.
1696 *
1697 * @param {number} index Current layer index.
1698 * @param {Object} data Current template data.
1699 */
1700
1701
1702 function createLayerFilter(index, data) {
1703 const description = [];
1704 description.push(editor_('Filters are applied one after another to the entire editor image.', 'sharing-image'));
1705 description.push(editor_('If you want to control their order, create multiple layers.', 'sharing-image'));
1706 const layer = builders.layer({
1707 classes: ['sharing-image-editor-layer', 'layer-text'],
1708 label: editor_('Filter', 'sharing-image'),
1709 description: description.join(' ')
1710 }); // Form fields name for this layer.
1711
1712 const name = editor_params.name + `[layers][${index}]`;
1713 builders.element('input', {
1714 attributes: {
1715 type: 'hidden',
1716 name: name + '[type]',
1717 value: 'filter'
1718 },
1719 append: layer
1720 });
1721 builders.control({
1722 classes: ['sharing-image-editor-control'],
1723 fields: [{
1724 group: 'checkbox',
1725 classes: ['sharing-image-editor-control-checkbox'],
1726 attributes: {
1727 name: name + '[grayscale]',
1728 value: 'grayscale'
1729 },
1730 label: editor_('Turns image into a grayscale version', 'sharing-image'),
1731 checked: data.grayscale
1732 }],
1733 append: layer
1734 });
1735 builders.control({
1736 classes: ['sharing-image-editor-control'],
1737 fields: [{
1738 group: 'checkbox',
1739 classes: ['sharing-image-editor-control-checkbox'],
1740 attributes: {
1741 name: name + '[blur]',
1742 value: 'blur'
1743 },
1744 label: editor_('Blur image by Gaussian effect', 'sharing-image'),
1745 checked: data.blur
1746 }],
1747 append: layer
1748 });
1749 builders.control({
1750 classes: ['sharing-image-editor-control'],
1751 fields: [{
1752 group: 'input',
1753 classes: ['sharing-image-editor-control-range'],
1754 attributes: {
1755 type: 'range',
1756 name: name + '[contrast]',
1757 min: -50,
1758 max: 50,
1759 step: 5,
1760 value: data.contrast || '0'
1761 },
1762 label: editor_('Contrast', 'sharing-image')
1763 }],
1764 append: layer
1765 });
1766 builders.control({
1767 classes: ['sharing-image-editor-control'],
1768 fields: [{
1769 group: 'input',
1770 classes: ['sharing-image-editor-control-range'],
1771 attributes: {
1772 type: 'range',
1773 name: name + '[brightness]',
1774 min: -50,
1775 max: 50,
1776 step: 5,
1777 value: data.brightness || '0'
1778 },
1779 label: editor_('Brightness', 'sharing-image')
1780 }],
1781 append: layer
1782 });
1783 builders.control({
1784 classes: ['sharing-image-editor-control'],
1785 fields: [{
1786 group: 'input',
1787 classes: ['sharing-image-editor-control-range'],
1788 attributes: {
1789 type: 'range',
1790 name: name + '[blackout]',
1791 min: 0,
1792 max: 100,
1793 step: 5,
1794 value: data.blackout || '0'
1795 },
1796 label: editor_('Blackout', 'sharing-image')
1797 }],
1798 append: layer
1799 });
1800 return layer;
1801 }
1802 /**
1803 * Create rectangle layer.
1804 *
1805 * @param {number} index Current layer index.
1806 * @param {Object} data Current template data.
1807 */
1808
1809
1810 function createLayerRectangle(index, data) {
1811 const description = [];
1812 description.push(editor_('Draw a colored rectangle on current image.', 'sharing-image'));
1813 description.push(editor_('You can get filled or outlined figure with custom color and opacity.', 'sharing-image'));
1814 description.push(editor_('Use small height to draw the line.', 'sharing-image'));
1815 const layer = builders.layer({
1816 classes: ['sharing-image-editor-layer', 'layer-text'],
1817 label: editor_('Rectangle', 'sharing-image'),
1818 description: description.join(' ')
1819 }); // Form fields name for this layer.
1820
1821 const name = editor_params.name + `[layers][${index}]`;
1822 builders.element('input', {
1823 attributes: {
1824 type: 'hidden',
1825 name: name + '[type]',
1826 value: 'rectangle'
1827 },
1828 append: layer
1829 });
1830 builders.control({
1831 classes: ['sharing-image-editor-control'],
1832 fields: [{
1833 group: 'input',
1834 classes: ['sharing-image-editor-control-color'],
1835 attributes: {
1836 type: 'color',
1837 name: name + '[color]',
1838 value: data.color || '#ffffff'
1839 },
1840 label: editor_('Rectangle color', 'sharing-image')
1841 }],
1842 append: layer
1843 });
1844 builders.control({
1845 classes: ['sharing-image-editor-control', 'control-sizes'],
1846 fields: [{
1847 group: 'input',
1848 classes: ['sharing-image-editor-control-input'],
1849 attributes: {
1850 type: 'text',
1851 name: name + '[x]' || 0,
1852 value: data.x
1853 },
1854 label: editor_('X', 'sharing-image')
1855 }, {
1856 group: 'input',
1857 classes: ['sharing-image-editor-control-input'],
1858 attributes: {
1859 type: 'text',
1860 name: name + '[y]' || 0,
1861 value: data.y
1862 },
1863 label: editor_('Y', 'sharing-image')
1864 }, {
1865 group: 'input',
1866 classes: ['sharing-image-editor-control-input'],
1867 attributes: {
1868 type: 'text',
1869 name: name + '[width]',
1870 value: data.width
1871 },
1872 label: editor_('Width', 'sharing-image')
1873 }, {
1874 group: 'input',
1875 classes: ['sharing-image-editor-control-input'],
1876 attributes: {
1877 type: 'text',
1878 name: name + '[height]',
1879 value: data.height
1880 },
1881 label: editor_('Height', 'sharing-image')
1882 }],
1883 append: layer
1884 });
1885 createRectangleOutline(layer, name, data);
1886 builders.control({
1887 classes: ['sharing-image-editor-control'],
1888 fields: [{
1889 group: 'input',
1890 classes: ['sharing-image-editor-control-range'],
1891 attributes: {
1892 type: 'range',
1893 name: name + '[opacity]',
1894 min: 0,
1895 max: 100,
1896 step: 5,
1897 value: data.opacity || '0'
1898 },
1899 label: editor_('Opacity', 'sharing-image')
1900 }],
1901 append: layer
1902 });
1903 return layer;
1904 }
1905 /**
1906 * Create new layer.
1907 *
1908 * @param {HTMLElement} designer Designer HTML element.
1909 * @param {string} type New layer type.
1910 * @param {number} index Layer index.
1911 * @param {Object} data New layer data.
1912 */
1913
1914
1915 function createLayer(designer, type, index, data = {}) {
1916 let layer = null;
1917
1918 switch (type) {
1919 case 'image':
1920 layer = createLayerImage(index, data);
1921 break;
1922
1923 case 'text':
1924 layer = createLayerText(index, data);
1925 break;
1926
1927 case 'filter':
1928 layer = createLayerFilter(index, data);
1929 break;
1930
1931 case 'rectangle':
1932 layer = createLayerRectangle(index, data);
1933 break;
1934 }
1935
1936 if (null === layer) {
1937 return;
1938 }
1939
1940 designer.insertBefore(layer, designer.firstChild); // Delete this layer button.
1941
1942 createDeleteLayerButton(designer, layer); // Reorder layers button.
1943
1944 createOrderLayersButton(designer, layer);
1945 }
1946 /**
1947 * Create layers designer control.
1948 *
1949 * @param {HTMLElement} fieldset Fieldset HTML element.
1950 * @param {Object} data Current template data.
1951 */
1952
1953
1954 function createDesigner(fieldset, data) {
1955 const control = builders.control({
1956 classes: ['sharing-image-editor-control', 'control-select', 'control-compact'],
1957 fields: [{
1958 group: 'select',
1959 classes: ['sharing-image-editor-control-select'],
1960 options: {
1961 text: editor_('Text', 'sharing-image'),
1962 image: editor_('Image', 'sharing-image'),
1963 filter: editor_('Filter', 'sharing-image'),
1964 rectangle: editor_('Rectangle', 'sharing-image')
1965 }
1966 }],
1967 append: fieldset
1968 });
1969 const button = builders.element('button', {
1970 classes: ['button'],
1971 text: editor_('Add new', 'sharing-image'),
1972 attributes: {
1973 type: 'button'
1974 },
1975 append: control
1976 });
1977 const designer = builders.element('div', {
1978 classes: ['sharing-image-editor-designer'],
1979 append: fieldset
1980 }); // Set default layers set.
1981
1982 let layers = data.layers || [];
1983 layers = layers.reverse();
1984 layers.forEach((layer, index) => {
1985 if (layer.hasOwnProperty('type')) {
1986 createLayer(designer, layer.type, index++, layer);
1987 }
1988 });
1989 button.addEventListener('click', () => {
1990 const select = control.querySelector('select');
1991
1992 if (null === select) {
1993 return;
1994 }
1995
1996 createLayer(designer, select.value, designer.children.length);
1997 });
1998 }
1999 /**
2000 * Create common settings on template editor screen.
2001 *
2002 * @param {Object} data Current template data.
2003 */
2004
2005
2006 function createFieldset(data) {
2007 const fieldset = builders.element('div', {
2008 classes: ['sharing-image-editor-fieldset'],
2009 append: editor
2010 }); // Create template title control.
2011
2012 builders.control({
2013 classes: ['sharing-image-editor-control', 'control-compact', 'control-extend'],
2014 help: editor_('Used only in the admin panel', 'sharing-image'),
2015 fields: [{
2016 group: 'input',
2017 classes: ['sharing-image-editor-control-input'],
2018 attributes: {
2019 name: editor_params.name + '[title]',
2020 value: data.title
2021 },
2022 dataset: {
2023 persistent: true
2024 },
2025 label: editor_('Template title', 'sharing-image')
2026 }],
2027 append: fieldset
2028 }); // Create background settings with custom logic.
2029
2030 createPermanentAttachment(fieldset, data); // Create width/height settings control.
2031
2032 builders.control({
2033 classes: ['sharing-image-editor-control', 'control-compact', 'control-sizes'],
2034 fields: [{
2035 group: 'input',
2036 classes: ['sharing-image-editor-control-input'],
2037 attributes: {
2038 name: editor_params.name + '[width]',
2039 value: data.width || '1200',
2040 placeholder: '1200'
2041 },
2042 label: editor_('Editor width', 'sharing-image')
2043 }, {
2044 group: 'input',
2045 classes: ['sharing-image-editor-control-input'],
2046 attributes: {
2047 name: editor_params.name + '[height]',
2048 value: data.height || '630',
2049 placeholder: '630'
2050 },
2051 label: editor_('Editor height', 'sharing-image')
2052 }],
2053 append: fieldset
2054 });
2055 const description = [];
2056 description.push(editor_('You can add multiple layers on your editor.', 'sharing-image'));
2057 description.push(editor_('Note that the stacking order of the layers is important.', 'sharing-image'));
2058 description.push(editor_('You can change the order using the arrows in the corner of each box.', 'sharing-image'));
2059 builders.control({
2060 classes: ['sharing-image-editor-control', 'control-reduced'],
2061 label: editor_('Add layers', 'sharing-image'),
2062 description: description.join(' '),
2063 append: fieldset
2064 }); // Create layers designer block.
2065
2066 createDesigner(fieldset, data);
2067 const footer = builders.control({
2068 classes: ['sharing-image-editor-control', 'control-footer'],
2069 append: fieldset
2070 }); // Create back to catalog button.
2071
2072 createCatalogButton(footer); // Create template deletion button.
2073
2074 createDeleteButton(footer);
2075 fieldset.addEventListener('change', e => {
2076 if (editor.classList.contains('editor-suspend')) {
2077 return;
2078 }
2079
2080 const target = e.target; // Skip fields that don't affect the poster.
2081
2082 if (target.hasAttribute('data-persistent')) {
2083 return;
2084 }
2085
2086 if (!target.hasAttribute('name')) {
2087 return;
2088 }
2089
2090 generateTemplate();
2091 });
2092 }
2093 /**
2094 * Create button to submit editor form.
2095 *
2096 * @param {HTMLElement} manager Manager element.
2097 */
2098
2099
2100 function createSubmitButton(manager) {
2101 builders.element('button', {
2102 text: editor_('Save changes', 'sharing-image'),
2103 classes: ['button', 'button-primary'],
2104 attributes: {
2105 type: 'submit'
2106 },
2107 append: manager
2108 });
2109 }
2110 /**
2111 * Create button to generate new template manually.
2112 *
2113 * @param {HTMLElement} manager Manager element.
2114 */
2115
2116
2117 function createGenerateButton(manager) {
2118 const button = builders.element('button', {
2119 text: editor_('Generate preview', 'sharing-image'),
2120 classes: ['button'],
2121 attributes: {
2122 type: 'button'
2123 },
2124 append: manager
2125 });
2126 button.addEventListener('click', () => {
2127 generateTemplate();
2128 });
2129 }
2130 /**
2131 * Create disable live-reloading checkbox.
2132 *
2133 * @param {HTMLElement} manager Manager element.
2134 * @param {Object} data Template data.
2135 */
2136
2137
2138 function createSuspendCheckbox(manager, data) {
2139 const checkbox = builders.checkbox({
2140 classes: ['sharing-image-editor-suspend'],
2141 attributes: {
2142 name: editor_params.name + '[suspend]',
2143 value: 'suspend'
2144 },
2145 label: editor_('Disable live-reload', 'sharing-image'),
2146 checked: data.suspend
2147 }, manager);
2148
2149 if (data.suspend) {
2150 editor.classList.add('editor-suspend');
2151 }
2152
2153 checkbox.addEventListener('change', () => {
2154 editor.classList.remove('editor-suspend');
2155
2156 if (checkbox.checked) {
2157 editor.classList.add('editor-suspend');
2158 }
2159 });
2160 }
2161 /**
2162 * Create template settings preview.
2163 *
2164 * @param {Object} data Current template data.
2165 */
2166
2167
2168 function createMonitor(data) {
2169 const monitor = builders.element('div', {
2170 classes: ['sharing-image-editor-monitor'],
2171 append: editor
2172 });
2173 const viewport = builders.element('div', {
2174 classes: ['sharing-image-editor-viewport'],
2175 append: monitor
2176 });
2177 createPreview(viewport, data);
2178 builders.element('div', {
2179 classes: ['sharing-image-editor-warning'],
2180 append: viewport
2181 });
2182 const manager = builders.element('div', {
2183 classes: ['sharing-image-editor-manager'],
2184 append: viewport
2185 }); // Create live-reload manager checkbox.
2186
2187 createSuspendCheckbox(manager, data); // Create submit form button.
2188
2189 createSubmitButton(manager); // Create template generator button.
2190
2191 createGenerateButton(manager);
2192 }
2193 /**
2194 * Create form hidden settings fields.
2195 *
2196 * @param {HTMLElement} content Settings content element.
2197 * @param {number} index Current option index.
2198 */
2199
2200
2201 function prepareEditor(content, index) {
2202 editor_params.name = 'sharing_image_editor';
2203 const form = builders.element('form', {
2204 classes: ['sharing-image-editor'],
2205 attributes: {
2206 action: editor_params.links.action,
2207 method: 'POST'
2208 },
2209 append: content
2210 });
2211 builders.element('input', {
2212 attributes: {
2213 type: 'hidden',
2214 name: 'action',
2215 value: editor_params.name
2216 },
2217 append: form
2218 });
2219 builders.element('input', {
2220 attributes: {
2221 type: 'hidden',
2222 name: 'sharing_image_index',
2223 value: index
2224 },
2225 append: form
2226 });
2227 builders.element('input', {
2228 attributes: {
2229 type: 'hidden',
2230 name: 'sharing_image_nonce',
2231 value: editor_params.nonce
2232 },
2233 append: form
2234 });
2235 form.addEventListener('submit', e => {
2236 e.preventDefault();
2237 saveTemplate();
2238 });
2239 return form;
2240 }
2241 /**
2242 * Create template editor page.
2243 *
2244 * @param {HTMLElement} content Settings content element.
2245 * @param {Object} settings Global settings object.
2246 * @param {number} index Current option index.
2247 * @param {Object} data Template data.
2248 */
2249
2250
2251 function createEditor(content, settings, index, data = {}) {
2252 editor_params = settings; // Prepare form with hidden fields and events.
2253
2254 editor = prepareEditor(content, index); // Create monitor section part.
2255
2256 createMonitor(data); // Create fieldset section part.
2257
2258 createFieldset(data);
2259 }
2260
2261 /* harmony default export */ const sections_editor = (createEditor);
2262 ;// CONCATENATED MODULE: ./src/scripts/sections/config.js
2263 /**
2264 * Config settings tab.
2265 */
2266
2267 const {
2268 __: config_
2269 } = wp.i18n; // Store global scriot object for settings page.
2270
2271 let config_params = null;
2272 /**
2273 * Create default poster option.
2274 *
2275 * @param {HTMLElement} options Options form element.
2276 * @param {Object} data Config data object.
2277 */
2278
2279 function createDefaultOptions(options, data) {
2280 const control = builders.control({
2281 classes: ['sharing-image-config-control'],
2282 label: config_('Default poster', 'sharing-image'),
2283 append: options
2284 });
2285 builders.media({
2286 name: config_params.name + '[default]',
2287 classes: ['sharing-image-config-control-media'],
2288 label: config_('Default poster', 'sharing-image'),
2289 value: data.default,
2290 link: config_params.links.uploads,
2291 labels: {
2292 button: config_('Upload image', 'sharing-image'),
2293 heading: config_('Select default poster', 'sharing-image'),
2294 details: config_('Attachment details', 'sharing-image'),
2295 remove: config_('Remove image', 'sharing-image')
2296 },
2297 remove: true,
2298 append: control
2299 });
2300 const description = [];
2301 description.push(config_('The default poster is used on pages where there is no generated.', 'sharing-image'));
2302 description.push(config_('Best image size: 1200×630 pixels.', 'sharing-image'));
2303 builders.element('small', {
2304 text: description.join(' '),
2305 append: control
2306 });
2307 }
2308 /**
2309 * Create uploads directory option.
2310 *
2311 * @param {HTMLElement} options Options form element.
2312 * @param {Object} data Config data object.
2313 */
2314
2315
2316 function createUploadsOptions(options, data) {
2317 const control = builders.control({
2318 classes: ['sharing-image-config-control'],
2319 label: config_('Upload directory', 'sharing-image'),
2320 append: options
2321 });
2322 const fieldset = builders.element('div', {
2323 classes: ['sharing-image-config-control-fieldset'],
2324 append: control
2325 });
2326 builders.radio({
2327 classes: ['sharing-image-config-control-radio'],
2328 attributes: {
2329 name: config_params.name + '[uploads]',
2330 value: 'default'
2331 },
2332 label: config_('Use default uploads directory', 'sharing-image'),
2333 checked: data.uploads || 'default'
2334 }, fieldset);
2335 builders.radio({
2336 classes: ['sharing-image-config-control-radio'],
2337 attributes: {
2338 name: config_params.name + '[uploads]',
2339 value: 'custom'
2340 },
2341 label: config_('Choose custom storage for posters', 'sharing-image'),
2342 checked: data.uploads || 'default'
2343 }, fieldset);
2344 const input = builders.input({
2345 classes: ['sharing-image-config-control-input'],
2346 attributes: {
2347 name: config_params.name + '[storage]',
2348 value: data.storage || config_params.links.storage,
2349 disabled: 'disabled'
2350 }
2351 }, control);
2352 builders.element('small', {
2353 text: config_('Use relative path from site root. Directory should be writeable.', 'sharing-image'),
2354 append: control
2355 });
2356 control.querySelectorAll('input').forEach(radio => {
2357 if ('radio' !== radio.type) {
2358 return;
2359 } // Show storage input for checked custom radio.
2360
2361
2362 if (radio.checked && 'custom' === radio.value) {
2363 input.disabled = false;
2364 }
2365
2366 radio.addEventListener('change', () => {
2367 input.disabled = true;
2368
2369 if ('custom' === radio.value) {
2370 input.disabled = false;
2371 }
2372 });
2373 });
2374 }
2375 /**
2376 * Create format and quality poster options.
2377 *
2378 * @param {HTMLElement} options Options form element.
2379 * @param {Object} data Config data object.
2380 */
2381
2382
2383 function createImageOptions(options, data) {
2384 const control = builders.control({
2385 classes: ['sharing-image-config-control', 'control-extra'],
2386 label: config_('Poster image format', 'sharing-image'),
2387 help: config_('The higher the value, the less compression. Availible for JPEG only.', 'sharing-image'),
2388 fields: [{
2389 group: 'select',
2390 classes: ['sharing-image-config-control-select'],
2391 options: {
2392 jpg: config_('JPEG', 'sharing-image'),
2393 png: config_('PNG', 'sharing-image')
2394 },
2395 attributes: {
2396 name: config_params.name + '[format]'
2397 },
2398 selected: data.format || 'jpg'
2399 }, {
2400 group: 'input',
2401 classes: ['sharing-image-config-control-range'],
2402 attributes: {
2403 type: 'range',
2404 name: config_params.name + '[quality]',
2405 min: 10,
2406 max: 100,
2407 step: 5,
2408 value: data.quality || '90',
2409 disabled: 'disabled'
2410 },
2411 label: config_('Image quality', 'sharing-image')
2412 }],
2413 append: options
2414 }); // Find control format select.
2415
2416 const format = control.querySelector('select'); // Find control quiality input.
2417
2418 const quality = control.querySelector('input');
2419
2420 if ('jpg' === format.value) {
2421 quality.disabled = false;
2422 }
2423
2424 format.addEventListener('change', () => {
2425 quality.disabled = true;
2426
2427 if ('jpg' === format.value) {
2428 quality.disabled = false;
2429 }
2430 });
2431 }
2432 /**
2433 * Create required form meta fields.
2434 *
2435 * @param {HTMLElement} options Options form element.
2436 */
2437
2438
2439 function createMetaFields(options) {
2440 builders.element('input', {
2441 attributes: {
2442 type: 'hidden',
2443 name: 'action',
2444 value: config_params.name
2445 },
2446 append: options
2447 });
2448 builders.element('input', {
2449 attributes: {
2450 type: 'hidden',
2451 name: 'sharing_image_nonce',
2452 value: config_params.nonce
2453 },
2454 append: options
2455 });
2456 builders.element('button', {
2457 text: config_('Save changes', 'sharing-image'),
2458 classes: ['button', 'button-primary'],
2459 attributes: {
2460 type: 'submit'
2461 },
2462 append: options
2463 });
2464 }
2465 /**
2466 * Create templates catalog from options.
2467 *
2468 * @param {HTMLElement} content Settings content element.
2469 * @param {Object} settings Global settings field.
2470 */
2471
2472
2473 function createConfig(content, settings) {
2474 config_params = settings; // Set params name for template form fields.
2475
2476 config_params.name = 'sharing_image_config'; // Find config element
2477
2478 const config = content.querySelector('.sharing-image-config');
2479
2480 if (null === config) {
2481 return;
2482 }
2483
2484 const options = builders.element('form', {
2485 classes: ['sharing-image-config-options'],
2486 attributes: {
2487 action: config_params.links.action,
2488 method: 'POST'
2489 },
2490 append: config
2491 });
2492 const data = config_params.config || {}; // Poster image options.
2493
2494 createImageOptions(options, data); // Uploads directory options.
2495
2496 createUploadsOptions(options, data); // Default poster.
2497
2498 createDefaultOptions(options, data); // Create required form fields
2499
2500 createMetaFields(options);
2501 }
2502
2503 /* harmony default export */ const config = (createConfig);
2504 ;// CONCATENATED MODULE: ./src/scripts/sections/premium.js
2505 /**
2506 * Premium settings tab.
2507 */
2508
2509 /* global ajaxurl:true */
2510
2511 const {
2512 __: premium_
2513 } = wp.i18n; // Store global scriot object for settings page.
2514
2515 let premium_params = null; // Premium HTML emelent.
2516
2517 let premium = null;
2518 /**
2519 * Parse error code from settings or AJAX response.
2520 *
2521 * @param {string} code Error code from settings or AJAX response.
2522 * @param {string} title Prepended error title. Optional.
2523 */
2524
2525 function parseErrorCode(code, title) {
2526 const message = [];
2527
2528 if (undefined === title) {
2529 title = premium_('Verification failed.', 'sharing-image');
2530 }
2531
2532 message.push(title);
2533
2534 switch (code) {
2535 case 'LIMIT_EXCEEDED':
2536 message.push(premium_('The number of valid licenses for this key has been exceeded.', 'sharing-image'));
2537 break;
2538
2539 case 'KEY_NOT_FOUND':
2540 message.push(premium_('Premium key is invalid or expired.', 'sharing-image'));
2541 break;
2542
2543 case 'SERVER_ERROR':
2544 message.push(premium_('Unable to get a response from the verification server.', 'sharing-image'));
2545 break;
2546 }
2547
2548 return message.join(' ');
2549 }
2550 /**
2551 * Show premium warning message.
2552 *
2553 * @param {string} message Warning message.
2554 */
2555
2556
2557 function showPremiumError(message) {
2558 // Try to find warning element.
2559 const warning = premium.querySelector('.sharing-image-premium-warning');
2560
2561 if (null === warning) {
2562 return;
2563 }
2564
2565 warning.classList.add('warning-visible');
2566 warning.textContent = message || premium_('Unknown request error', 'sharing-image');
2567 }
2568 /**
2569 * Remove warning message block.
2570 */
2571
2572
2573 function hidePremiumError() {
2574 // Try to find warning element.
2575 const warning = premium.querySelector('.sharing-image-premium-warning');
2576
2577 if (null === warning) {
2578 return;
2579 }
2580
2581 warning.classList.remove('warning-visible');
2582 }
2583 /**
2584 * Revoke Premium key.
2585 *
2586 * @param {HTMLElement} access Access form element.
2587 */
2588
2589
2590 function revokePremium(access) {
2591 access.classList.add('access-loader');
2592 const request = new XMLHttpRequest();
2593 request.open('POST', ajaxurl);
2594 request.responseType = 'json'; // Create data bundle using form data.
2595
2596 const bundle = new window.FormData(access);
2597 bundle.set('action', 'sharing_image_revoke');
2598 hidePremiumError();
2599 request.addEventListener('load', () => {
2600 const response = request.response || {}; // Hide form loader class.
2601
2602 access.classList.remove('access-loader');
2603
2604 if (!response.data) {
2605 return showPremiumError();
2606 }
2607
2608 if (!response.success) {
2609 return showPremiumError(response.data);
2610 }
2611
2612 premium_params.license = response.data; // Refresh premium fields.
2613
2614 preparePremiumFields();
2615 });
2616 request.addEventListener('error', () => {
2617 showPremiumError(); // Hide form loader class.
2618
2619 access.classList.remove('access-loader');
2620 });
2621 request.send(bundle);
2622 }
2623 /**
2624 * Verify Premium key.
2625 *
2626 * @param {HTMLElement} access Access form element.
2627 */
2628
2629
2630 function verifyPremium(access) {
2631 access.classList.add('access-loader');
2632 const request = new XMLHttpRequest();
2633 request.open('POST', ajaxurl);
2634 request.responseType = 'json'; // Create data bundle using form data.
2635
2636 const bundle = new window.FormData(access);
2637 bundle.set('action', 'sharing_image_verify');
2638 hidePremiumError();
2639 request.addEventListener('load', () => {
2640 const response = request.response || {}; // Hide form loader class.
2641
2642 access.classList.remove('access-loader');
2643
2644 if (!response.data) {
2645 return showPremiumError();
2646 }
2647
2648 if (!response.success) {
2649 return showPremiumError(parseErrorCode(response.code, response.data));
2650 }
2651
2652 premium_params.license = response.data; // Refresh premium fields.
2653
2654 preparePremiumFields();
2655 });
2656 request.addEventListener('error', () => {
2657 showPremiumError(); // Hide form loader class.
2658
2659 access.classList.remove('access-loader');
2660 });
2661 request.send(bundle);
2662 }
2663 /**
2664 * Show verify form if stil not premium.
2665 *
2666 * @param {HTMLElement} access Access HTML element.
2667 * @param {Object} license License data.
2668 */
2669
2670
2671 function showVerifyForm(access, license) {
2672 if (license.error) {
2673 showPremiumError(parseErrorCode(license.error));
2674 }
2675
2676 builders.element('strong', {
2677 text: premium_('Do you already have a key? Enter it here', 'sharing-image'),
2678 append: access
2679 });
2680 const verify = builders.element('div', {
2681 classes: ['sharing-image-premium-verify'],
2682 append: access
2683 });
2684 builders.element('input', {
2685 label: premium_('Your Premium key', 'sharing-image'),
2686 attributes: {
2687 type: 'text',
2688 name: 'sharing_image_key',
2689 value: license.key
2690 },
2691 append: verify
2692 });
2693 builders.element('button', {
2694 classes: ['button'],
2695 text: premium_('Submit', 'sharing-image'),
2696 attributes: {
2697 type: 'submit'
2698 },
2699 append: verify
2700 });
2701 builders.element('span', {
2702 classes: ['spinner'],
2703 append: verify
2704 });
2705 access.addEventListener('submit', e => {
2706 e.preventDefault();
2707 verifyPremium(access);
2708 });
2709 }
2710 /**
2711 * Show alert for develop license mode.
2712 */
2713
2714
2715 function showDevelopAlert() {
2716 showPremiumError(premium_('Using plugin with a development license is prohibited in production.', 'sharing-image'));
2717 }
2718 /**
2719 * Show revoke Premium button.
2720 *
2721 * @param {HTMLElement} access Access HTML element.
2722 */
2723
2724
2725 function showRevokeButton(access) {
2726 const revoke = builders.element('div', {
2727 classes: ['sharing-image-premium-revoke'],
2728 append: access
2729 });
2730 const description = [];
2731 description.push(premium_('Disabling premium mode will not remove the license for this domain.', 'sharing-image'));
2732 description.push(premium_('Your current key will also be saved in the plugin settings.', 'sharing-image'));
2733 description.push(premium_('Use key management tool to delete the license for the site.', 'sharing-image'));
2734 builders.element('p', {
2735 text: description.join(' '),
2736 append: revoke
2737 });
2738 builders.element('button', {
2739 classes: ['button'],
2740 text: premium_('Disable Premium'),
2741 attributes: {
2742 type: 'submit'
2743 },
2744 append: revoke
2745 });
2746 builders.element('span', {
2747 classes: ['spinner'],
2748 append: revoke
2749 });
2750 access.addEventListener('submit', e => {
2751 e.preventDefault();
2752 revokePremium(access);
2753 });
2754 }
2755 /**
2756 * Show permit information.
2757 *
2758 * @param {HTMLElement} access Access HTML element.
2759 * @param {string} key License key from settings.
2760 */
2761
2762
2763 function showLicenseInfo(access, key) {
2764 const permit = builders.element('div', {
2765 classes: ['sharing-image-premium-permit'],
2766 append: access
2767 });
2768 const button = builders.element('button', {
2769 classes: ['sharing-image-premium-show', 'button'],
2770 text: premium_('Show License key'),
2771 attributes: {
2772 type: 'button'
2773 },
2774 append: permit
2775 });
2776 button.addEventListener('click', () => {
2777 permit.classList.toggle('permit-visible');
2778 });
2779 builders.element('strong', {
2780 text: key,
2781 append: permit
2782 });
2783 return permit;
2784 }
2785 /**
2786 * Show fields if user has the license.
2787 *
2788 * @param {HTMLElement} access Access HTML element.
2789 * @param {Object} license License data.
2790 */
2791
2792
2793 function showPremiumData(access, license) {
2794 premium.classList.add('premium-enabled');
2795
2796 if (license.develop) {
2797 return showDevelopAlert();
2798 }
2799
2800 if (license.key) {
2801 showLicenseInfo(access, license.key);
2802 }
2803
2804 showRevokeButton(access);
2805 }
2806 /**
2807 * Set premium fields according settings.
2808 */
2809
2810
2811 function preparePremiumFields() {
2812 let access = premium.querySelector('.sharing-image-premium-access');
2813
2814 if (null !== access) {
2815 premium.removeChild(access);
2816 }
2817
2818 access = builders.element('form', {
2819 classes: ['sharing-image-premium-access'],
2820 attributes: {
2821 action: '',
2822 method: 'POST'
2823 },
2824 append: premium
2825 });
2826 premium.classList.remove('premium-enabled');
2827 builders.element('input', {
2828 attributes: {
2829 type: 'hidden',
2830 name: 'sharing_image_nonce',
2831 value: premium_params.nonce
2832 },
2833 append: access
2834 });
2835 const license = premium_params.license || {}; // Show fields if user has the license.
2836
2837 if (license.premium || license.develop) {
2838 return showPremiumData(access, license);
2839 }
2840
2841 return showVerifyForm(access, license);
2842 }
2843 /**
2844 * Create templates catalog from options.
2845 *
2846 * @param {HTMLElement} content Settings content element.
2847 * @param {Object} settings Global settings field.
2848 */
2849
2850
2851 function createPremium(content, settings) {
2852 premium_params = settings; // Find premium element
2853
2854 premium = content.querySelector('.sharing-image-premium');
2855
2856 if (null === premium) {
2857 return;
2858 }
2859
2860 builders.element('div', {
2861 classes: ['sharing-image-premium-warning'],
2862 append: premium
2863 });
2864 preparePremiumFields();
2865 }
2866
2867 /* harmony default export */ const sections_premium = (createPremium);
2868 ;// CONCATENATED MODULE: ./src/scripts/sections/picker.js
2869 /**
2870 * Metabox handler.
2871 */
2872
2873 /* global ajaxurl:true */
2874
2875 const {
2876 __: picker_
2877 } = wp.i18n; // Store global script object for metabox.
2878
2879 let picker_params = null; // Poster HTML element.
2880
2881 let poster = null;
2882 /**
2883 * Show picker warning message.
2884 *
2885 * @param {string} message Warning message.
2886 */
2887
2888 function showPickerError(message) {
2889 const picker = poster.parentNode; // Try to find warning element.
2890
2891 const warning = picker.querySelector('.sharing-image-picker-warning');
2892
2893 if (null === warning) {
2894 return;
2895 }
2896
2897 warning.classList.add('warning-visible');
2898 warning.textContent = message || picker_('Unknown generation error', 'sharing-image');
2899 }
2900 /**
2901 * Remove warning message block.
2902 */
2903
2904
2905 function hidePickerError() {
2906 const picker = poster.parentNode; // Try to find warning element.
2907
2908 const warning = picker.querySelector('.sharing-image-picker-warning');
2909
2910 if (null === warning) {
2911 return;
2912 }
2913
2914 warning.classList.remove('warning-visible');
2915 }
2916 /**
2917 * Handle poster generation action.
2918 *
2919 * @param {HTMLElement} picker Picker element.
2920 */
2921
2922
2923 function generatePoster(picker) {
2924 const request = new XMLHttpRequest();
2925 request.open('POST', ajaxurl);
2926 request.responseType = 'json';
2927 poster.classList.add('poster-loader'); // Create data form data bundle.
2928
2929 const bundle = new window.FormData();
2930 bundle.set('action', 'sharing_image_generate');
2931 picker.querySelectorAll('[name]').forEach(field => {
2932 bundle.append(field.name, field.value);
2933 });
2934 hidePickerError();
2935 request.addEventListener('load', () => {
2936 const response = request.response || {}; // Hide preview loader on request complete.
2937
2938 poster.classList.remove('poster-loader');
2939
2940 if (!response.data) {
2941 return showPickerError();
2942 }
2943
2944 if (!response.success) {
2945 return showPickerError(response.data);
2946 }
2947
2948 for (const key in response.data) {
2949 // Find all poster input fields and set response data value.
2950 poster.querySelectorAll('input').forEach(input => {
2951 const name = picker_params.name + '[' + key + ']';
2952
2953 if (name === input.name) {
2954 input.value = response.data[key];
2955 }
2956 });
2957 }
2958
2959 let image = poster.querySelector('img');
2960
2961 if (null === image) {
2962 image = builders.element('img', {
2963 append: poster
2964 });
2965 }
2966
2967 image.src = response.data.poster; // Show the poster.
2968
2969 poster.classList.add('poster-visible');
2970 });
2971 request.addEventListener('error', () => {
2972 showPickerError(); // Hide preview loader on request complete.
2973
2974 poster.classList.remove('poster-loader');
2975 });
2976 request.send(bundle);
2977 }
2978 /**
2979 * Create designer template selector.
2980 *
2981 * @param {HTMLElement} picker Picker element.
2982 * @param {HTMLElement} designer Designer element.
2983 * @param {Object} selected Seleted template.
2984 */
2985
2986
2987 function createTemplate(picker, designer, selected) {
2988 const fields = {};
2989 picker_params.templates.forEach((template, i) => {
2990 fields[i] = template.title || picker_('Untitled', 'sharing-image');
2991 });
2992 const template = builders.select({
2993 classes: ['sharing-image-picker-template'],
2994 options: fields,
2995 attributes: {
2996 name: picker_params.name + '[template]'
2997 },
2998 selected: String(selected)
2999 }, picker);
3000 template.addEventListener('change', () => {
3001 const fieldset = designer.childNodes;
3002
3003 for (let i = 0; i < fieldset.length; i++) {
3004 fieldset[i].classList.remove('fieldset-visible');
3005
3006 if (i === parseInt(template.value)) {
3007 fieldset[i].classList.add('fieldset-visible');
3008 }
3009 }
3010 });
3011 return template;
3012 }
3013 /**
3014 * Prefill caption fields for classic editor.
3015 *
3016 * @param {HTMLElement} textarea Caption textarea field.
3017 * @param {string} preset Preset field.
3018 */
3019
3020
3021 function fillClassicEditorPreset(textarea, preset) {
3022 const source = document.getElementById(preset);
3023
3024 if (null === source) {
3025 return;
3026 }
3027
3028 const updateCaption = () => {
3029 textarea.value = source.value;
3030 };
3031
3032 source.addEventListener('change', updateCaption); // Stop textarea update after first user input.
3033
3034 textarea.addEventListener('change', () => {
3035 source.removeEventListener('change', updateCaption);
3036 });
3037 updateCaption();
3038 }
3039 /**
3040 * Prefill caption fields for block editor.
3041 *
3042 * @param {HTMLElement} textarea Caption textarea field.
3043 * @param {string} preset Preset field.
3044 */
3045
3046
3047 function fillBlockEditorPreset(textarea, preset) {
3048 const getAttribute = () => {
3049 return wp.data.select('core/editor').getEditedPostAttribute(preset);
3050 };
3051
3052 let attribute = getAttribute();
3053 wp.data.subscribe(() => {
3054 const updated = getAttribute();
3055
3056 if (attribute !== updated) {
3057 textarea.textContent = updated;
3058 }
3059
3060 attribute = updated;
3061 });
3062 }
3063 /**
3064 * Try to prefill caption field.
3065 *
3066 * @param {HTMLElement} textarea Caption textarea field.
3067 * @param {string} preset Preset field.
3068 */
3069
3070
3071 function fillCaptionPreset(textarea, preset) {
3072 if (wp.data) {
3073 return fillBlockEditorPreset(textarea, preset);
3074 }
3075
3076 fillClassicEditorPreset(textarea, preset);
3077 }
3078 /**
3079 * Create designer attachment field for dynamic background.
3080 *
3081 * @param {HTMLElement} fieldset Fieldset element.
3082 * @param {Object} template Template data.
3083 * @param {Array} values Template fieldset values.
3084 * @param {string} name Field name attribute.
3085 */
3086
3087
3088 function createDesignerAttachment(fieldset, template, values, name) {
3089 if ('dynamic' !== template.background) {
3090 return;
3091 }
3092
3093 builders.media({
3094 name: name + '[attachment]',
3095 classes: ['sharing-image-picker-media'],
3096 value: values.attachment,
3097 link: picker_params.links.uploads,
3098 labels: {
3099 button: picker_('Upload background', 'sharing-image'),
3100 heading: picker_('Select background image', 'sharing-image'),
3101 details: picker_('Attachment', 'sharing-image')
3102 },
3103 append: fieldset
3104 });
3105 }
3106 /**
3107 * Create designer captions for text layers.
3108 *
3109 * @param {HTMLElement} fieldset Fieldset element.
3110 * @param {Object} template Template data.
3111 * @param {Array} values Template fieldset values.
3112 * @param {string} name Field name attribute.
3113 */
3114
3115
3116 function createDesignerCaptions(fieldset, template, values, name) {
3117 const captions = values.captions || []; // Set default layers list.
3118
3119 template.layers = template.layers || [];
3120 template.layers.forEach((layer, n) => {
3121 if ('text' !== layer.type || !layer.dynamic) {
3122 return;
3123 }
3124
3125 const textarea = builders.textarea({
3126 classes: ['sharing-image-picker-caption'],
3127 label: layer.title || null,
3128 attributes: {
3129 name: name + `[captions][${n}]`
3130 }
3131 }, fieldset);
3132
3133 if (!captions[n]) {
3134 fillCaptionPreset(textarea, layer.preset);
3135 }
3136
3137 textarea.textContent = captions[n];
3138 });
3139 }
3140 /**
3141 * Create fields designer.
3142 *
3143 * @param {HTMLElement} picker Picker element.
3144 * @param {Object} data Picker data object.
3145 */
3146
3147
3148 function picker_createDesigner(picker, data) {
3149 const designer = builders.element('div', {
3150 classes: ['sharing-image-picker-designer']
3151 });
3152 let selected = data.template || 0; // Reset selected template if index undefined.
3153
3154 if (!picker_params.templates[selected]) {
3155 selected = 0;
3156 } // Create designer fields
3157
3158
3159 picker_params.templates.forEach((template, i) => {
3160 // Set default layers list.
3161 template.layers = template.layers || [];
3162 const fieldset = builders.element('div', {
3163 classes: ['sharing-image-picker-fieldset'],
3164 append: designer
3165 });
3166
3167 if (i === parseInt(selected)) {
3168 fieldset.classList.add('fieldset-visible');
3169 }
3170
3171 let values = {};
3172
3173 if (data.fieldset && data.fieldset[i]) {
3174 values = data.fieldset[i];
3175 }
3176
3177 const name = picker_params.name + `[fieldset][${i}]`;
3178 builders.element('input', {
3179 attributes: {
3180 type: 'hidden',
3181 name: name
3182 },
3183 append: fieldset
3184 }); // Create attachment field.
3185
3186 createDesignerAttachment(fieldset, template, values, name); // Create all caption fields.
3187
3188 createDesignerCaptions(fieldset, template, values, name);
3189 }); // Create template selector.
3190
3191 if (picker_params.templates.length > 1) {
3192 createTemplate(picker, designer, selected);
3193 }
3194
3195 picker.appendChild(designer);
3196 }
3197 /**
3198 * Create button to generate new metabox poster.
3199 *
3200 * @param {HTMLElement} picker Picker element.
3201 * @param {HTMLElement} manager Manager element.
3202 */
3203
3204
3205 function picker_createGenerateButton(picker, manager) {
3206 const button = builders.element('button', {
3207 classes: ['sharing-image-picker-generate', 'button'],
3208 text: picker_('Generate', 'sharing-image'),
3209 attributes: {
3210 type: 'button'
3211 },
3212 append: manager
3213 });
3214 button.addEventListener('click', () => {
3215 generatePoster(picker);
3216 });
3217 }
3218 /**
3219 * Create button to delete current metabox poster.
3220 *
3221 * @param {HTMLElement} manager Manager element.
3222 */
3223
3224
3225 function picker_createDeleteButton(manager) {
3226 const button = builders.element('button', {
3227 classes: ['sharing-image-picker-delete', 'button', 'button-delete'],
3228 text: picker_('Remove', 'sharing-image'),
3229 attributes: {
3230 type: 'button'
3231 },
3232 append: manager
3233 });
3234 button.addEventListener('click', () => {
3235 const image = poster.querySelector('img');
3236
3237 if (null !== image) {
3238 poster.removeChild(image);
3239 }
3240
3241 poster.querySelectorAll('input').forEach(input => {
3242 input.value = '';
3243 });
3244 poster.classList.remove('poster-visible');
3245 });
3246 }
3247 /**
3248 * Create picker manager.
3249 *
3250 * @param {HTMLElement} picker Picker element.
3251 */
3252
3253
3254 function createManager(picker) {
3255 const manager = builders.element('div', {
3256 classes: ['sharing-image-picker-manager'],
3257 append: picker
3258 }); // Create poster generation button.
3259
3260 picker_createGenerateButton(picker, manager); // Create poster removing button.
3261
3262 picker_createDeleteButton(manager);
3263 builders.element('span', {
3264 classes: ['sharing-image-picker-spinner', 'spinner'],
3265 append: manager
3266 });
3267 }
3268 /**
3269 * Create poster block.
3270 *
3271 * @param {HTMLElement} picker Picker element.
3272 * @param {Object} data Picker data object.
3273 */
3274
3275
3276 function createPoster(picker, data) {
3277 poster = builders.element('div', {
3278 classes: ['sharing-image-picker-poster'],
3279 append: picker
3280 });
3281
3282 if (data.poster) {
3283 builders.element('img', {
3284 attributes: {
3285 src: data.poster,
3286 alt: ''
3287 },
3288 append: poster
3289 });
3290 poster.classList.add('poster-visible');
3291 }
3292
3293 builders.element('input', {
3294 attributes: {
3295 type: 'hidden',
3296 name: picker_params.name + '[poster]',
3297 value: data.poster
3298 },
3299 append: poster
3300 });
3301 builders.element('input', {
3302 attributes: {
3303 type: 'hidden',
3304 name: picker_params.name + '[width]',
3305 value: data.width
3306 },
3307 append: poster
3308 });
3309 builders.element('input', {
3310 attributes: {
3311 type: 'hidden',
3312 name: picker_params.name + '[height]',
3313 value: data.height
3314 },
3315 append: poster
3316 });
3317 return poster;
3318 }
3319 /**
3320 * Check that the poster sizes are set or show an error message.
3321 *
3322 * @param {Object} data Picker data object.
3323 */
3324
3325
3326 function showSizesWarning(data) {
3327 if (!data.poster) {
3328 return;
3329 }
3330
3331 if (!data.width || !data.height) {
3332 showPickerError(picker_('Image sizes are not set. Regenerate the poster.', 'sharing-image'));
3333 }
3334 }
3335 /**
3336 * Create metabox generator picker.
3337 *
3338 * @param {HTMLElement} widget Widget element.
3339 * @param {Object} settings Global settings object.
3340 */
3341
3342
3343 function createPicker(widget, settings) {
3344 picker_params = settings; // Set params name for template form fields.
3345
3346 picker_params.name = 'sharing_image_picker';
3347
3348 if ('taxonomy' === picker_params.context) {
3349 const title = builders.element('div', {
3350 classes: ['sharing-image-title'],
3351 append: widget
3352 });
3353 builders.element('strong', {
3354 text: picker_('Sharing Image', 'sharing-image'),
3355 append: title
3356 });
3357 }
3358
3359 const picker = builders.element('div', {
3360 classes: ['sharing-image-picker'],
3361 append: widget
3362 });
3363 const data = picker_params.meta || {}; // Create poster block.
3364
3365 createPoster(picker, data); // Create fields designer.
3366
3367 picker_createDesigner(picker, data);
3368 builders.element('div', {
3369 classes: ['sharing-image-picker-warning'],
3370 append: picker
3371 }); // Show unset poster sizes warning.
3372
3373 showSizesWarning(data); // Create metabox manager block.
3374
3375 createManager(picker);
3376 builders.element('input', {
3377 attributes: {
3378 type: 'hidden',
3379 name: 'sharing_image_nonce',
3380 value: picker_params.nonce
3381 },
3382 append: picker
3383 });
3384 }
3385
3386 /* harmony default export */ const picker = (createPicker);
3387 ;// CONCATENATED MODULE: ./src/scripts/sections/index.js
3388
3389
3390
3391
3392
3393 const Section = {
3394 catalog: catalog,
3395 editor: sections_editor,
3396 config: config,
3397 premium: sections_premium,
3398 picker: picker
3399 };
3400 /* harmony default export */ const sections = (Section);
3401 ;// CONCATENATED MODULE: ./src/scripts/widget.js
3402
3403 /**
3404 * Init metabox handler.
3405 */
3406
3407 (function () {
3408 if (typeof 'undefined' === wp) {
3409 return;
3410 }
3411
3412 const object = window.sharingImageWidget || {}; // Set default templates empty list.
3413
3414 object.templates = object.templates || []; // Find metabox element.
3415
3416 const widgets = document.querySelectorAll('.sharing-image-widget');
3417 widgets.forEach(widget => {
3418 if (object.context) {
3419 widget.classList.add(`widget-${object.context}`);
3420 }
3421
3422 sections.picker(widget, object);
3423 });
3424 })();
3425 /******/ })()
3426 ;