PluginProbe
Sharing Image / 2.0.1
Sharing Image v2.0.1
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 / settings.js

settings.js in Sharing Image 2.0.1, at assets/scripts/settings.js

3,496 lines 85.2 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/helpers/param.js
6 /**
7 * Get current location search parameter.
8 *
9 * @param {string} key URL parameter key.
10 */
11 function getSearchParam(key) {
12 const params = new URL(document.location.href);
13 return params.searchParams.get(key);
14 }
15
16 /* harmony default export */ const param = (getSearchParam);
17 ;// CONCATENATED MODULE: ./src/scripts/helpers/attachment.js
18 /**
19 * Upload media frame.
20 *
21 * @param {string} header Frame header text.
22 * @param {Function} callback Callback function.
23 */
24 function uploadMedia(header, callback) {
25 const frame = wp.media({
26 title: header,
27 multiple: false
28 });
29 frame.on('select', () => {
30 const selection = frame.state().get('selection').first().toJSON();
31
32 if (selection.id) {
33 callback(selection.id);
34 }
35 });
36 frame.open();
37 }
38
39 /* harmony default export */ const attachment = (uploadMedia);
40 ;// CONCATENATED MODULE: ./src/scripts/helpers/defaults.js
41 /**
42 * Append empty default properties to object if not exist.
43 *
44 * @param {Object} object Source object.
45 * @param {Array} defaults Required defaults properties.
46 */
47 function intersectDefaults(object, defaults) {
48 defaults.forEach(item => {
49 if (undefined === object[item]) {
50 object[item] = {};
51 }
52 });
53 return object;
54 }
55
56 /* harmony default export */ const defaults = (intersectDefaults);
57 ;// CONCATENATED MODULE: ./src/scripts/helpers/index.js
58
59
60
61 const Helper = {
62 param: param,
63 attachment: attachment,
64 defaults: defaults
65 };
66 /* harmony default export */ const helpers = (Helper);
67 ;// CONCATENATED MODULE: ./src/scripts/builders/element.js
68 /**
69 * Helper to create new DOM element.
70 *
71 * @param {string} tag Element tagname.
72 * @param {Object} args List of element options.
73 */
74 function buildElement(tag, args) {
75 const element = document.createElement(tag); // Set class list
76
77 if (args.hasOwnProperty('classes')) {
78 args.classes.forEach(cl => {
79 element.classList.add(cl);
80 });
81 } // Set textContent
82
83
84 if (args.hasOwnProperty('text')) {
85 element.textContent = args.text;
86 } // Set innerHTML
87
88
89 if (args.hasOwnProperty('html')) {
90 element.innerHTML = args.html;
91 } // Set attributes
92
93
94 if (args.hasOwnProperty('attributes')) {
95 for (const key in args.attributes) {
96 const value = args.attributes[key];
97
98 if (undefined === value) {
99 continue;
100 }
101
102 element.setAttribute(key, value);
103 }
104 } // Set data attributes
105
106
107 if (args.hasOwnProperty('dataset')) {
108 for (const key in args.dataset) {
109 element.setAttribute('data-' + key, args.dataset[key]);
110 }
111 } // Append child
112
113
114 if (args.hasOwnProperty('append')) {
115 args.append.appendChild(element);
116 } // Prepend child
117
118
119 if (args.hasOwnProperty('prepend')) {
120 args.prepend.insertBefore(element, args.prepend.firstChild);
121 }
122
123 return element;
124 }
125
126 /* harmony default export */ const builders_element = (buildElement);
127 ;// CONCATENATED MODULE: ./src/scripts/builders/input.js
128
129
130 /**
131 * Helper to create input 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 buildInput(args, parent) {
138 const field = builders_element('div', {
139 classes: args.classes || [],
140 append: parent
141 });
142
143 if (args.hasOwnProperty('label')) {
144 const label = builders_element('h4', {
145 text: args.label
146 });
147
148 if (null !== args.label) {
149 field.appendChild(label);
150 }
151 }
152
153 const input = builders_element('input', {
154 attributes: {
155 type: 'text'
156 },
157 dataset: args.dataset || {},
158 append: field
159 }); // Set attributes.
160
161 if (args.hasOwnProperty('attributes')) {
162 for (const key in args.attributes) {
163 const value = args.attributes[key];
164
165 if (undefined === value) {
166 continue;
167 }
168
169 input.setAttribute(key, value);
170 }
171 }
172
173 if ('range' === input.type) {
174 const counter = builders.element('em', {
175 text: input.value,
176 append: field
177 });
178 input.addEventListener('change', () => {
179 counter.textContent = input.value;
180 });
181 input.addEventListener('input', () => {
182 counter.textContent = input.value;
183 });
184 }
185
186 return input;
187 }
188
189 /* harmony default export */ const input = (buildInput);
190 ;// CONCATENATED MODULE: ./src/scripts/builders/checkbox.js
191
192 /**
193 * Helper to create radio field.
194 *
195 * @param {Object} args List of control options.
196 * @param {HTMLElement} parent Parent HTML element to append this field.
197 */
198
199 function buildCheckbox(args, parent) {
200 const field = builders_element('label', {
201 classes: args.classes || [],
202 append: parent
203 });
204 const checkbox = builders_element('input', {
205 attributes: {
206 type: 'checkbox'
207 },
208 dataset: args.dataset || {},
209 append: field
210 }); // Set attributes
211
212 if (args.hasOwnProperty('attributes')) {
213 for (const key in args.attributes) {
214 const value = args.attributes[key];
215
216 if (undefined === value) {
217 continue;
218 }
219
220 checkbox.setAttribute(key, value);
221 }
222 }
223
224 if (args.hasOwnProperty('checked')) {
225 const checked = args.checked;
226
227 if (checked && checked === checkbox.value) {
228 checkbox.setAttribute('checked', 'checked');
229 }
230 }
231
232 if (args.hasOwnProperty('label')) {
233 const label = builders_element('span', {
234 text: args.label
235 });
236
237 if (null !== args.label) {
238 field.appendChild(label);
239 }
240 }
241
242 return checkbox;
243 }
244
245 /* harmony default export */ const builders_checkbox = (buildCheckbox);
246 ;// CONCATENATED MODULE: ./src/scripts/builders/radio.js
247
248 /**
249 * Helper to create radio field.
250 *
251 * @param {Object} args List of control options.
252 * @param {HTMLElement} parent Parent HTML element to append this field.
253 */
254
255 function buildRadio(args, parent) {
256 const field = builders_element('label', {
257 classes: args.classes || [],
258 append: parent
259 });
260 const radio = builders_element('input', {
261 attributes: {
262 type: 'radio'
263 },
264 dataset: args.dataset || {},
265 append: field
266 }); // Set attributes
267
268 if (args.hasOwnProperty('attributes')) {
269 for (const key in args.attributes) {
270 const value = args.attributes[key];
271
272 if (undefined === value) {
273 continue;
274 }
275
276 radio.setAttribute(key, value);
277 }
278 }
279
280 if (args.hasOwnProperty('checked')) {
281 const checked = args.checked;
282
283 if (checked && checked === radio.value) {
284 radio.setAttribute('checked', 'checked');
285 }
286 }
287
288 if (args.hasOwnProperty('label')) {
289 const label = builders_element('span', {
290 text: args.label
291 });
292
293 if (null !== args.label) {
294 field.appendChild(label);
295 }
296 }
297
298 return radio;
299 }
300
301 /* harmony default export */ const builders_radio = (buildRadio);
302 ;// CONCATENATED MODULE: ./src/scripts/builders/select.js
303
304 /**
305 * Helper to create select field.
306 *
307 * @param {Object} args List of control options.
308 * @param {HTMLElement} parent Parent HTML element to append this field.
309 */
310
311 function buildSelect(args, parent) {
312 const field = builders_element('div', {
313 classes: args.classes || [],
314 append: parent
315 });
316
317 if (args.hasOwnProperty('label')) {
318 const label = builders_element('h4', {
319 text: args.label
320 });
321
322 if (null !== args.label) {
323 field.appendChild(label);
324 }
325 }
326
327 const select = builders_element('select', {
328 dataset: args.dataset || {},
329 append: field
330 }); // Set attributes
331
332 if (args.hasOwnProperty('attributes')) {
333 for (const key in args.attributes) {
334 const value = args.attributes[key];
335
336 if (undefined === value) {
337 continue;
338 }
339
340 select.setAttribute(key, value);
341 }
342 }
343
344 const options = args.options || {};
345
346 for (const key in options) {
347 const option = builders_element('option', {
348 text: options[key],
349 attributes: {
350 value: key
351 },
352 append: select
353 });
354
355 if (args.hasOwnProperty('selected')) {
356 const selected = args.selected;
357
358 if (selected && selected === option.value) {
359 option.setAttribute('selected', 'selected');
360 }
361 }
362 }
363
364 return select;
365 }
366
367 /* harmony default export */ const builders_select = (buildSelect);
368 ;// CONCATENATED MODULE: ./src/scripts/builders/textarea.js
369
370 /**
371 * Helper to create input field.
372 *
373 * @param {Object} args List of control options.
374 * @param {HTMLElement} parent Parent HTML element to append this field.
375 */
376
377 function buildTextarea(args, parent) {
378 const field = builders_element('div', {
379 classes: args.classes || [],
380 append: parent
381 });
382
383 if (args.hasOwnProperty('label')) {
384 const label = builders_element('h4', {
385 text: args.label
386 });
387
388 if (null !== args.label) {
389 field.appendChild(label);
390 }
391 }
392
393 const textarea = builders_element('textarea', {
394 dataset: args.dataset || {},
395 append: field
396 }); // Set attributes
397
398 if (args.hasOwnProperty('attributes')) {
399 for (const key in args.attributes) {
400 const value = args.attributes[key];
401
402 if (undefined === value) {
403 continue;
404 }
405
406 textarea.setAttribute(key, value);
407 }
408 } // Set content
409
410
411 if (args.hasOwnProperty('content')) {
412 const content = args.content;
413
414 if (undefined !== content) {
415 textarea.innerHTML = content;
416 }
417 }
418
419 return textarea;
420 }
421
422 /* harmony default export */ const builders_textarea = (buildTextarea);
423 ;// CONCATENATED MODULE: ./src/scripts/builders/control.js
424
425
426
427
428
429
430 /**
431 * Helper to create control.
432 *
433 * @param {Object} args List of control options.
434 */
435
436 function buildControl(args) {
437 const control = builders_element('div', {
438 classes: args.classes || []
439 });
440
441 if (args.hasOwnProperty('append')) {
442 args.append.appendChild(control);
443 }
444
445 if (args.hasOwnProperty('prepend')) {
446 args.prepend.insertBefore(control, args.prepend.firstChild);
447 }
448
449 if (args.hasOwnProperty('label')) {
450 builders_element('h3', {
451 text: args.label,
452 append: control
453 });
454 }
455
456 if (args.hasOwnProperty('description')) {
457 builders_element('p', {
458 text: args.description,
459 append: control
460 });
461 }
462
463 if (args.hasOwnProperty('fields')) {
464 args.fields.forEach(field => {
465 switch (field.group) {
466 case 'input':
467 input(field, control);
468 break;
469
470 case 'textarea':
471 builders_textarea(field, control);
472 break;
473
474 case 'radio':
475 builders_radio(field, control);
476 break;
477
478 case 'select':
479 builders_select(field, control);
480 break;
481
482 case 'checkbox':
483 builders_checkbox(field, control);
484 break;
485 }
486 });
487 }
488
489 if (args.hasOwnProperty('help')) {
490 builders_element('small', {
491 text: args.help,
492 append: control
493 });
494 }
495
496 return control;
497 }
498
499 /* harmony default export */ const control = (buildControl);
500 ;// CONCATENATED MODULE: ./src/scripts/builders/layer.js
501
502 /**
503 * Helper to create layer.
504 *
505 * @param {Object} args List of layer options.
506 */
507
508 function buildLayer(args) {
509 const layer = builders_element('div', {
510 classes: args.classes || []
511 });
512
513 if (args.hasOwnProperty('append')) {
514 args.append.appendChild(layer);
515 }
516
517 if (args.hasOwnProperty('prepend')) {
518 args.prepend.insertBefore(layer, args.prepend.firstChild);
519 }
520
521 if (args.hasOwnProperty('label')) {
522 builders_element('h2', {
523 text: args.label,
524 append: layer
525 });
526 }
527
528 if (args.hasOwnProperty('description')) {
529 builders_element('h5', {
530 text: args.description,
531 append: layer
532 });
533 }
534
535 return layer;
536 }
537
538 /* harmony default export */ const builders_layer = (buildLayer);
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);
1941 reorderLayers(designer); // Delete this layer button.
1942
1943 createDeleteLayerButton(designer, layer); // Reorder layers button.
1944
1945 createOrderLayersButton(designer, layer);
1946 }
1947 /**
1948 * Create layers designer control.
1949 *
1950 * @param {HTMLElement} fieldset Fieldset HTML element.
1951 * @param {Object} data Current template data.
1952 */
1953
1954
1955 function createDesigner(fieldset, data) {
1956 const control = builders.control({
1957 classes: ['sharing-image-editor-control', 'control-select', 'control-compact'],
1958 fields: [{
1959 group: 'select',
1960 classes: ['sharing-image-editor-control-select'],
1961 options: {
1962 text: editor_('Text', 'sharing-image'),
1963 image: editor_('Image', 'sharing-image'),
1964 filter: editor_('Filter', 'sharing-image'),
1965 rectangle: editor_('Rectangle', 'sharing-image')
1966 }
1967 }],
1968 append: fieldset
1969 });
1970 const button = builders.element('button', {
1971 classes: ['button'],
1972 text: editor_('Add new', 'sharing-image'),
1973 attributes: {
1974 type: 'button'
1975 },
1976 append: control
1977 });
1978 const designer = builders.element('div', {
1979 classes: ['sharing-image-editor-designer'],
1980 append: fieldset
1981 }); // Set default layers set.
1982
1983 let layers = data.layers || [];
1984 layers = layers.reverse();
1985 layers.forEach((layer, index) => {
1986 if (layer.hasOwnProperty('type')) {
1987 createLayer(designer, layer.type, index++, layer);
1988 }
1989 });
1990 button.addEventListener('click', () => {
1991 const select = control.querySelector('select');
1992
1993 if (null === select) {
1994 return;
1995 }
1996
1997 createLayer(designer, select.value, designer.children.length);
1998 });
1999 }
2000 /**
2001 * Create common settings on template editor screen.
2002 *
2003 * @param {Object} data Current template data.
2004 */
2005
2006
2007 function createFieldset(data) {
2008 const fieldset = builders.element('div', {
2009 classes: ['sharing-image-editor-fieldset'],
2010 append: editor
2011 }); // Create template title control.
2012
2013 builders.control({
2014 classes: ['sharing-image-editor-control', 'control-compact', 'control-extend'],
2015 help: editor_('Used only in the admin panel', 'sharing-image'),
2016 fields: [{
2017 group: 'input',
2018 classes: ['sharing-image-editor-control-input'],
2019 attributes: {
2020 name: editor_params.name + '[title]',
2021 value: data.title
2022 },
2023 dataset: {
2024 persistent: true
2025 },
2026 label: editor_('Template title', 'sharing-image')
2027 }],
2028 append: fieldset
2029 }); // Create background settings with custom logic.
2030
2031 createPermanentAttachment(fieldset, data); // Create width/height settings control.
2032
2033 builders.control({
2034 classes: ['sharing-image-editor-control', 'control-compact', 'control-sizes'],
2035 fields: [{
2036 group: 'input',
2037 classes: ['sharing-image-editor-control-input'],
2038 attributes: {
2039 name: editor_params.name + '[width]',
2040 value: data.width || '1200',
2041 placeholder: '1200'
2042 },
2043 label: editor_('Editor width', 'sharing-image')
2044 }, {
2045 group: 'input',
2046 classes: ['sharing-image-editor-control-input'],
2047 attributes: {
2048 name: editor_params.name + '[height]',
2049 value: data.height || '630',
2050 placeholder: '630'
2051 },
2052 label: editor_('Editor height', 'sharing-image')
2053 }],
2054 append: fieldset
2055 });
2056 const description = [];
2057 description.push(editor_('You can add multiple layers on your editor.', 'sharing-image'));
2058 description.push(editor_('Note that the stacking order of the layers is important.', 'sharing-image'));
2059 description.push(editor_('You can change the order using the arrows in the corner of each box.', 'sharing-image'));
2060 builders.control({
2061 classes: ['sharing-image-editor-control', 'control-reduced'],
2062 label: editor_('Add layers', 'sharing-image'),
2063 description: description.join(' '),
2064 append: fieldset
2065 }); // Create layers designer block.
2066
2067 createDesigner(fieldset, data);
2068 const footer = builders.control({
2069 classes: ['sharing-image-editor-control', 'control-footer'],
2070 append: fieldset
2071 }); // Create back to catalog button.
2072
2073 createCatalogButton(footer); // Create template deletion button.
2074
2075 createDeleteButton(footer);
2076 fieldset.addEventListener('change', e => {
2077 if (editor.classList.contains('editor-suspend')) {
2078 return;
2079 }
2080
2081 const target = e.target; // Skip fields that don't affect the poster.
2082
2083 if (target.hasAttribute('data-persistent')) {
2084 return;
2085 }
2086
2087 if (!target.hasAttribute('name')) {
2088 return;
2089 }
2090
2091 generateTemplate();
2092 });
2093 }
2094 /**
2095 * Create button to submit editor form.
2096 *
2097 * @param {HTMLElement} manager Manager element.
2098 */
2099
2100
2101 function createSubmitButton(manager) {
2102 builders.element('button', {
2103 text: editor_('Save changes', 'sharing-image'),
2104 classes: ['button', 'button-primary'],
2105 attributes: {
2106 type: 'submit'
2107 },
2108 append: manager
2109 });
2110 }
2111 /**
2112 * Create button to generate new template manually.
2113 *
2114 * @param {HTMLElement} manager Manager element.
2115 */
2116
2117
2118 function createGenerateButton(manager) {
2119 const button = builders.element('button', {
2120 text: editor_('Generate preview', 'sharing-image'),
2121 classes: ['button'],
2122 attributes: {
2123 type: 'button'
2124 },
2125 append: manager
2126 });
2127 button.addEventListener('click', () => {
2128 generateTemplate();
2129 });
2130 }
2131 /**
2132 * Create disable live-reloading checkbox.
2133 *
2134 * @param {HTMLElement} manager Manager element.
2135 * @param {Object} data Template data.
2136 */
2137
2138
2139 function createSuspendCheckbox(manager, data) {
2140 const checkbox = builders.checkbox({
2141 classes: ['sharing-image-editor-suspend'],
2142 attributes: {
2143 name: editor_params.name + '[suspend]',
2144 value: 'suspend'
2145 },
2146 label: editor_('Disable live-reload', 'sharing-image'),
2147 checked: data.suspend
2148 }, manager);
2149
2150 if (data.suspend) {
2151 editor.classList.add('editor-suspend');
2152 }
2153
2154 checkbox.addEventListener('change', () => {
2155 editor.classList.remove('editor-suspend');
2156
2157 if (checkbox.checked) {
2158 editor.classList.add('editor-suspend');
2159 }
2160 });
2161 }
2162 /**
2163 * Create template settings preview.
2164 *
2165 * @param {Object} data Current template data.
2166 */
2167
2168
2169 function createMonitor(data) {
2170 const monitor = builders.element('div', {
2171 classes: ['sharing-image-editor-monitor'],
2172 append: editor
2173 });
2174 const viewport = builders.element('div', {
2175 classes: ['sharing-image-editor-viewport'],
2176 append: monitor
2177 });
2178 createPreview(viewport, data);
2179 builders.element('div', {
2180 classes: ['sharing-image-editor-warning'],
2181 append: viewport
2182 });
2183 const manager = builders.element('div', {
2184 classes: ['sharing-image-editor-manager'],
2185 append: viewport
2186 }); // Create live-reload manager checkbox.
2187
2188 createSuspendCheckbox(manager, data); // Create submit form button.
2189
2190 createSubmitButton(manager); // Create template generator button.
2191
2192 createGenerateButton(manager);
2193 }
2194 /**
2195 * Create form hidden settings fields.
2196 *
2197 * @param {HTMLElement} content Settings content element.
2198 * @param {number} index Current option index.
2199 */
2200
2201
2202 function prepareEditor(content, index) {
2203 editor_params.name = 'sharing_image_editor';
2204 const form = builders.element('form', {
2205 classes: ['sharing-image-editor'],
2206 attributes: {
2207 action: editor_params.links.action,
2208 method: 'POST'
2209 },
2210 append: content
2211 });
2212 builders.element('input', {
2213 attributes: {
2214 type: 'hidden',
2215 name: 'action',
2216 value: editor_params.name
2217 },
2218 append: form
2219 });
2220 builders.element('input', {
2221 attributes: {
2222 type: 'hidden',
2223 name: 'sharing_image_index',
2224 value: index
2225 },
2226 append: form
2227 });
2228 builders.element('input', {
2229 attributes: {
2230 type: 'hidden',
2231 name: 'sharing_image_nonce',
2232 value: editor_params.nonce
2233 },
2234 append: form
2235 });
2236 form.addEventListener('submit', e => {
2237 e.preventDefault();
2238 saveTemplate();
2239 });
2240 return form;
2241 }
2242 /**
2243 * Create template editor page.
2244 *
2245 * @param {HTMLElement} content Settings content element.
2246 * @param {Object} settings Global settings object.
2247 * @param {number} index Current option index.
2248 * @param {Object} data Template data.
2249 */
2250
2251
2252 function createEditor(content, settings, index, data = {}) {
2253 editor_params = settings; // Prepare form with hidden fields and events.
2254
2255 editor = prepareEditor(content, index); // Create monitor section part.
2256
2257 createMonitor(data); // Create fieldset section part.
2258
2259 createFieldset(data);
2260 }
2261
2262 /* harmony default export */ const sections_editor = (createEditor);
2263 ;// CONCATENATED MODULE: ./src/scripts/sections/config.js
2264 /**
2265 * Config settings tab.
2266 */
2267
2268 const {
2269 __: config_
2270 } = wp.i18n; // Store global scriot object for settings page.
2271
2272 let config_params = null;
2273 /**
2274 * Create default poster option.
2275 *
2276 * @param {HTMLElement} options Options form element.
2277 * @param {Object} data Config data object.
2278 */
2279
2280 function createDefaultOptions(options, data) {
2281 const control = builders.control({
2282 classes: ['sharing-image-config-control'],
2283 label: config_('Default poster', 'sharing-image'),
2284 append: options
2285 });
2286 builders.media({
2287 name: config_params.name + '[default]',
2288 classes: ['sharing-image-config-control-media'],
2289 label: config_('Default poster', 'sharing-image'),
2290 value: data.default,
2291 link: config_params.links.uploads,
2292 labels: {
2293 button: config_('Upload image', 'sharing-image'),
2294 heading: config_('Select default poster', 'sharing-image'),
2295 details: config_('Attachment details', 'sharing-image'),
2296 remove: config_('Remove image', 'sharing-image')
2297 },
2298 remove: true,
2299 append: control
2300 });
2301 const description = [];
2302 description.push(config_('The default poster is used on pages where there is no generated.', 'sharing-image'));
2303 description.push(config_('Best image size: 1200×630 pixels.', 'sharing-image'));
2304 builders.element('small', {
2305 text: description.join(' '),
2306 append: control
2307 });
2308 }
2309 /**
2310 * Create uploads directory option.
2311 *
2312 * @param {HTMLElement} options Options form element.
2313 * @param {Object} data Config data object.
2314 */
2315
2316
2317 function createUploadsOptions(options, data) {
2318 const control = builders.control({
2319 classes: ['sharing-image-config-control'],
2320 label: config_('Upload directory', 'sharing-image'),
2321 append: options
2322 });
2323 const fieldset = builders.element('div', {
2324 classes: ['sharing-image-config-control-fieldset'],
2325 append: control
2326 });
2327 builders.radio({
2328 classes: ['sharing-image-config-control-radio'],
2329 attributes: {
2330 name: config_params.name + '[uploads]',
2331 value: 'default'
2332 },
2333 label: config_('Use default uploads directory', 'sharing-image'),
2334 checked: data.uploads || 'default'
2335 }, fieldset);
2336 builders.radio({
2337 classes: ['sharing-image-config-control-radio'],
2338 attributes: {
2339 name: config_params.name + '[uploads]',
2340 value: 'custom'
2341 },
2342 label: config_('Choose custom storage for posters', 'sharing-image'),
2343 checked: data.uploads || 'default'
2344 }, fieldset);
2345 const input = builders.input({
2346 classes: ['sharing-image-config-control-input'],
2347 attributes: {
2348 name: config_params.name + '[storage]',
2349 value: data.storage || config_params.links.storage,
2350 disabled: 'disabled'
2351 }
2352 }, control);
2353 builders.element('small', {
2354 text: config_('Use relative path from site root. Directory should be writeable.', 'sharing-image'),
2355 append: control
2356 });
2357 control.querySelectorAll('input').forEach(radio => {
2358 if ('radio' !== radio.type) {
2359 return;
2360 } // Show storage input for checked custom radio.
2361
2362
2363 if (radio.checked && 'custom' === radio.value) {
2364 input.disabled = false;
2365 }
2366
2367 radio.addEventListener('change', () => {
2368 input.disabled = true;
2369
2370 if ('custom' === radio.value) {
2371 input.disabled = false;
2372 }
2373 });
2374 });
2375 }
2376 /**
2377 * Create format and quality poster options.
2378 *
2379 * @param {HTMLElement} options Options form element.
2380 * @param {Object} data Config data object.
2381 */
2382
2383
2384 function createImageOptions(options, data) {
2385 const control = builders.control({
2386 classes: ['sharing-image-config-control', 'control-extra'],
2387 label: config_('Poster image format', 'sharing-image'),
2388 help: config_('The higher the value, the less compression. Availible for JPEG only.', 'sharing-image'),
2389 fields: [{
2390 group: 'select',
2391 classes: ['sharing-image-config-control-select'],
2392 options: {
2393 jpg: config_('JPEG', 'sharing-image'),
2394 png: config_('PNG', 'sharing-image')
2395 },
2396 attributes: {
2397 name: config_params.name + '[format]'
2398 },
2399 selected: data.format || 'jpg'
2400 }, {
2401 group: 'input',
2402 classes: ['sharing-image-config-control-range'],
2403 attributes: {
2404 type: 'range',
2405 name: config_params.name + '[quality]',
2406 min: 10,
2407 max: 100,
2408 step: 5,
2409 value: data.quality || '90',
2410 disabled: 'disabled'
2411 },
2412 label: config_('Image quality', 'sharing-image')
2413 }],
2414 append: options
2415 }); // Find control format select.
2416
2417 const format = control.querySelector('select'); // Find control quiality input.
2418
2419 const quality = control.querySelector('input');
2420
2421 if ('jpg' === format.value) {
2422 quality.disabled = false;
2423 }
2424
2425 format.addEventListener('change', () => {
2426 quality.disabled = true;
2427
2428 if ('jpg' === format.value) {
2429 quality.disabled = false;
2430 }
2431 });
2432 }
2433 /**
2434 * Create required form meta fields.
2435 *
2436 * @param {HTMLElement} options Options form element.
2437 */
2438
2439
2440 function createMetaFields(options) {
2441 builders.element('input', {
2442 attributes: {
2443 type: 'hidden',
2444 name: 'action',
2445 value: config_params.name
2446 },
2447 append: options
2448 });
2449 builders.element('input', {
2450 attributes: {
2451 type: 'hidden',
2452 name: 'sharing_image_nonce',
2453 value: config_params.nonce
2454 },
2455 append: options
2456 });
2457 builders.element('button', {
2458 text: config_('Save changes', 'sharing-image'),
2459 classes: ['button', 'button-primary'],
2460 attributes: {
2461 type: 'submit'
2462 },
2463 append: options
2464 });
2465 }
2466 /**
2467 * Create templates catalog from options.
2468 *
2469 * @param {HTMLElement} content Settings content element.
2470 * @param {Object} settings Global settings field.
2471 */
2472
2473
2474 function createConfig(content, settings) {
2475 config_params = settings; // Set params name for template form fields.
2476
2477 config_params.name = 'sharing_image_config'; // Find config element
2478
2479 const config = content.querySelector('.sharing-image-config');
2480
2481 if (null === config) {
2482 return;
2483 }
2484
2485 const options = builders.element('form', {
2486 classes: ['sharing-image-config-options'],
2487 attributes: {
2488 action: config_params.links.action,
2489 method: 'POST'
2490 },
2491 append: config
2492 });
2493 const data = config_params.config || {}; // Poster image options.
2494
2495 createImageOptions(options, data); // Uploads directory options.
2496
2497 createUploadsOptions(options, data); // Default poster.
2498
2499 createDefaultOptions(options, data); // Create required form fields
2500
2501 createMetaFields(options);
2502 }
2503
2504 /* harmony default export */ const config = (createConfig);
2505 ;// CONCATENATED MODULE: ./src/scripts/sections/premium.js
2506 /**
2507 * Premium settings tab.
2508 */
2509
2510 /* global ajaxurl:true */
2511
2512 const {
2513 __: premium_
2514 } = wp.i18n; // Store global scriot object for settings page.
2515
2516 let premium_params = null; // Premium HTML emelent.
2517
2518 let premium = null;
2519 /**
2520 * Parse error code from settings or AJAX response.
2521 *
2522 * @param {string} code Error code from settings or AJAX response.
2523 * @param {string} title Prepended error title. Optional.
2524 */
2525
2526 function parseErrorCode(code, title) {
2527 const message = [];
2528
2529 if (undefined === title) {
2530 title = premium_('Verification failed.', 'sharing-image');
2531 }
2532
2533 message.push(title);
2534
2535 switch (code) {
2536 case 'LIMIT_EXCEEDED':
2537 message.push(premium_('The number of valid licenses for this key has been exceeded.', 'sharing-image'));
2538 break;
2539
2540 case 'KEY_NOT_FOUND':
2541 message.push(premium_('Premium key is invalid or expired.', 'sharing-image'));
2542 break;
2543
2544 case 'SERVER_ERROR':
2545 message.push(premium_('Unable to get a response from the verification server.', 'sharing-image'));
2546 break;
2547
2548 default:
2549 message.push(premium_('Unknown error.', 'sharing-image'));
2550 }
2551
2552 return message.join(' ');
2553 }
2554 /**
2555 * Show premium warning message.
2556 *
2557 * @param {string} message Warning message.
2558 */
2559
2560
2561 function showPremiumError(message) {
2562 // Try to find warning element.
2563 const warning = premium.querySelector('.sharing-image-premium-warning');
2564
2565 if (null === warning) {
2566 return;
2567 }
2568
2569 warning.classList.add('warning-visible');
2570 warning.textContent = message || premium_('Unknown request error', 'sharing-image');
2571 }
2572 /**
2573 * Remove warning message block.
2574 */
2575
2576
2577 function hidePremiumError() {
2578 // Try to find warning element.
2579 const warning = premium.querySelector('.sharing-image-premium-warning');
2580
2581 if (null === warning) {
2582 return;
2583 }
2584
2585 warning.classList.remove('warning-visible');
2586 }
2587 /**
2588 * Revoke Premium key.
2589 *
2590 * @param {HTMLElement} access Access form element.
2591 */
2592
2593
2594 function revokePremium(access) {
2595 access.classList.add('access-loader');
2596 const request = new XMLHttpRequest();
2597 request.open('POST', ajaxurl);
2598 request.responseType = 'json'; // Create data bundle using form data.
2599
2600 const bundle = new window.FormData(access);
2601 bundle.set('action', 'sharing_image_revoke');
2602 hidePremiumError();
2603 request.addEventListener('load', () => {
2604 const response = request.response || {}; // Hide form loader class.
2605
2606 access.classList.remove('access-loader');
2607
2608 if (!response.data) {
2609 return showPremiumError();
2610 }
2611
2612 if (!response.success) {
2613 return showPremiumError(response.data);
2614 }
2615
2616 premium_params.license = response.data; // Refresh premium fields.
2617
2618 preparePremiumFields();
2619 });
2620 request.addEventListener('error', () => {
2621 showPremiumError(); // Hide form loader class.
2622
2623 access.classList.remove('access-loader');
2624 });
2625 request.send(bundle);
2626 }
2627 /**
2628 * Verify Premium key.
2629 *
2630 * @param {HTMLElement} access Access form element.
2631 */
2632
2633
2634 function verifyPremium(access) {
2635 access.classList.add('access-loader');
2636 const request = new XMLHttpRequest();
2637 request.open('POST', ajaxurl);
2638 request.responseType = 'json'; // Create data bundle using form data.
2639
2640 const bundle = new window.FormData(access);
2641 bundle.set('action', 'sharing_image_verify');
2642 hidePremiumError();
2643 request.addEventListener('load', () => {
2644 const response = request.response || {}; // Hide form loader class.
2645
2646 access.classList.remove('access-loader');
2647
2648 if (!response.data) {
2649 return showPremiumError();
2650 }
2651
2652 if (!response.success) {
2653 return showPremiumError(parseErrorCode(response.code, response.data));
2654 }
2655
2656 premium_params.license = response.data; // Refresh premium fields.
2657
2658 preparePremiumFields();
2659 });
2660 request.addEventListener('error', () => {
2661 showPremiumError(); // Hide form loader class.
2662
2663 access.classList.remove('access-loader');
2664 });
2665 request.send(bundle);
2666 }
2667 /**
2668 * Show verify form if stil not premium.
2669 *
2670 * @param {HTMLElement} access Access HTML element.
2671 * @param {Object} license License data.
2672 */
2673
2674
2675 function showVerifyForm(access, license) {
2676 if (license.error) {
2677 showPremiumError(parseErrorCode(license.error));
2678 }
2679
2680 builders.element('strong', {
2681 text: premium_('Do you already have a key? Enter it here', 'sharing-image'),
2682 append: access
2683 });
2684 const verify = builders.element('div', {
2685 classes: ['sharing-image-premium-verify'],
2686 append: access
2687 });
2688 builders.element('input', {
2689 label: premium_('Your Premium key', 'sharing-image'),
2690 attributes: {
2691 type: 'text',
2692 name: 'sharing_image_key',
2693 value: license.key
2694 },
2695 append: verify
2696 });
2697 builders.element('button', {
2698 classes: ['button'],
2699 text: premium_('Submit', 'sharing-image'),
2700 attributes: {
2701 type: 'submit'
2702 },
2703 append: verify
2704 });
2705 builders.element('span', {
2706 classes: ['spinner'],
2707 append: verify
2708 });
2709 access.addEventListener('submit', e => {
2710 e.preventDefault();
2711 verifyPremium(access);
2712 });
2713 }
2714 /**
2715 * Show alert for develop license mode.
2716 */
2717
2718
2719 function showDevelopAlert() {
2720 showPremiumError(premium_('Using plugin with a development license is prohibited in production.', 'sharing-image'));
2721 }
2722 /**
2723 * Show revoke Premium button.
2724 *
2725 * @param {HTMLElement} access Access HTML element.
2726 */
2727
2728
2729 function showRevokeButton(access) {
2730 const revoke = builders.element('div', {
2731 classes: ['sharing-image-premium-revoke'],
2732 append: access
2733 });
2734 const description = [];
2735 description.push(premium_('Disabling premium mode will not remove the license for this domain.', 'sharing-image'));
2736 description.push(premium_('Your current key will also be saved in the plugin settings.', 'sharing-image'));
2737 description.push(premium_('Use key management tool to delete the license for the site.', 'sharing-image'));
2738 builders.element('p', {
2739 text: description.join(' '),
2740 append: revoke
2741 });
2742 builders.element('button', {
2743 classes: ['button'],
2744 text: premium_('Disable Premium'),
2745 attributes: {
2746 type: 'submit'
2747 },
2748 append: revoke
2749 });
2750 builders.element('span', {
2751 classes: ['spinner'],
2752 append: revoke
2753 });
2754 access.addEventListener('submit', e => {
2755 e.preventDefault();
2756 revokePremium(access);
2757 });
2758 }
2759 /**
2760 * Show permit information.
2761 *
2762 * @param {HTMLElement} access Access HTML element.
2763 * @param {string} key License key from settings.
2764 */
2765
2766
2767 function showLicenseInfo(access, key) {
2768 const permit = builders.element('div', {
2769 classes: ['sharing-image-premium-permit'],
2770 append: access
2771 });
2772 const button = builders.element('button', {
2773 classes: ['sharing-image-premium-show', 'button'],
2774 text: premium_('Show License key'),
2775 attributes: {
2776 type: 'button'
2777 },
2778 append: permit
2779 });
2780 button.addEventListener('click', () => {
2781 permit.classList.toggle('permit-visible');
2782 });
2783 builders.element('strong', {
2784 text: key,
2785 append: permit
2786 });
2787 return permit;
2788 }
2789 /**
2790 * Show fields if user has the license.
2791 *
2792 * @param {HTMLElement} access Access HTML element.
2793 * @param {Object} license License data.
2794 */
2795
2796
2797 function showPremiumData(access, license) {
2798 premium.classList.add('premium-enabled');
2799
2800 if (license.develop) {
2801 return showDevelopAlert();
2802 }
2803
2804 if (license.key) {
2805 showLicenseInfo(access, license.key);
2806 }
2807
2808 showRevokeButton(access);
2809 }
2810 /**
2811 * Set premium fields according settings.
2812 */
2813
2814
2815 function preparePremiumFields() {
2816 let access = premium.querySelector('.sharing-image-premium-access');
2817
2818 if (null !== access) {
2819 premium.removeChild(access);
2820 }
2821
2822 access = builders.element('form', {
2823 classes: ['sharing-image-premium-access'],
2824 attributes: {
2825 action: '',
2826 method: 'POST'
2827 },
2828 append: premium
2829 });
2830 premium.classList.remove('premium-enabled');
2831 builders.element('input', {
2832 attributes: {
2833 type: 'hidden',
2834 name: 'sharing_image_nonce',
2835 value: premium_params.nonce
2836 },
2837 append: access
2838 });
2839 const license = premium_params.license || {}; // Show fields if user has the license.
2840
2841 if (license.premium || license.develop) {
2842 return showPremiumData(access, license);
2843 }
2844
2845 return showVerifyForm(access, license);
2846 }
2847 /**
2848 * Create templates catalog from options.
2849 *
2850 * @param {HTMLElement} content Settings content element.
2851 * @param {Object} settings Global settings field.
2852 */
2853
2854
2855 function createPremium(content, settings) {
2856 premium_params = settings; // Find premium element
2857
2858 premium = content.querySelector('.sharing-image-premium');
2859
2860 if (null === premium) {
2861 return;
2862 }
2863
2864 builders.element('div', {
2865 classes: ['sharing-image-premium-warning'],
2866 append: premium
2867 });
2868 preparePremiumFields();
2869 }
2870
2871 /* harmony default export */ const sections_premium = (createPremium);
2872 ;// CONCATENATED MODULE: ./src/scripts/sections/picker.js
2873 /**
2874 * Metabox handler.
2875 */
2876
2877 /* global ajaxurl:true */
2878
2879 const {
2880 __: picker_
2881 } = wp.i18n; // Store global script object for metabox.
2882
2883 let picker_params = null; // Poster HTML element.
2884
2885 let poster = null;
2886 /**
2887 * Show picker warning message.
2888 *
2889 * @param {string} message Warning message.
2890 */
2891
2892 function showPickerError(message) {
2893 const picker = poster.parentNode; // Try to find warning element.
2894
2895 const warning = picker.querySelector('.sharing-image-picker-warning');
2896
2897 if (null === warning) {
2898 return;
2899 }
2900
2901 warning.classList.add('warning-visible');
2902 warning.textContent = message || picker_('Unknown generation error', 'sharing-image');
2903 }
2904 /**
2905 * Remove warning message block.
2906 */
2907
2908
2909 function hidePickerError() {
2910 const picker = poster.parentNode; // Try to find warning element.
2911
2912 const warning = picker.querySelector('.sharing-image-picker-warning');
2913
2914 if (null === warning) {
2915 return;
2916 }
2917
2918 warning.classList.remove('warning-visible');
2919 }
2920 /**
2921 * Handle poster generation action.
2922 *
2923 * @param {HTMLElement} picker Picker element.
2924 */
2925
2926
2927 function generatePoster(picker) {
2928 const request = new XMLHttpRequest();
2929 request.open('POST', ajaxurl);
2930 request.responseType = 'json';
2931 poster.classList.add('poster-loader'); // Create data form data bundle.
2932
2933 const bundle = new window.FormData();
2934 bundle.set('action', 'sharing_image_generate');
2935 picker.querySelectorAll('[name]').forEach(field => {
2936 bundle.append(field.name, field.value);
2937 });
2938 hidePickerError();
2939 request.addEventListener('load', () => {
2940 const response = request.response || {}; // Hide preview loader on request complete.
2941
2942 poster.classList.remove('poster-loader');
2943
2944 if (!response.data) {
2945 return showPickerError();
2946 }
2947
2948 if (!response.success) {
2949 return showPickerError(response.data);
2950 }
2951
2952 for (const key in response.data) {
2953 // Find all poster input fields and set response data value.
2954 poster.querySelectorAll('input').forEach(input => {
2955 const name = picker_params.name + '[' + key + ']';
2956
2957 if (name === input.name) {
2958 input.value = response.data[key];
2959 }
2960 });
2961 }
2962
2963 let image = poster.querySelector('img');
2964
2965 if (null === image) {
2966 image = builders.element('img', {
2967 append: poster
2968 });
2969 }
2970
2971 image.src = response.data.poster; // Show the poster.
2972
2973 poster.classList.add('poster-visible');
2974 });
2975 request.addEventListener('error', () => {
2976 showPickerError(); // Hide preview loader on request complete.
2977
2978 poster.classList.remove('poster-loader');
2979 });
2980 request.send(bundle);
2981 }
2982 /**
2983 * Create designer template selector.
2984 *
2985 * @param {HTMLElement} picker Picker element.
2986 * @param {HTMLElement} designer Designer element.
2987 * @param {Object} selected Seleted template.
2988 */
2989
2990
2991 function createTemplate(picker, designer, selected) {
2992 const fields = {};
2993 picker_params.templates.forEach((template, i) => {
2994 fields[i] = template.title || picker_('Untitled', 'sharing-image');
2995 });
2996 const template = builders.select({
2997 classes: ['sharing-image-picker-template'],
2998 options: fields,
2999 attributes: {
3000 name: picker_params.name + '[template]'
3001 },
3002 selected: String(selected)
3003 }, picker);
3004 template.addEventListener('change', () => {
3005 const fieldset = designer.childNodes;
3006
3007 for (let i = 0; i < fieldset.length; i++) {
3008 fieldset[i].classList.remove('fieldset-visible');
3009
3010 if (i === parseInt(template.value)) {
3011 fieldset[i].classList.add('fieldset-visible');
3012 }
3013 }
3014 });
3015 return template;
3016 }
3017 /**
3018 * Prefill caption fields for classic editor.
3019 *
3020 * @param {HTMLElement} textarea Caption textarea field.
3021 * @param {string} preset Preset field.
3022 */
3023
3024
3025 function fillClassicEditorPreset(textarea, preset) {
3026 const source = document.getElementById(preset);
3027
3028 if (null === source) {
3029 return;
3030 }
3031
3032 const updateCaption = () => {
3033 textarea.value = source.value;
3034 };
3035
3036 source.addEventListener('change', updateCaption); // Stop textarea update after first user input.
3037
3038 textarea.addEventListener('change', () => {
3039 source.removeEventListener('change', updateCaption);
3040 });
3041 updateCaption();
3042 }
3043 /**
3044 * Prefill caption fields for block editor.
3045 *
3046 * @param {HTMLElement} textarea Caption textarea field.
3047 * @param {string} preset Preset field.
3048 */
3049
3050
3051 function fillBlockEditorPreset(textarea, preset) {
3052 const getAttribute = () => {
3053 return wp.data.select('core/editor').getEditedPostAttribute(preset);
3054 };
3055
3056 let attribute = getAttribute();
3057 wp.data.subscribe(() => {
3058 const updated = getAttribute();
3059
3060 if (attribute !== updated) {
3061 textarea.textContent = updated;
3062 }
3063
3064 attribute = updated;
3065 });
3066 }
3067 /**
3068 * Try to prefill caption field.
3069 *
3070 * @param {HTMLElement} textarea Caption textarea field.
3071 * @param {string} preset Preset field.
3072 */
3073
3074
3075 function fillCaptionPreset(textarea, preset) {
3076 if (wp.data) {
3077 return fillBlockEditorPreset(textarea, preset);
3078 }
3079
3080 fillClassicEditorPreset(textarea, preset);
3081 }
3082 /**
3083 * Create designer attachment field for dynamic background.
3084 *
3085 * @param {HTMLElement} fieldset Fieldset element.
3086 * @param {Object} template Template data.
3087 * @param {Array} values Template fieldset values.
3088 * @param {string} name Field name attribute.
3089 */
3090
3091
3092 function createDesignerAttachment(fieldset, template, values, name) {
3093 if ('dynamic' !== template.background) {
3094 return;
3095 }
3096
3097 builders.media({
3098 name: name + '[attachment]',
3099 classes: ['sharing-image-picker-media'],
3100 value: values.attachment,
3101 link: picker_params.links.uploads,
3102 labels: {
3103 button: picker_('Upload background', 'sharing-image'),
3104 heading: picker_('Select background image', 'sharing-image'),
3105 details: picker_('Attachment', 'sharing-image')
3106 },
3107 append: fieldset
3108 });
3109 }
3110 /**
3111 * Create designer captions for text layers.
3112 *
3113 * @param {HTMLElement} fieldset Fieldset element.
3114 * @param {Object} template Template data.
3115 * @param {Array} values Template fieldset values.
3116 * @param {string} name Field name attribute.
3117 */
3118
3119
3120 function createDesignerCaptions(fieldset, template, values, name) {
3121 const captions = values.captions || []; // Set default layers list.
3122
3123 template.layers = template.layers || [];
3124 template.layers.forEach((layer, n) => {
3125 if ('text' !== layer.type || !layer.dynamic) {
3126 return;
3127 }
3128
3129 const textarea = builders.textarea({
3130 classes: ['sharing-image-picker-caption'],
3131 label: layer.title || null,
3132 attributes: {
3133 name: name + `[captions][${n}]`
3134 }
3135 }, fieldset);
3136
3137 if (!captions[n]) {
3138 fillCaptionPreset(textarea, layer.preset);
3139 }
3140
3141 textarea.textContent = captions[n];
3142 });
3143 }
3144 /**
3145 * Create fields designer.
3146 *
3147 * @param {HTMLElement} picker Picker element.
3148 * @param {Object} data Picker data object.
3149 */
3150
3151
3152 function picker_createDesigner(picker, data) {
3153 const designer = builders.element('div', {
3154 classes: ['sharing-image-picker-designer']
3155 });
3156 let selected = data.template || 0; // Reset selected template if index undefined.
3157
3158 if (!picker_params.templates[selected]) {
3159 selected = 0;
3160 } // Create designer fields
3161
3162
3163 picker_params.templates.forEach((template, i) => {
3164 // Set default layers list.
3165 template.layers = template.layers || [];
3166 const fieldset = builders.element('div', {
3167 classes: ['sharing-image-picker-fieldset'],
3168 append: designer
3169 });
3170
3171 if (i === parseInt(selected)) {
3172 fieldset.classList.add('fieldset-visible');
3173 }
3174
3175 let values = {};
3176
3177 if (data.fieldset && data.fieldset[i]) {
3178 values = data.fieldset[i];
3179 }
3180
3181 const name = picker_params.name + `[fieldset][${i}]`;
3182 builders.element('input', {
3183 attributes: {
3184 type: 'hidden',
3185 name: name
3186 },
3187 append: fieldset
3188 }); // Create attachment field.
3189
3190 createDesignerAttachment(fieldset, template, values, name); // Create all caption fields.
3191
3192 createDesignerCaptions(fieldset, template, values, name);
3193 }); // Create template selector.
3194
3195 if (picker_params.templates.length > 1) {
3196 createTemplate(picker, designer, selected);
3197 }
3198
3199 picker.appendChild(designer);
3200 }
3201 /**
3202 * Create button to generate new metabox poster.
3203 *
3204 * @param {HTMLElement} picker Picker element.
3205 * @param {HTMLElement} manager Manager element.
3206 */
3207
3208
3209 function picker_createGenerateButton(picker, manager) {
3210 const button = builders.element('button', {
3211 classes: ['sharing-image-picker-generate', 'button'],
3212 text: picker_('Generate', 'sharing-image'),
3213 attributes: {
3214 type: 'button'
3215 },
3216 append: manager
3217 });
3218 button.addEventListener('click', () => {
3219 generatePoster(picker);
3220 });
3221 }
3222 /**
3223 * Create button to delete current metabox poster.
3224 *
3225 * @param {HTMLElement} manager Manager element.
3226 */
3227
3228
3229 function picker_createDeleteButton(manager) {
3230 const button = builders.element('button', {
3231 classes: ['sharing-image-picker-delete', 'button', 'button-delete'],
3232 text: picker_('Remove', 'sharing-image'),
3233 attributes: {
3234 type: 'button'
3235 },
3236 append: manager
3237 });
3238 button.addEventListener('click', () => {
3239 const image = poster.querySelector('img');
3240
3241 if (null !== image) {
3242 poster.removeChild(image);
3243 }
3244
3245 poster.querySelectorAll('input').forEach(input => {
3246 input.value = '';
3247 });
3248 poster.classList.remove('poster-visible');
3249 });
3250 }
3251 /**
3252 * Create picker manager.
3253 *
3254 * @param {HTMLElement} picker Picker element.
3255 */
3256
3257
3258 function createManager(picker) {
3259 const manager = builders.element('div', {
3260 classes: ['sharing-image-picker-manager'],
3261 append: picker
3262 }); // Create poster generation button.
3263
3264 picker_createGenerateButton(picker, manager); // Create poster removing button.
3265
3266 picker_createDeleteButton(manager);
3267 builders.element('span', {
3268 classes: ['sharing-image-picker-spinner', 'spinner'],
3269 append: manager
3270 });
3271 }
3272 /**
3273 * Create poster block.
3274 *
3275 * @param {HTMLElement} picker Picker element.
3276 * @param {Object} data Picker data object.
3277 */
3278
3279
3280 function createPoster(picker, data) {
3281 poster = builders.element('div', {
3282 classes: ['sharing-image-picker-poster'],
3283 append: picker
3284 });
3285
3286 if (data.poster) {
3287 builders.element('img', {
3288 attributes: {
3289 src: data.poster,
3290 alt: ''
3291 },
3292 append: poster
3293 });
3294 poster.classList.add('poster-visible');
3295 }
3296
3297 builders.element('input', {
3298 attributes: {
3299 type: 'hidden',
3300 name: picker_params.name + '[poster]',
3301 value: data.poster
3302 },
3303 append: poster
3304 });
3305 builders.element('input', {
3306 attributes: {
3307 type: 'hidden',
3308 name: picker_params.name + '[width]',
3309 value: data.width
3310 },
3311 append: poster
3312 });
3313 builders.element('input', {
3314 attributes: {
3315 type: 'hidden',
3316 name: picker_params.name + '[height]',
3317 value: data.height
3318 },
3319 append: poster
3320 });
3321 return poster;
3322 }
3323 /**
3324 * Check that the poster sizes are set or show an error message.
3325 *
3326 * @param {Object} data Picker data object.
3327 */
3328
3329
3330 function showSizesWarning(data) {
3331 if (!data.poster) {
3332 return;
3333 }
3334
3335 if (!data.width || !data.height) {
3336 showPickerError(picker_('Image sizes are not set. Regenerate the poster.', 'sharing-image'));
3337 }
3338 }
3339 /**
3340 * Create metabox generator picker.
3341 *
3342 * @param {HTMLElement} widget Widget element.
3343 * @param {Object} settings Global settings object.
3344 */
3345
3346
3347 function createPicker(widget, settings) {
3348 picker_params = settings; // Set params name for template form fields.
3349
3350 picker_params.name = 'sharing_image_picker';
3351
3352 if ('taxonomy' === picker_params.context) {
3353 const title = builders.element('div', {
3354 classes: ['sharing-image-title'],
3355 append: widget
3356 });
3357 builders.element('strong', {
3358 text: picker_('Sharing Image', 'sharing-image'),
3359 append: title
3360 });
3361 }
3362
3363 const picker = builders.element('div', {
3364 classes: ['sharing-image-picker'],
3365 append: widget
3366 });
3367 const data = picker_params.meta || {}; // Create poster block.
3368
3369 createPoster(picker, data); // Create fields designer.
3370
3371 picker_createDesigner(picker, data);
3372 builders.element('div', {
3373 classes: ['sharing-image-picker-warning'],
3374 append: picker
3375 }); // Show unset poster sizes warning.
3376
3377 showSizesWarning(data); // Create metabox manager block.
3378
3379 createManager(picker);
3380 builders.element('input', {
3381 attributes: {
3382 type: 'hidden',
3383 name: 'sharing_image_nonce',
3384 value: picker_params.nonce
3385 },
3386 append: picker
3387 });
3388 }
3389
3390 /* harmony default export */ const picker = (createPicker);
3391 ;// CONCATENATED MODULE: ./src/scripts/sections/index.js
3392
3393
3394
3395
3396
3397 const Section = {
3398 catalog: catalog,
3399 editor: sections_editor,
3400 config: config,
3401 premium: sections_premium,
3402 picker: picker
3403 };
3404 /* harmony default export */ const sections = (Section);
3405 ;// CONCATENATED MODULE: ./src/scripts/settings.js
3406
3407
3408 /**
3409 * Init premium settings tab.
3410 *
3411 * @param {HTMLElement} content Settings content element.
3412 * @param {Object} settings Global settings object.
3413 */
3414
3415 function initPremiumTab(content, settings) {
3416 sections.premium(content, settings);
3417 }
3418 /**
3419 * Init config settings tab.
3420 *
3421 * @param {HTMLElement} content Settings content element.
3422 * @param {Object} settings Global settings object.
3423 */
3424
3425
3426 function initConfigTab(content, settings) {
3427 sections.config(content, settings);
3428 }
3429 /**
3430 * Init config settings tab.
3431 *
3432 * @param {HTMLElement} content Settings content element.
3433 * @param {Object} settings Global settings object.
3434 */
3435
3436
3437 function initTemplatesTab(content, settings) {
3438 // Get index from URL search parameter.
3439 let index = null; // Set default templates empty list.
3440
3441 settings.templates = settings.templates || [];
3442
3443 if (helpers.param('template')) {
3444 index = parseInt(helpers.param('template')) - 1;
3445 }
3446
3447 const data = settings.templates[index]; // Create editor for existing template.
3448
3449 if (undefined !== data) {
3450 return sections.editor(content, settings, index, data);
3451 } // Create editor for new template.
3452
3453
3454 if (settings.templates.length === index) {
3455 return sections.editor(content, settings, index);
3456 }
3457
3458 sections.catalog(content, settings);
3459 }
3460 /**
3461 * Init settings page handler.
3462 */
3463
3464
3465 (function () {
3466 if (typeof 'undefined' === wp) {
3467 return;
3468 }
3469
3470 let object = window.sharingImageSettings || {}; // Add default required values to object.
3471
3472 object = helpers.defaults(object, ['links', 'fonts', 'config', 'templates', 'license']); // Find settings content element.
3473
3474 const content = document.querySelector('#sharing-image-settings .sharing-image-content');
3475
3476 if (null === content) {
3477 return;
3478 }
3479
3480 content.classList.add('content-visible');
3481
3482 switch (helpers.param('tab')) {
3483 case 'config':
3484 initConfigTab(content, object);
3485 break;
3486
3487 case 'premium':
3488 initPremiumTab(content, object);
3489 break;
3490
3491 default:
3492 initTemplatesTab(content, object);
3493 }
3494 })();
3495 /******/ })()
3496 ;