File "gutenberg-shortcodes.js"

Full Path: /home/romayxjt/public_html/wp-content/plugins/vikbooking/admin/resources/js/gutenberg-shortcodes.js
File size: 17.13 KB
MIME-type: text/plain
Charset: utf-8

(function(wp) {

	/**
	 * Registers a new block provided a unique name and an object defining its behavior.
	 * @link https://github.com/WordPress/gutenberg/tree/master/blocks#api
	 */
	const registerBlockType = wp.blocks.registerBlockType;

	/**
	 * Returns a new element of given type. Element is an abstraction layer atop React.
	 * @link https://github.com/WordPress/gutenberg/tree/master/packages/element#element
	 */
	const el = wp.element.createElement;

	/**
	 * Retrieves the translation of text.
	 * @link https://github.com/WordPress/gutenberg/tree/master/i18n#api
	 */
	const __ = wp.i18n.__;

	/**
	 * This variable is used to keep the very first shortcode
	 * after the loading of the page.
	 */
	let currentShortcode = null;

	/**
	 * This registry holds the information specified during the
	 * creation of a new shortcode.
	 */
	let newShortcodeRegistry = null;

	/**
	 * Every block starts by registering a new block type definition.
	 * @link https://wordpress.org/gutenberg/handbook/block-api/
	 */
	registerBlockType('vikbooking/gutenberg-shortcodes', {
		/**
		 * This is the block display title, which can be translated with `i18n` functions.
		 * The block inserter will show this name.
		 */
		title: __('VikBooking Shortcode', 'vikbooking'),

		/**
		 * This is the block description, which is displayed within the right sidebar.
		 */
		description: __('Add a shortcode configured through VikBooking.', 'vikbooking'),

		/**
		 * The icon can be a DASHICON or a SVG entity.
		 * NOTE: we need to use a different icon because Gutenberg seems
		 * to have problems in displaying the coffee icon.
		 */
		icon: 'building',

		/**
		 * Blocks are grouped into categories to help users browse and discover them.
		 * The categories provided by core are `common`, `embed`, `formatting`, `layout` and `widgets`.
		 */
		category: 'widgets',

		/**
		 * Sometimes a block could have aliases that help users discover it while searching.
		 * You can do so by providing an array of terms (which can be translated).
		 * It is only allowed to add as much as three terms per block.
		 */
		keywords: [
			__('shortcodes'), __('list'), __('page'),
		],

		/**
		 * Optional block extended support features.
		 */
		supports: {
			// do not edit as HTML
			html: false,

			// use the block just once per post
			multiple: false,

			// don't allow the block to be converted into a reusable block
			reusable: false,
		},

		/**
		 * Attributes provide the structured data needs of a block.
		 * They can exist in different forms when they are serialized, 
		 * but they are declared together under a common interface.
		 */
		attributes: {
			shortcode: {
				type: 'string',
				source: 'html',
				selector: 'div',
			},
			toggler: {
				type: 'boolean',
				default: false,
			}
		},

		/**
		 * The edit function describes the structure of your block in the context of the editor.
		 * This represents what the editor will render when the block is used.
		 * @link https://wordpress.org/gutenberg/handbook/block-edit-save/#edit
		 *
		 * @param 	Object 	 props 	Properties passed from the editor.
		 *
		 * @return 	Element  Element to render.
		 */
		edit: (props) => {

			// iterate vikbooking shortcodes to build select options
			let options = [];

			let shortcodes_boxes = [];

			if (currentShortcode === null) {
				// if not set, define current value
				currentShortcode = props.attributes.shortcode;
			}

			// insert empty option
			options.push({
				label: __('- pick a shortcode -', 'vikbooking'),
				value: '',
			});

			// insert an option to support the creation of new shortcodes
			options.push({
				label: __('- create a new shortcode -', 'vikbooking'),
				value: 'new',
			});

			// evaluate if toggler checkbox is checked
			let togglerChecked = props.attributes.toggler;

			for (let group in VIKBOOKING_SHORTCODES_BLOCK.shortcodes) {
				let groups = VIKBOOKING_SHORTCODES_BLOCK.shortcodes[group];

				for (let i = 0; i < groups.length; i++) {
					let data = groups[i];

					let post_id  = parseInt(data.post_id);

					// push option only in case:
					// - toggler is enabled (see all)
					// - the shortcode is not assigned to any post
					// - the page of the shortcode is equal to this one
					// - the shortcode is equal to the current one
					if (togglerChecked || !post_id || post_id === wp.data.select('core/editor').getCurrentPostId() || data.shortcode == currentShortcode) {

						options.push({
							label: data.name,
							value: data.shortcode,
						});

						// build ASSIGNEE field
						let assigneeField = null;

						if (post_id && data.shortcode != currentShortcode) {
							assigneeField = el(
								'a',
								{
									href: 'javascript:void(0);',
									onClick: () => {
										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'));
									},
								},
								el(
									'span',
									{
										className: 'assigned',
									},
									__('Post #', 'vikbooking') + post_id
								)
							);
						} else {
							// safe shortcode
							assigneeField = el('span', {}, (post_id ? __('Post #', 'vikbooking') + post_id : '--'));
						}

						// check if the box should be displayed
						let toggled = props.attributes.shortcode == data.shortcode;

						// setup information div
						shortcodes_boxes.push(el(
							'div',
							{
								className: 'vikbooking-shortcode-info-box' + (toggled ? ' toggled' : ''),
							},
							// create child elements
							el(
								'div',
								{
									className: 'vbo-sh-info-control'
								},
								[
									el('label', {}, __('Type:', 'vikbooking')),
									el('span', {}, group),
								]
							),
							el(
								'div',
								{
									className: 'vbo-sh-info-control'
								},
								[
									el('label', {}, __('Name:', 'vikbooking')),
									el('span', {}, data.name),
								]
							),
							el(
								'div',
								{
									className: 'vbo-sh-info-control'
								},
								[
									el('label', {}, __('Created on:', 'vikbooking')),
									el('span', {}, data.createdon),
								]
							),
							el(
								'div',
								{
									className: 'vbo-sh-info-control'
								},
								[
									el('label', {}, __('Assignee:', 'vikbooking')),
									assigneeField
								]
							),
							el(
								'div',
								{
									className: 'vbo-sh-info-control'
								},
								el(
									wp.components.TextareaControl,
									{
										label: __('Shortcode:', 'vikbooking'),
										value: data.shortcode,
										readonly: true,
									}
								)
							),
						));
					}
				}
			}

			// build SVG for shortcode dashicon
			const svg = el(
				'svg',
				{
					className: 'dashicon dashicons-shortcode',
					role: 'img',
					focusable: false,
					xmlns: 'http://www.w3.org/2000/svg',
					width: '20',
					height: '20',
					viewBox: '0 0 20 20',
				},
				el(
					'path',
					{
						d: 'M6 14H4V6h2V4H2v12h4M7.1 17h2.1l3.7-14h-2.1M14 4v2h2v8h-2v2h4V4',
					}
				)
			);

			// use the new version of InspectorControls
			// provided by WordPress 5.4, if supported
			let InspectorControls;

			if (wp.blockEditor && wp.blockEditor.InspectorControls) {
				// use new version
				InspectorControls = wp.blockEditor.InspectorControls;
			} else {
				// fallback to the older one
				InspectorControls = wp.editor.InspectorControls;
			}

			let navBar = [];

			// add shortcode accordion
			navBar.push(
				el(
					wp.components.PanelBody,
					{
						title: __('Shortcode'),
						initialOpen: true,
					},
					// add shortcodes information
					shortcodes_boxes,
					el(
						wp.components.ToggleControl,
						{
							label: __('See all', 'vikbooking'),
							checked: togglerChecked,
							help: togglerChecked ? __('Display all the existing shortcodes.', 'vikbooking') : __('Toggle to display also the shortcodes that are already assigned to a post.', 'vikbooking'),
							onChange: (toggler) => {
								props.setAttributes({ toggler: toggler });
							}
						}
					)
				)
			);

			// in case the shortcode dropdown has the "new" option selected,
			// display a new panel to start the creation of a new shortcode
			if (props.attributes.shortcode === 'new') {

				if (!newShortcodeRegistry) {
					// initialize only once the registry used to hold the data
					// of the new shortcode that we want to create
					newShortcodeRegistry = new VBOBlockPropertiesDecorator({lang: '*'}, props);
				} else {
					// always reconnect the registry to the updated attributes holder 
					// as the saved reference might differ in case the block gets deleted
					newShortcodeRegistry.connect(props);
				}

				let newItemPanelFields = [];

				// add field to select the site page
				newItemPanelFields.push(
					el(
						wp.components.SelectControl,
						{
							label: __('Page Type', 'vikbooking'),
							help: __('Choose the type of the page that should be displayed in the front-end.', 'vikbooking'),
							value: newShortcodeRegistry.attributes.type,
							options: [
								{
									label: __('Select an option', 'vikbooking'),
									value: '',
								}
							].concat(VIKBOOKING_SHORTCODES_BLOCK.views.map((page) => {
								return {
									label: page.name,
									value: page.type,
								};
							})),
							onChange: (value) => {
								// preserve only the language whenever the type changes
								newShortcodeRegistry.replaceAttributes({type: value, lang: newShortcodeRegistry.attributes.lang});
								newShortcodeRegistry.setAttributes({});
							},
						}
					)
				);

				// add field to select the language
				newItemPanelFields.push(
					el(
						wp.components.SelectControl,
						{
							label: __('Language', 'vikbooking'),
							help: __('Choose whether the shortcode should be available for a specific language only.', 'vikbooking'),
							value: newShortcodeRegistry.attributes.lang,
							options: [
								{
									label: __('All', 'vikbooking'),
									value: '*',
								}
							].concat(VIKBOOKING_SHORTCODES_BLOCK.languages.map((lang) => {
								return {
									label: lang.name,
									value: lang.tag,
								};
							})),
							onChange: (value) => {
								newShortcodeRegistry.setAttributes({lang: value});
							},
						}
					)
				);

				// get the details of the selected view
				let selectedView = VIKBOOKING_SHORTCODES_BLOCK.views.filter(view => view.type === newShortcodeRegistry.attributes.type).shift();

				if (selectedView) {
					selectedView.fields.forEach((field) => {
						// check if we have a list with predefined options
						if (Array.isArray(field.options)) {
							// make sure the default value is supported
							if (field.options.filter(opt => opt.value == field.value).length === 0) {
								// take the first option as default value, or an empty array in case of multiple list
								field.value = field.multiple ? [] : field.options[0].value;
							}
						}

						// set up a default value for the current field
						newShortcodeRegistry.defineAttribute(field.name, field.value);

						// create element according to the provided fields
						let fieldControl = window.vboCreateControlElement(field, newShortcodeRegistry);

						// register the element within the list of fields
						newItemPanelFields.push(fieldControl);
					});
				}

				// add the panel to create a new shortcode at runtime
				navBar.push(
					el(
						wp.components.PanelBody,
						{
							title: __('New Item', 'vikbooking'),
							initialOpen: true,
						},
						newItemPanelFields
					)
				);

				// add button to save the shortcode
				newItemPanelFields.push(
					el(
						wp.components.Button,
						{
							text: __('Create Shortcode', 'vikbooking'),
							isPrimary: true,
							onClick: (event) => {
								// make sure we don't have a pending request
								// if (event.target.disabled) {
								if (event.target.getAttribute('aria-disabled') === 'true') {
									return false;
								}

								// prevent duplicate requests
								event.target.setAttribute('aria-disabled', true);
								event.target.classList.add('is-busy');

								// access the post data container
								let postData = wp.data.select('core/editor');

								// access the view parameters
								let params = Object.assign({}, newShortcodeRegistry.attributes);
								delete params.type;
								delete params.lang;

								// get the currently selected parent for this post
								let parentId = postData.getEditedPostAttribute('parent');

								// prepare request data
								const request = {
									name: postData.getEditedPostAttribute('title'),
									type: newShortcodeRegistry.attributes.type,
									lang: newShortcodeRegistry.attributes.lang,
									parent_id: 0,
									jform: params,
								};

								if (parentId) {
									request.parent_id = (
										// we need to find the shortcode assigned to a post ID equal to the parent selected for this page/post
										Object.values(VIKBOOKING_SHORTCODES_BLOCK.shortcodes).flat().filter(shortcode => shortcode.post_id == parentId).shift()
										// use a placeholder in case of no matching shortcodes
										|| {id: 0}
									).id;
								}

								// rely on a promise for a better ease of use
								new Promise((resolve, reject) => {
									// validate the title first
									if (!request.name) {
										reject(__('You need to specify a title for this post first.', 'vikbooking'));
										return;
									}

									// make request to save the shortcode
									jQuery.ajax({
										url: VIKBOOKING_SHORTCODES_BLOCK.ajaxurl,
										type: 'post',
										data: request,
									}).done((shortcode) => {
										resolve(shortcode);
									}).fail((error) => {
										reject(error.responseText || error.statusText);
									});
								}).then((shortcode) => {
									// create a new custom slot is not yet registered
									if (VIKBOOKING_SHORTCODES_BLOCK.shortcodes.custom === undefined) {
										VIKBOOKING_SHORTCODES_BLOCK.shortcodes.custom = [];
									}

									// append the newly created shortcode to the custom slot
									VIKBOOKING_SHORTCODES_BLOCK.shortcodes.custom.push(shortcode);

									// update the selected shortcode
									props.setAttributes({shortcode: shortcode.shortcode});
								}).catch((error) => {
									// something went wrong, alert the user
									alert(error || __('An error has occurred', 'vikbooking'));

									// re-enable save button
									event.target.setAttribute('aria-disabled', false);
									event.target.classList.remove('is-busy');
								});
							},
						}
					)
				);
			}

			// setup inspector (right-side area)
			let controls = el(
				// create InspectorControls element
				InspectorControls,
				// define inspector properties
				{
					key: 'controls',
				},
				navBar
			);

			let renderer;

			if (props.attributes.shortcode === 'new') {
				renderer = el(
					'div',
					{
						style: {
							border: '2px solid #ddd',
							padding: '10px',
							background: '#eee',
						},
					},
					__('You can create a new shortcode from the block settings under the right sidebar.', 'vikbooking')
				);
			} else {
				renderer = el(
					wp.serverSideRender,
					{
						block: 'vikbooking/gutenberg-shortcodes',
						attributes: props.attributes,
					}
				);
			}

			return [
				controls,
				el(
					// create <div> wrapper
					'div',
					// define wrapper properties
					{
						className: 'vbo-shortcode-admin-wrapper',
					},
					// <div> contains select
					[
						el(
							// create <select> for shortcode
							wp.components.SelectControl,
							// define select properties
							{
								label: [svg, __('Shortcode')],
								value: props.attributes.shortcode,
								onChange: (shortcode) => {
									props.setAttributes({shortcode: shortcode});
								},
								options: options,
								className: 'wp-block-shortcode',
							}
						),
						renderer,
					]
				)
			];
		},

		/**
		 * The save function defines the way in which the different attributes should be combined
		 * into the final markup, which is then serialized by Gutenberg into `post_content`.
		 * @link https://wordpress.org/gutenberg/handbook/block-edit-save/#save
		 *
		 * @return 	Element  Element to render.
		 */
		 save: (props) => {
			let shortcode = props.attributes.shortcode;

			return el(
				'div',
				{
					className: props.className,
				},
				shortcode === 'new' ? '' : shortcode
			);
		}
	});
	
	/**
	 * Use a placeholder for the props parameter provided by the edit callback.
	 */
	const VBOBlockPropertiesDecorator = class VBOBlockPropertiesDecorator {
		constructor(props, adaptee) {
			this.attributes = props || {};

			this.connect(adaptee);
		}

		connect(adaptee) {
			// keep a reference to the original inspector properties holder
			this.adaptee = adaptee;
		}

		setAttributes(props) {
			Object.assign(this.attributes, props);

			// update a temporary field within the original attributes to force the inspector refresh
			this.adaptee.setAttributes({refreshTrigger: new Date().toISOString()});
		}

		defineAttribute(key, value) {
			if (!this.attributes.hasOwnProperty(key) && value !== undefined && value !== null) {
				this.attributes[key] = value;
			}
		}

		replaceAttributes(props) {
			this.attributes = props;
		}
	}
})(window.wp);