function getCleanDomain() { return document.location.hostname } function createButtonFromData(formData) { let domainType = 'STARTER' if (formData && formData.domain) { domainType = formData.domain.type } return { "userId": cnb_preview_data.user.id, "domains": [ { "id": "domain", "user": cnb_preview_data.user.id, "type": domainType, "name": getCleanDomain() } ], "buttons": [ { "id": "button", "domain": getCleanDomain(), "domainId": "domain", "active": true, "name": "Live preview", "type": formData.button.type, "options": formData.button.options, "multiButtonOptions": formData.button.multiButtonOptions, "actions": Object.values(formData.actions_ordered), "conditions": [] } ], "actions": Object.values(formData.actions), "conditions": [], "options": { "debugMode": false, "cssLocation": cnb_preview_data.cssLocation + "/css/main.css", "apiRoot": cnb_preview_data.apiRoot, ...formData.options } } } /** * Via https://dev.to/afewminutesofcode/how-to-convert-an-array-into-an-object-in-javascript-25a4 * * NOTE that this does not work for "daysOfWeek", since the proper order of the array is lost. */ function convertArrayToObject (array, key) { const initialValue = {} return array.reduce((obj, item) => { return { ...obj, [item[key]]: item, } }, initialValue) } async function livePreview() { const parsedData = jQuery('.cnb-container').serializeAssoc() // Find a button via JS (instead of via a form) if (typeof cnb_button !== 'undefined') { parsedData.button = cnb_button } // If there is no button at all, fabricate one // This might happen when no Button is associated with an action if (!parsedData.button) { parsedData.button = { type: 'SINGLE', options: {}, multiButtonOptions: {}, } } if (typeof cnb_options !== 'undefined') { parsedData.options = cnb_preview_data.options } // Ensure it is always visible parsedData.button.options.displayMode = 'ALWAYS' // This ensures that the scroll option(s) do not affect the preview delete parsedData.button.options.scroll // Ensure all Actions are visible if (typeof cnb_actions !== 'undefined' && cnb_ignore_schedule) { cnb_actions = cnb_actions.map((item) => { item.schedule.showAlways = true return item }) } if (!cnb_ignore_schedule) { jQuery('#phone-preview').addClass('using-scheduler') } // This ensures we keep the order in the table parsedData.actions_ordered = [] if (parsedData.actions) { parsedData.actions_ordered = Object.values(parsedData.actions).map((item) => item.id) } // Ensure a Multi button / Buttonbar gets its actions if (typeof cnb_actions !== 'undefined') { if (parsedData && parsedData.actions && ((parsedData.actions[Object.keys(parsedData.actions)[0]] && parsedData.actions[Object.keys(parsedData.actions)[0]].actionType) || parsedData.actions.new)) { // Editing Multi & Full Button parsedData.actions = Object.assign(convertArrayToObject(cnb_actions, 'id'), parsedData.actions) // Since we're editing, we need to do this a bit different. Also see 'new' logic below parsedData.actions_ordered = Object.values(parsedData.actions).map((item) => item.id) } else { // Overview Multi & Full Button parsedData.actions = convertArrayToObject(cnb_actions, 'id') } } // Ensure a "new" Action (in case of a new SINGLE) gets an ID to work with if (parsedData && parsedData.actions && parsedData.actions.new) { parsedData.actions.new.id = 'new' parsedData.actions_ordered.pop() parsedData.actions_ordered.push('new') } if (typeof cnb_actions !== 'undefined') { cnb_actions = cnb_actions.map((item) => { item.schedule.showAlways = item.schedule.showAlways || item.schedule.showAlways === 'true' item.schedule.daysOfWeek = item.schedule.daysOfWeek.map((daysOfWeek) => daysOfWeek ) return item }) } // Fix: Force all booleans for schedule (and force daysOfWeek into array) if (!cnb_ignore_schedule && parsedData.action_id && parsedData.actions && parsedData.actions[parsedData.action_id]) { const showAlways = parsedData.actions[parsedData.action_id].schedule.showAlways parsedData.actions[parsedData.action_id].schedule.showAlways = showAlways !== "false" const daysOfWeek = [0, 1, 2, 3, 4, 5, 6] let newDaysOfWeek = [] for (const day in daysOfWeek) { const ele = jQuery('#cnb_weekday_' + day) newDaysOfWeek[day] = ele.prop('checked') } parsedData.actions[parsedData.action_id].schedule.daysOfWeek = newDaysOfWeek } // Fix iconenabled (should be true/false instead of 0/1) if (parsedData.action_id && parsedData.actions && parsedData.actions[parsedData.action_id]) { const iconEnabled = parsedData.actions[parsedData.action_id].iconEnabled parsedData.actions[parsedData.action_id].iconEnabled = iconEnabled !== "0" } if (typeof cnb_domain !== 'undefined') { parsedData.domain = cnb_domain } // Ensure WhatsApp/Signal works if (parsedData.action_id && parsedData.actions && parsedData.actions[parsedData.action_id]) { const viberIntlInput = parsedData.actions[parsedData.action_id].actionType === 'VIBER' && (parsedData.actions[parsedData.action_id].properties['viber-link-type'] === 'CHAT' || parsedData.actions[parsedData.action_id].properties['viber-link-type'] === 'ADD_NUMBER') if ( parsedData.actions[parsedData.action_id].actionType === 'WHATSAPP' || parsedData.actions[parsedData.action_id].actionType === 'SIGNAL' || viberIntlInput ) { const input = document.querySelector('#cnb_action_value_input_whatsapp') const iti = window.intlTelInputGlobals.getInstance(input) const number = iti.getNumber() if (number) { parsedData.actions[parsedData.action_id].actionValue = number } } } // Fix: Filter out LINK and IFRAME actions, then test if the actionValue is a proper URL, otherwise show an error for (const actionId in parsedData.actions) { const action = parsedData.actions[actionId] const result = verifyAction(action) if (!result) { delete parsedData.actions[actionId] parsedData.actions_ordered = parsedData.actions_ordered.filter(item => item !== actionId); } } // Delete old items jQuery('.cnb-single.call-now-button').remove() jQuery('.cnb-full.call-now-button').remove() jQuery('.cnb-multi.call-now-button').remove() jQuery('.cnb-dots.call-now-button').remove() jQuery('.cnb-message-modal').remove() const cnbData = createButtonFromData(parsedData) const previewContainer = jQuery('#cnb-button-preview') previewContainer.text('') if (typeof CNB !== 'undefined') { // pass "false" to ensure we do NOT add the client's native observers const result = await CNB.render(cnbData, false) const isWhatsappEditScreen = parsedData.action_id && parsedData.actions && parsedData.actions[parsedData.action_id] && parsedData.actions[parsedData.action_id].actionType === 'WHATSAPP' // If there is a Whatsapp modal, trigger it // The "parsedData.action_id" check is to ensure it does not expand on a FULL or MULTI overview page // We should also NOT expand if the edit screen for the current type is NOT a WhatsApp edit screen // Basically, only expand on a WhatsApp edit screen if (isWhatsappEditScreen) { const whatsappButton = jQuery('.call-now-button a[data-action-type="WHATSAPP"]') if (whatsappButton.length > 0) { whatsappButton[0].dispatchEvent(new window.CustomEvent('toggle')) } } // If there is a Multibutton, expand it (test this AFTER the modal, so we only toggle if it isn't ALREADY expanded) if (!window.cnbMultiDoNotExpand) { const multiButton = jQuery('.cnb-multi.call-now-button:not(.cnb-expand) .cnb-floating-main') if (multiButton.length > 0) { multiButton[0].dispatchEvent(new window.CustomEvent('toggle')) } } window.cnbMultiDoNotExpand = undefined // Move the result into a new special div (if found) const button = jQuery('.cnb-single.call-now-button, .cnb-full.call-now-button, .cnb-multi.call-now-button, .cnb-dots.call-now-button').detach() if (previewContainer.length > 0) { previewContainer.append(button) } // There are no actions to work with... const previewMoment = jQuery('.cnb-preview-moment') previewMoment.show() if (parsedData.actions && Object.keys(parsedData.actions).length === 0) { let message = '<h3 class="cnb_inscreen_notification">Nothing to show yet...</h3><p class="cnb_inscreen_notification">Once you add an Action, a preview of your Button will be shown here..</p>' previewContainer.html(message) previewMoment.hide() } else if (result.length === 0 && !cnb_ignore_schedule) { let message = '<h3 class="cnb_inscreen_notification">Nothing to show...</h3><p class="cnb_inscreen_notification">Following your schedule there\'s <strong>nothing to display at the current time</strong>.</p>' message += '<p class="cnb_inscreen_notification">You can adjust the day and time at the top of this screen to preview what your visitors will see at the selected time.</p>' previewContainer.html(message) } return result } } function verifyAction(action) { if (action.actionType === 'LINK' || action.actionType === 'IFRAME') { try { new URL(action.actionValue) jQuery('#cnb_action_value_input')[0]?.setCustomValidity(''); return true; } catch (e) { jQuery('#cnb_action_value_input')[0]?.setCustomValidity('This action requires a valid URL, including the protocol (http:// or https://).'); return false } } return true } /*** Scheduler: Day and Time selector **/ function updateScheduler(day, hour, minute) { const date = new Date() date.setHours(hour) date.setMinutes(minute) date.setSeconds(0) // Settings day is weird... const currentDay = date.getDay() const distance = day - currentDay date.setDate(date.getDate() + distance) cnb_options.date = date.getTime() // Trigger a rerender livePreview() } function updateSchedulerCall() { const day = jQuery('#call-now-button-preview-selector-day').val() const hour = jQuery('#call-now-button-preview-selector-hour').val() const minute = jQuery('#call-now-button-preview-selector-minute').val() updateScheduler(day, hour, minute) } function initPreviewDayAndTimeSelector() { jQuery('.call-now-button-preview-selector').on('change', () => { updateSchedulerCall() }) } /*** END: Scheduler: Day and Time selector **/ function initButtonEdit() { jQuery(() => { const idElement = jQuery('form.cnb-container :input[name="button[id]"]') if (idElement.length > 0 && !idElement.val().trim()) { return false } // Load the required dependencies and render the preview once // All refreshes happen inside formToJson() livePreview() jQuery("form.cnb-container :input").on('change input', function() { // An input can signal that the MultiButton should stay closed window.cnbMultiDoNotExpand = !!jQuery(this).closest("[data-cnb-multi-do-not-expand]").length livePreview() }) // No need to call "livePreview", this is done via the ".done()" handler on cnb_delete_action() // jQuery('form.cnb-container a[data-ajax="true"]').on('change input', function() {}) }) } jQuery(() => { // This enables the scheduler (which can be disabled on a per-screen basis) window.cnb_ignore_schedule = false initButtonEdit() initPreviewDayAndTimeSelector() })