PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.6.9
VikBooking Hotel Booking Engine & PMS v1.6.9
1.8.16 1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 All 37 releases
vikbooking / admin / resources / js / gutenberg-shortcodes.js

gutenberg-shortcodes.js in VikBooking Hotel Booking Engine & PMS 1.6.9, at admin/resources/js/gutenberg-shortcodes.js

625 lines 17.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function(wp) {
2
3 /**
4 * Registers a new block provided a unique name and an object defining its behavior.
5 * @link https://github.com/WordPress/gutenberg/tree/master/blocks#api
6 */
7 const registerBlockType = wp.blocks.registerBlockType;
8
9 /**
10 * Returns a new element of given type. Element is an abstraction layer atop React.
11 * @link https://github.com/WordPress/gutenberg/tree/master/packages/element#element
12 */
13 const el = wp.element.createElement;
14
15 /**
16 * Retrieves the translation of text.
17 * @link https://github.com/WordPress/gutenberg/tree/master/i18n#api
18 */
19 const __ = wp.i18n.__;
20
21 /**
22 * This variable is used to keep the very first shortcode
23 * after the loading of the page.
24 */
25 let currentShortcode = null;
26
27 /**
28 * This registry holds the information specified during the
29 * creation of a new shortcode.
30 */
31 let newShortcodeRegistry = null;
32
33 /**
34 * Every block starts by registering a new block type definition.
35 * @link https://wordpress.org/gutenberg/handbook/block-api/
36 */
37 registerBlockType('vikbooking/gutenberg-shortcodes', {
38 /**
39 * This is the block display title, which can be translated with `i18n` functions.
40 * The block inserter will show this name.
41 */
42 title: __('VikBooking Shortcode', 'vikbooking'),
43
44 /**
45 * This is the block description, which is displayed within the right sidebar.
46 */
47 description: __('Add a shortcode configured through VikBooking.', 'vikbooking'),
48
49 /**
50 * The icon can be a DASHICON or a SVG entity.
51 * NOTE: we need to use a different icon because Gutenberg seems
52 * to have problems in displaying the coffee icon.
53 */
54 icon: 'building',
55
56 /**
57 * Blocks are grouped into categories to help users browse and discover them.
58 * The categories provided by core are `common`, `embed`, `formatting`, `layout` and `widgets`.
59 */
60 category: 'widgets',
61
62 /**
63 * Sometimes a block could have aliases that help users discover it while searching.
64 * You can do so by providing an array of terms (which can be translated).
65 * It is only allowed to add as much as three terms per block.
66 */
67 keywords: [
68 __('shortcodes'), __('list'), __('page'),
69 ],
70
71 /**
72 * Optional block extended support features.
73 */
74 supports: {
75 // do not edit as HTML
76 html: false,
77
78 // use the block just once per post
79 multiple: false,
80
81 // don't allow the block to be converted into a reusable block
82 reusable: false,
83 },
84
85 /**
86 * Attributes provide the structured data needs of a block.
87 * They can exist in different forms when they are serialized,
88 * but they are declared together under a common interface.
89 */
90 attributes: {
91 shortcode: {
92 type: 'string',
93 source: 'html',
94 selector: 'div',
95 },
96 toggler: {
97 type: 'boolean',
98 default: false,
99 }
100 },
101
102 /**
103 * The edit function describes the structure of your block in the context of the editor.
104 * This represents what the editor will render when the block is used.
105 * @link https://wordpress.org/gutenberg/handbook/block-edit-save/#edit
106 *
107 * @param Object props Properties passed from the editor.
108 *
109 * @return Element Element to render.
110 */
111 edit: (props) => {
112
113 // iterate vikbooking shortcodes to build select options
114 let options = [];
115
116 let shortcodes_boxes = [];
117
118 if (currentShortcode === null) {
119 // if not set, define current value
120 currentShortcode = props.attributes.shortcode;
121 }
122
123 // insert empty option
124 options.push({
125 label: __('- pick a shortcode -', 'vikbooking'),
126 value: '',
127 });
128
129 // insert an option to support the creation of new shortcodes
130 options.push({
131 label: __('- create a new shortcode -', 'vikbooking'),
132 value: 'new',
133 });
134
135 // evaluate if toggler checkbox is checked
136 let togglerChecked = props.attributes.toggler;
137
138 for (let group in VIKBOOKING_SHORTCODES_BLOCK.shortcodes) {
139 let groups = VIKBOOKING_SHORTCODES_BLOCK.shortcodes[group];
140
141 for (let i = 0; i < groups.length; i++) {
142 let data = groups[i];
143
144 let post_id = parseInt(data.post_id);
145
146 // push option only in case:
147 // - toggler is enabled (see all)
148 // - the shortcode is not assigned to any post
149 // - the page of the shortcode is equal to this one
150 // - the shortcode is equal to the current one
151 if (togglerChecked || !post_id || post_id === wp.data.select('core/editor').getCurrentPostId() || data.shortcode == currentShortcode) {
152
153 options.push({
154 label: data.name,
155 value: data.shortcode,
156 });
157
158 // build ASSIGNEE field
159 let assigneeField = null;
160
161 if (post_id && data.shortcode != currentShortcode) {
162 assigneeField = el(
163 'a',
164 {
165 href: 'javascript:void(0);',
166 onClick: () => {
167 alert(__('This shortcode is already used by a different post. If you select this shortcode, it will be detached from the existing post and assigned to this new post.', 'vikbooking'));
168 },
169 },
170 el(
171 'span',
172 {
173 className: 'assigned',
174 },
175 __('Post #', 'vikbooking') + post_id
176 )
177 );
178 } else {
179 // safe shortcode
180 assigneeField = el('span', {}, (post_id ? __('Post #', 'vikbooking') + post_id : '--'));
181 }
182
183 // check if the box should be displayed
184 let toggled = props.attributes.shortcode == data.shortcode;
185
186 // setup information div
187 shortcodes_boxes.push(el(
188 'div',
189 {
190 className: 'vikbooking-shortcode-info-box' + (toggled ? ' toggled' : ''),
191 },
192 // create child elements
193 el(
194 'div',
195 {
196 className: 'vbo-sh-info-control'
197 },
198 [
199 el('label', {}, __('Type:', 'vikbooking')),
200 el('span', {}, group),
201 ]
202 ),
203 el(
204 'div',
205 {
206 className: 'vbo-sh-info-control'
207 },
208 [
209 el('label', {}, __('Name:', 'vikbooking')),
210 el('span', {}, data.name),
211 ]
212 ),
213 el(
214 'div',
215 {
216 className: 'vbo-sh-info-control'
217 },
218 [
219 el('label', {}, __('Created on:', 'vikbooking')),
220 el('span', {}, data.createdon),
221 ]
222 ),
223 el(
224 'div',
225 {
226 className: 'vbo-sh-info-control'
227 },
228 [
229 el('label', {}, __('Assignee:', 'vikbooking')),
230 assigneeField
231 ]
232 ),
233 el(
234 'div',
235 {
236 className: 'vbo-sh-info-control'
237 },
238 el(
239 wp.components.TextareaControl,
240 {
241 label: __('Shortcode:', 'vikbooking'),
242 value: data.shortcode,
243 readonly: true,
244 }
245 )
246 ),
247 ));
248 }
249 }
250 }
251
252 // build SVG for shortcode dashicon
253 const svg = el(
254 'svg',
255 {
256 className: 'dashicon dashicons-shortcode',
257 role: 'img',
258 focusable: false,
259 xmlns: 'http://www.w3.org/2000/svg',
260 width: '20',
261 height: '20',
262 viewBox: '0 0 20 20',
263 },
264 el(
265 'path',
266 {
267 d: 'M6 14H4V6h2V4H2v12h4M7.1 17h2.1l3.7-14h-2.1M14 4v2h2v8h-2v2h4V4',
268 }
269 )
270 );
271
272 // use the new version of InspectorControls
273 // provided by WordPress 5.4, if supported
274 let InspectorControls;
275
276 if (wp.blockEditor && wp.blockEditor.InspectorControls) {
277 // use new version
278 InspectorControls = wp.blockEditor.InspectorControls;
279 } else {
280 // fallback to the older one
281 InspectorControls = wp.editor.InspectorControls;
282 }
283
284 let navBar = [];
285
286 // add shortcode accordion
287 navBar.push(
288 el(
289 wp.components.PanelBody,
290 {
291 title: __('Shortcode'),
292 initialOpen: true,
293 },
294 // add shortcodes information
295 shortcodes_boxes,
296 el(
297 wp.components.ToggleControl,
298 {
299 label: __('See all', 'vikbooking'),
300 checked: togglerChecked,
301 help: togglerChecked ? __('Display all the existing shortcodes.', 'vikbooking') : __('Toggle to display also the shortcodes that are already assigned to a post.', 'vikbooking'),
302 onChange: (toggler) => {
303 props.setAttributes({ toggler: toggler });
304 }
305 }
306 )
307 )
308 );
309
310 // in case the shortcode dropdown has the "new" option selected,
311 // display a new panel to start the creation of a new shortcode
312 if (props.attributes.shortcode === 'new') {
313
314 if (!newShortcodeRegistry) {
315 // initialize only once the registry used to hold the data
316 // of the new shortcode that we want to create
317 newShortcodeRegistry = new VBOBlockPropertiesDecorator({lang: '*'}, props);
318 } else {
319 // always reconnect the registry to the updated attributes holder
320 // as the saved reference might differ in case the block gets deleted
321 newShortcodeRegistry.connect(props);
322 }
323
324 let newItemPanelFields = [];
325
326 // add field to select the site page
327 newItemPanelFields.push(
328 el(
329 wp.components.SelectControl,
330 {
331 label: __('Page Type', 'vikbooking'),
332 help: __('Choose the type of the page that should be displayed in the front-end.', 'vikbooking'),
333 value: newShortcodeRegistry.attributes.type,
334 options: [
335 {
336 label: __('Select an option', 'vikbooking'),
337 value: '',
338 }
339 ].concat(VIKBOOKING_SHORTCODES_BLOCK.views.map((page) => {
340 return {
341 label: page.name,
342 value: page.type,
343 };
344 })),
345 onChange: (value) => {
346 // preserve only the language whenever the type changes
347 newShortcodeRegistry.replaceAttributes({type: value, lang: newShortcodeRegistry.attributes.lang});
348 newShortcodeRegistry.setAttributes({});
349 },
350 }
351 )
352 );
353
354 // add field to select the language
355 newItemPanelFields.push(
356 el(
357 wp.components.SelectControl,
358 {
359 label: __('Language', 'vikbooking'),
360 help: __('Choose whether the shortcode should be available for a specific language only.', 'vikbooking'),
361 value: newShortcodeRegistry.attributes.lang,
362 options: [
363 {
364 label: __('All', 'vikbooking'),
365 value: '*',
366 }
367 ].concat(VIKBOOKING_SHORTCODES_BLOCK.languages.map((lang) => {
368 return {
369 label: lang.name,
370 value: lang.tag,
371 };
372 })),
373 onChange: (value) => {
374 newShortcodeRegistry.setAttributes({lang: value});
375 },
376 }
377 )
378 );
379
380 // get the details of the selected view
381 let selectedView = VIKBOOKING_SHORTCODES_BLOCK.views.filter(view => view.type === newShortcodeRegistry.attributes.type).shift();
382
383 if (selectedView) {
384 selectedView.fields.forEach((field) => {
385 // check if we have a list with predefined options
386 if (Array.isArray(field.options)) {
387 // make sure the default value is supported
388 if (field.options.filter(opt => opt.value == field.value).length === 0) {
389 // take the first option as default value, or an empty array in case of multiple list
390 field.value = field.multiple ? [] : field.options[0].value;
391 }
392 }
393
394 // set up a default value for the current field
395 newShortcodeRegistry.defineAttribute(field.name, field.value);
396
397 // create element according to the provided fields
398 let fieldControl = window.vboCreateControlElement(field, newShortcodeRegistry);
399
400 // register the element within the list of fields
401 newItemPanelFields.push(fieldControl);
402 });
403 }
404
405 // add the panel to create a new shortcode at runtime
406 navBar.push(
407 el(
408 wp.components.PanelBody,
409 {
410 title: __('New Item', 'vikbooking'),
411 initialOpen: true,
412 },
413 newItemPanelFields
414 )
415 );
416
417 // add button to save the shortcode
418 newItemPanelFields.push(
419 el(
420 wp.components.Button,
421 {
422 text: __('Create Shortcode', 'vikbooking'),
423 isPrimary: true,
424 onClick: (event) => {
425 // make sure we don't have a pending request
426 // if (event.target.disabled) {
427 if (event.target.getAttribute('aria-disabled') === 'true') {
428 return false;
429 }
430
431 // prevent duplicate requests
432 event.target.setAttribute('aria-disabled', true);
433 event.target.classList.add('is-busy');
434
435 // access the post data container
436 let postData = wp.data.select('core/editor');
437
438 // access the view parameters
439 let params = Object.assign({}, newShortcodeRegistry.attributes);
440 delete params.type;
441 delete params.lang;
442
443 // get the currently selected parent for this post
444 let parentId = postData.getEditedPostAttribute('parent');
445
446 // prepare request data
447 const request = {
448 name: postData.getEditedPostAttribute('title'),
449 type: newShortcodeRegistry.attributes.type,
450 lang: newShortcodeRegistry.attributes.lang,
451 parent_id: 0,
452 jform: params,
453 };
454
455 if (parentId) {
456 request.parent_id = (
457 // we need to find the shortcode assigned to a post ID equal to the parent selected for this page/post
458 Object.values(VIKBOOKING_SHORTCODES_BLOCK.shortcodes).flat().filter(shortcode => shortcode.post_id == parentId).shift()
459 // use a placeholder in case of no matching shortcodes
460 || {id: 0}
461 ).id;
462 }
463
464 // rely on a promise for a better ease of use
465 new Promise((resolve, reject) => {
466 // validate the title first
467 if (!request.name) {
468 reject(__('You need to specify a title for this post first.', 'vikbooking'));
469 return;
470 }
471
472 // make request to save the shortcode
473 jQuery.ajax({
474 url: VIKBOOKING_SHORTCODES_BLOCK.ajaxurl,
475 type: 'post',
476 data: request,
477 }).done((shortcode) => {
478 resolve(shortcode);
479 }).fail((error) => {
480 reject(error.responseText || error.statusText);
481 });
482 }).then((shortcode) => {
483 // create a new custom slot is not yet registered
484 if (VIKBOOKING_SHORTCODES_BLOCK.shortcodes.custom === undefined) {
485 VIKBOOKING_SHORTCODES_BLOCK.shortcodes.custom = [];
486 }
487
488 // append the newly created shortcode to the custom slot
489 VIKBOOKING_SHORTCODES_BLOCK.shortcodes.custom.push(shortcode);
490
491 // update the selected shortcode
492 props.setAttributes({shortcode: shortcode.shortcode});
493 }).catch((error) => {
494 // something went wrong, alert the user
495 alert(error || __('An error has occurred', 'vikbooking'));
496
497 // re-enable save button
498 event.target.setAttribute('aria-disabled', false);
499 event.target.classList.remove('is-busy');
500 });
501 },
502 }
503 )
504 );
505 }
506
507 // setup inspector (right-side area)
508 let controls = el(
509 // create InspectorControls element
510 InspectorControls,
511 // define inspector properties
512 {
513 key: 'controls',
514 },
515 navBar
516 );
517
518 let renderer;
519
520 if (props.attributes.shortcode === 'new') {
521 renderer = el(
522 'div',
523 {
524 style: {
525 border: '2px solid #ddd',
526 padding: '10px',
527 background: '#eee',
528 },
529 },
530 __('You can create a new shortcode from the block settings under the right sidebar.', 'vikbooking')
531 );
532 } else {
533 renderer = el(
534 wp.serverSideRender,
535 {
536 block: 'vikbooking/gutenberg-shortcodes',
537 attributes: props.attributes,
538 }
539 );
540 }
541
542 return [
543 controls,
544 el(
545 // create <div> wrapper
546 'div',
547 // define wrapper properties
548 {
549 className: 'vbo-shortcode-admin-wrapper',
550 },
551 // <div> contains select
552 [
553 el(
554 // create <select> for shortcode
555 wp.components.SelectControl,
556 // define select properties
557 {
558 label: [svg, __('Shortcode')],
559 value: props.attributes.shortcode,
560 onChange: (shortcode) => {
561 props.setAttributes({shortcode: shortcode});
562 },
563 options: options,
564 className: 'wp-block-shortcode',
565 }
566 ),
567 renderer,
568 ]
569 )
570 ];
571 },
572
573 /**
574 * The save function defines the way in which the different attributes should be combined
575 * into the final markup, which is then serialized by Gutenberg into `post_content`.
576 * @link https://wordpress.org/gutenberg/handbook/block-edit-save/#save
577 *
578 * @return Element Element to render.
579 */
580 save: (props) => {
581 let shortcode = props.attributes.shortcode;
582
583 return el(
584 'div',
585 {
586 className: props.className,
587 },
588 shortcode === 'new' ? '' : shortcode
589 );
590 }
591 });
592
593 /**
594 * Use a placeholder for the props parameter provided by the edit callback.
595 */
596 const VBOBlockPropertiesDecorator = class VBOBlockPropertiesDecorator {
597 constructor(props, adaptee) {
598 this.attributes = props || {};
599
600 this.connect(adaptee);
601 }
602
603 connect(adaptee) {
604 // keep a reference to the original inspector properties holder
605 this.adaptee = adaptee;
606 }
607
608 setAttributes(props) {
609 Object.assign(this.attributes, props);
610
611 // update a temporary field within the original attributes to force the inspector refresh
612 this.adaptee.setAttributes({refreshTrigger: new Date().toISOString()});
613 }
614
615 defineAttribute(key, value) {
616 if (!this.attributes.hasOwnProperty(key) && value !== undefined && value !== null) {
617 this.attributes[key] = value;
618 }
619 }
620
621 replaceAttributes(props) {
622 this.attributes = props;
623 }
624 }
625 })(window.wp);