PluginProbe
Sharing Image / 2.0.7
Sharing Image v2.0.7
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.7, at assets/scripts/settings.js

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