PluginProbe
Booking Calendar / 11.9
Booking Calendar v11.9
11.9 11.8.4 11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 All 205 releases
booking / includes / page-setup-wizard / step-publish-integration / _src / step-publish-integration.js

step-publish-integration.js in Booking Calendar 11.9, at includes/page-setup-wizard/step-publish-integration/_src/step-publish-integration.js

397 lines 13.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Drive the progressive Publish & integrate Setup Wizard page.
3 *
4 * The adapter updates presentation fields and validates the current draft. It
5 * never creates, publishes, or changes a WordPress page.
6 *
7 * @package Booking Calendar
8 */
9 ( function ( window, document ) {
10 'use strict';
11
12 var module_config = window.wpbc_setup_wizard_publish_integration || { i18n: {} };
13
14 /**
15 * Create one isolated publishing-plan step adapter.
16 *
17 * @param {Object} config Localized messages.
18 * @return {Object} Adapter accepted by the shared wizard shell.
19 */
20 function create_publish_integration_adapter( config ) {
21 var shell_api = null;
22 var editor = null;
23
24 /**
25 * Return a localized message with a safe fallback.
26 *
27 * @param {string} key Localized message key.
28 * @param {string} fallback English fallback.
29 * @return {string} Available message.
30 */
31 function get_message( key, fallback ) {
32 return config.i18n && config.i18n[ key ] ? String( config.i18n[ key ] ) : fallback;
33 }
34
35 /**
36 * Return the selected destination identifier.
37 *
38 * @return {string} Destination ID or an empty string.
39 */
40 function get_destination() {
41 var selected = editor ? editor.node.querySelector( '[data-wpbc-publish-destination]:checked' ) : null;
42
43 return selected ? String( selected.value || '' ) : '';
44 }
45
46 /**
47 * Toggle an element's semantic hidden state.
48 *
49 * @param {HTMLElement|null} element Element to update.
50 * @param {boolean} is_hidden Whether the element is hidden.
51 * @return {void}
52 */
53 function set_hidden( element, is_hidden ) {
54 if ( ! element ) {
55 return;
56 }
57
58 element.hidden = Boolean( is_hidden );
59 }
60
61 /**
62 * Build a safe root-relative URL preview from a page title.
63 *
64 * @param {string} page_title Proposed page title.
65 * @return {string} Root-relative preview URL.
66 */
67 function build_page_url( page_title ) {
68 var slug = '';
69 var home_path = String( editor.node.dataset.homePath || '/' );
70
71 if ( window.wp && window.wp.url && 'function' === typeof window.wp.url.cleanForSlug ) {
72 slug = window.wp.url.cleanForSlug( page_title );
73 } else {
74 slug = String( page_title || '' )
75 .toLowerCase()
76 .trim()
77 .replace( /[^a-z0-9\s-]/g, '' )
78 .replace( /[\s-]+/g, '-' )
79 .replace( /^-+|-+$/g, '' );
80 }
81
82 home_path = '/' + home_path.replace( /^\/+|\/+$/g, '' );
83 if ( '/' !== home_path ) {
84 home_path += '/';
85 }
86
87 return home_path + ( slug || 'booking' ) + '/';
88 }
89
90 /**
91 * Read the selected existing page's authorized display URL.
92 *
93 * @return {string} Root-relative page URL or an empty string.
94 */
95 function get_existing_page_url() {
96 var option = editor.existing_page && editor.existing_page.selectedIndex >= 0
97 ? editor.existing_page.options[ editor.existing_page.selectedIndex ]
98 : null;
99
100 return option ? String( option.dataset.pageUrl || '' ) : '';
101 }
102
103 /**
104 * Update the URL preview for the active page destination.
105 *
106 * @param {string} destination Active destination ID.
107 * @return {void}
108 */
109 function update_page_url( destination ) {
110 if ( ! editor.page_url ) {
111 return;
112 }
113
114 if ( 'create_page' === destination ) {
115 editor.page_url.value = build_page_url( editor.page_title ? editor.page_title.value : '' );
116 } else if ( 'existing_page' === destination ) {
117 editor.page_url.value = get_existing_page_url();
118 } else {
119 editor.page_url.value = '';
120 }
121 }
122
123 /**
124 * Update journey-aware content details and shortcode presentation.
125 *
126 * @return {void}
127 */
128 function update_content_details() {
129 var option = editor.content && editor.content.selectedIndex >= 0
130 ? editor.content.options[ editor.content.selectedIndex ]
131 : null;
132
133 if ( ! option ) {
134 return;
135 }
136
137 if ( editor.content_title ) {
138 editor.content_title.textContent = option.textContent.trim();
139 }
140 if ( editor.content_description ) {
141 editor.content_description.textContent = String( option.dataset.contentDescription || '' );
142 }
143 if ( editor.shortcode ) {
144 editor.shortcode.value = String( option.dataset.contentShortcode || '' );
145 }
146 if ( editor.shortcode_help ) {
147 editor.shortcode_help.href = String( option.dataset.contentHelpUrl || '' );
148 editor.shortcode_help.hidden = ! option.dataset.contentHelpUrl;
149 }
150 if ( editor.shortcode_help_label ) {
151 editor.shortcode_help_label.textContent = String( option.dataset.contentHelpLabel || '' );
152 }
153 }
154
155 /**
156 * Return destination-specific approval notice copy.
157 *
158 * @param {string} destination Active destination ID.
159 * @return {string} Notice message.
160 */
161 function get_notice( destination ) {
162 if ( 'existing_page' === destination ) {
163 return get_message( 'notice_existing', 'Save adds or updates one managed Booking Calendar block without replacing the page\'s other content.' );
164 }
165 if ( 'manual' === destination ) {
166 return get_message( 'notice_manual', 'Save records this shortcode for the setup summary. No WordPress page is created or changed.' );
167 }
168 if ( 'later' === destination ) {
169 return get_message( 'notice_later', 'No page will be created or changed. You can integrate the booking experience later.' );
170 }
171
172 return get_message( 'notice_create', 'Save this step to create the booking page now, or update the same page on a later save.' );
173 }
174
175 /**
176 * Return the server-localized primary action label for a destination.
177 *
178 * @param {string} destination Active destination ID.
179 * @return {string} Primary action label.
180 */
181 function get_action_label( destination ) {
182 if ( 'create_page' === destination ) {
183 return '1' === editor.node.dataset.hasCreatedPage
184 ? get_message( 'action_update', 'Update page & continue' )
185 : get_message( 'action_create', 'Create page & continue' );
186 }
187 if ( 'existing_page' === destination ) {
188 return get_message( 'action_update', 'Update page & continue' );
189 }
190
191 return get_message( 'action_save', 'Save & continue' );
192 }
193
194 /**
195 * Synchronize all conditional fields with the selected destination.
196 *
197 * @return {void}
198 */
199 function render_destination() {
200 var destination = get_destination();
201 var is_create = 'create_page' === destination;
202 var is_existing = 'existing_page' === destination;
203 var is_later = 'later' === destination;
204
205 editor.cards.forEach( function ( card ) {
206 var control = card.querySelector( '[data-wpbc-publish-destination]' );
207
208 card.classList.toggle( 'is-selected', Boolean( control && control.checked ) );
209 } );
210
211 set_hidden( editor.create_field, ! is_create );
212 set_hidden( editor.existing_field, ! is_existing );
213 set_hidden( editor.url_field, ! is_create && ! is_existing );
214 set_hidden( editor.settings_body, is_later );
215 set_hidden( editor.later_panel, ! is_later );
216
217 if ( editor.page_title ) {
218 editor.page_title.required = is_create;
219 }
220 if ( editor.existing_page ) {
221 editor.existing_page.required = is_existing;
222 }
223 if ( editor.content ) {
224 editor.content.required = ! is_later;
225 }
226 if ( editor.notice_text ) {
227 editor.notice_text.textContent = get_notice( destination );
228 }
229 if ( editor.continue_label ) {
230 editor.continue_label.textContent = get_action_label( destination );
231 }
232
233 update_page_url( destination );
234 }
235
236 /**
237 * Copy text while preserving keyboard focus on the invoking button.
238 *
239 * @param {string} text Text to copy.
240 * @param {HTMLElement} button Invoking button.
241 * @return {Promise<void>} Completion promise.
242 */
243 function copy_text( text, button ) {
244 var clipboard_promise;
245
246 if ( navigator.clipboard && 'function' === typeof navigator.clipboard.writeText ) {
247 clipboard_promise = navigator.clipboard.writeText( text );
248 } else {
249 clipboard_promise = new Promise( function ( resolve, reject ) {
250 var temporary = document.createElement( 'textarea' );
251
252 temporary.value = text;
253 temporary.setAttribute( 'readonly', 'readonly' );
254 temporary.style.position = 'fixed';
255 temporary.style.opacity = '0';
256 document.body.appendChild( temporary );
257 temporary.select();
258 if ( document.execCommand( 'copy' ) ) {
259 resolve();
260 } else {
261 reject( new Error( 'copy_failed' ) );
262 }
263 temporary.remove();
264 } );
265 }
266
267 return clipboard_promise.then( function () {
268 shell_api.set_status( get_message( 'copied', 'Copied to the clipboard.' ) );
269 button.focus();
270 } ).catch( function () {
271 shell_api.set_status( get_message( 'copy_failed', 'Copy failed. Select the text and copy it manually.' ) );
272 button.focus();
273 } );
274 }
275
276 /**
277 * Handle destination and copy-button clicks.
278 *
279 * @param {MouseEvent} event Click event.
280 * @return {void}
281 */
282 function handle_click( event ) {
283 var copy_button = event.target.closest( '[data-wpbc-publish-copy]' );
284 var copy_type;
285 var copy_source;
286
287 if ( ! copy_button || ! editor.node.contains( copy_button ) ) {
288 return;
289 }
290
291 copy_type = String( copy_button.dataset.wpbcPublishCopy || '' );
292 copy_source = 'page-url' === copy_type ? editor.page_url : editor.shortcode;
293 if ( copy_source && copy_source.value ) {
294 copy_text( String( copy_source.value ), copy_button );
295 }
296 }
297
298 /**
299 * Handle select and radio changes.
300 *
301 * @param {Event} event Change event.
302 * @return {void}
303 */
304 function handle_change( event ) {
305 if ( event.target.matches( '[data-wpbc-publish-destination]' ) ) {
306 render_destination();
307 } else if ( event.target === editor.content ) {
308 update_content_details();
309 } else if ( event.target === editor.existing_page ) {
310 update_page_url( get_destination() );
311 }
312 }
313
314 /**
315 * Initialize the adapter against shell-owned services.
316 *
317 * @param {Object} registered_shell_api Shared wizard adapter services.
318 * @return {void}
319 */
320 function initialize( registered_shell_api ) {
321 var editor_node;
322
323 shell_api = registered_shell_api;
324 editor_node = shell_api.root.querySelector( '[data-wpbc-publish-integration-editor]' );
325 if ( ! editor_node ) {
326 return;
327 }
328
329 editor = {
330 node: editor_node,
331 cards: Array.prototype.slice.call( editor_node.querySelectorAll( '.wpbc_setup_wizard__publish-destination-card' ) ),
332 create_field: editor_node.querySelector( '[data-wpbc-publish-create-field]' ),
333 existing_field: editor_node.querySelector( '[data-wpbc-publish-existing-field]' ),
334 url_field: editor_node.querySelector( '[data-wpbc-publish-page-url-field]' ),
335 settings_body: editor_node.querySelector( '[data-wpbc-publish-settings-body]' ),
336 later_panel: editor_node.querySelector( '[data-wpbc-publish-later-panel]' ),
337 page_title: editor_node.querySelector( '[data-wpbc-publish-page-title]' ),
338 existing_page: editor_node.querySelector( '[data-wpbc-publish-existing-page]' ),
339 page_url: editor_node.querySelector( '[data-wpbc-publish-page-url]' ),
340 content: editor_node.querySelector( '[data-wpbc-publish-page-content]' ),
341 content_title: editor_node.querySelector( '[data-wpbc-publish-content-title]' ),
342 content_description: editor_node.querySelector( '[data-wpbc-publish-content-description]' ),
343 shortcode_help: editor_node.querySelector( '[data-wpbc-publish-shortcode-help]' ),
344 shortcode_help_label: editor_node.querySelector( '[data-wpbc-publish-shortcode-help-label]' ),
345 shortcode: editor_node.querySelector( '[data-wpbc-publish-shortcode]' ),
346 notice_text: editor_node.querySelector( '[data-wpbc-publish-notice-text]' ),
347 continue_label: shell_api.root.querySelector( '[data-wpbc-setup-wizard-direction="next"] > span' )
348 };
349
350 editor.node.addEventListener( 'click', handle_click );
351 editor.node.addEventListener( 'change', handle_change );
352 if ( editor.page_title ) {
353 editor.page_title.addEventListener( 'input', function () {
354 update_page_url( get_destination() );
355 } );
356 }
357
358 render_destination();
359 update_content_details();
360 }
361
362 /**
363 * Validate destination-specific fields before forward navigation.
364 *
365 * @return {HTMLElement|null} First invalid control or null.
366 */
367 function validate() {
368 var destination = get_destination();
369
370 if ( 'create_page' === destination && ( ! editor.page_title || ! editor.page_title.value.trim() ) ) {
371 return shell_api.set_field_error( 'publish_page_title', get_message( 'title_required', 'Enter a page title.' ) );
372 }
373 if ( 'existing_page' === destination && ( ! editor.existing_page || '0' === editor.existing_page.value ) ) {
374 return shell_api.set_field_error( 'publish_existing_page_id', get_message( 'page_required', 'Choose a WordPress page.' ) );
375 }
376 if ( 'later' !== destination && ( ! editor.content || ! editor.content.value ) ) {
377 return shell_api.set_field_error( 'publish_page_content', get_message( 'content_required', 'Choose the booking experience to display.' ) );
378 }
379
380 return null;
381 }
382
383 return {
384 initialize: initialize,
385 sync: render_destination,
386 validate: validate
387 };
388 }
389
390 window.wpbc_setup_editor_modules = window.wpbc_setup_editor_modules || {};
391 window.wpbc_setup_editor_modules.publish_integration = { create: create_publish_integration_adapter };
392
393 if ( window.wpbc_setup_wizard_api && 'function' === typeof window.wpbc_setup_wizard_api.register_step_adapter ) {
394 window.wpbc_setup_wizard_api.register_step_adapter( create_publish_integration_adapter( module_config ) );
395 }
396 }( window, document ) );
397